diff --git a/db/migration/V0006__Alter_match_cycles_and_matches.sql b/db/migration/V0006__Alter_match_cycles_and_matches.sql index 7e09e1c..42e6c18 100644 --- a/db/migration/V0006__Alter_match_cycles_and_matches.sql +++ b/db/migration/V0006__Alter_match_cycles_and_matches.sql @@ -4,4 +4,4 @@ ALTER TABLE "match_cycles" ALTER TABLE "matches" DROP COLUMN "feedback_a", - DROP COLUMN "feedback_b"; + DROP COLUMN "feedback_b"; \ No newline at end of file diff --git a/src/main/java/org/patinanetwork/patchats/api/match/db/models/Match.java b/src/main/java/org/patinanetwork/patchats/api/match/db/models/Match.java index 2604bd5..b5be3df 100644 --- a/src/main/java/org/patinanetwork/patchats/api/match/db/models/Match.java +++ b/src/main/java/org/patinanetwork/patchats/api/match/db/models/Match.java @@ -31,11 +31,5 @@ public class Match { @Setter private String status; - @Setter - private String feedbackA; - - @Setter - private String feedbackB; - private Instant createdAt; } diff --git a/src/main/java/org/patinanetwork/patchats/api/match/db/models/MatchCycle.java b/src/main/java/org/patinanetwork/patchats/api/match/db/models/MatchCycle.java index 9866c79..4096845 100644 --- a/src/main/java/org/patinanetwork/patchats/api/match/db/models/MatchCycle.java +++ b/src/main/java/org/patinanetwork/patchats/api/match/db/models/MatchCycle.java @@ -21,6 +21,9 @@ public class MatchCycle { @Setter private Instant runAt; + @Setter + private boolean isDraft = true; + @Setter private Integer totalMembers; diff --git a/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchCycleFilterCriteria.java b/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchCycleFilterCriteria.java index 6654f1a..dcc2438 100644 --- a/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchCycleFilterCriteria.java +++ b/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchCycleFilterCriteria.java @@ -4,9 +4,9 @@ import java.util.Optional; public record MatchCycleFilterCriteria( - Optional period, Optional startTime, Optional endTime) { + Optional period, Optional startTime, Optional endTime, Optional isDraft) { public static MatchCycleFilterCriteria empty() { - return new MatchCycleFilterCriteria(Optional.empty(), Optional.empty(), Optional.empty()); + return new MatchCycleFilterCriteria(Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty()); } } diff --git a/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchCycleRepo.java b/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchCycleRepo.java index c870e14..84959ae 100644 --- a/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchCycleRepo.java +++ b/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchCycleRepo.java @@ -15,8 +15,7 @@ public interface MatchCycleRepo { * Optional fields: *
    *
  • period - *
  • totalMembers - *
  • totalMatched + *
  • isDraft *
* The id field will be auto-generated by the database. */ @@ -28,14 +27,15 @@ public interface MatchCycleRepo { *
    *
  • period *
  • runAt - *
  • totalMembers - *
  • totalMatched + *
  • isDraft *
*/ Optional updateMatchCycle(MatchCycle matchCycle); Optional getMatchCycleById(Integer id); + Optional setMatchCycleDraft(Integer id, boolean isDraft); + Optional deleteMatchCycleById(Integer id); List filterMatchCycles(MatchCycleFilterCriteria criteria); diff --git a/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchCycleSqlRepo.java b/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchCycleSqlRepo.java new file mode 100644 index 0000000..a573208 --- /dev/null +++ b/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchCycleSqlRepo.java @@ -0,0 +1,162 @@ +package org.patinanetwork.patchats.api.match.db.repos; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.Instant; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.patinanetwork.patchats.api.match.db.models.MatchCycle; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.simple.JdbcClient; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class MatchCycleSqlRepo implements MatchCycleRepo { + private final JdbcClient jdbc; + + private static final String TOTAL_MATCHED_SQL = + "(SELECT count(*) FROM matches m WHERE m.cycle_id = match_cycles.id) AS total_matched"; + + private MatchCycle parseResultSetToMatchCycle(final ResultSet rs) throws SQLException { + return MatchCycle.builder() + .id(rs.getInt("id")) + .period(rs.getString("period")) + .runAt(rs.getObject("run_at", Instant.class)) + .isDraft(rs.getBoolean("is_draft")) + .totalMembers(rs.getInt("total_members")) + .totalMatched(rs.getInt("total_matched")) + .build(); + } + + @Override + public MatchCycle createMatchCycle(MatchCycle matchCycle) { + String sql = """ + INSERT INTO "match_cycles" ( + "period", + "run_at", + "is_draft", + "total_members" + ) + VALUES( + :period, + :run_at, + :is_draft, + :total_members + ) + RETURNING + *, + %s + """.formatted(TOTAL_MATCHED_SQL); + + return jdbc.sql(sql) + .param("period", matchCycle.getPeriod()) + .param("run_at", matchCycle.getRunAt()) + .param("is_draft", matchCycle.isDraft()) + .param("total_members", matchCycle.getTotalMembers()) + .query((rs, rowNum) -> parseResultSetToMatchCycle(rs)) + .single(); + } + + @Override + public Optional updateMatchCycle(MatchCycle matchCycle) { + String sql = """ + UPDATE "match_cycles" SET + "period" = :period, + "run_at" = :run_at, + "is_draft" = :is_draft + WHERE "id" = :id + RETURNING + *, + %s + """.formatted(TOTAL_MATCHED_SQL); + + return jdbc.sql(sql) + .param("id", matchCycle.getId()) + .param("period", matchCycle.getPeriod()) + .param("run_at", matchCycle.getRunAt()) + .param("is_draft", matchCycle.isDraft()) + .query((rs, rowNum) -> parseResultSetToMatchCycle(rs)) + .optional(); + } + + @Override + public Optional getMatchCycleById(Integer id) { + String sql = """ + SELECT + *, + %s + FROM match_cycles + WHERE id = :id + """.formatted(TOTAL_MATCHED_SQL); + return jdbc.sql(sql) + .param("id", id) + .query((rs, rowNum) -> parseResultSetToMatchCycle(rs)) + .optional(); + } + + @Override + public Optional setMatchCycleDraft(Integer id, boolean isDraft) { + String sql = """ + UPDATE "match_cycles" SET + "is_draft" = :is_draft + WHERE "id" = :id + RETURNING + *, + %s + """.formatted(TOTAL_MATCHED_SQL); + + return jdbc.sql(sql) + .param("id", id) + .param("is_draft", isDraft) + .query((rs, rowNum) -> parseResultSetToMatchCycle(rs)) + .optional(); + } + + @Override + public Optional deleteMatchCycleById(Integer id) { + String sql = """ + DELETE FROM match_cycles + WHERE id = :id + RETURNING + *, + 0 AS total_matched + """; + return jdbc.sql(sql) + .param("id", id) + .query((rs, rowNum) -> parseResultSetToMatchCycle(rs)) + .optional(); + } + + @Override + public List filterMatchCycles(MatchCycleFilterCriteria criteria) { + StringBuilder sql = new StringBuilder("SELECT *, " + TOTAL_MATCHED_SQL + " FROM match_cycles WHERE 1=1"); + MapSqlParameterSource params = new MapSqlParameterSource(); + + criteria.period().ifPresent(period -> { + sql.append(" AND period = :period"); + params.addValue("period", period); + }); + + criteria.startTime().ifPresent(start -> { + sql.append(" AND run_at >= :start_time"); + params.addValue("start_time", start); + }); + + criteria.endTime().ifPresent(end -> { + sql.append(" AND run_at <= :end_time"); + params.addValue("end_time", end); + }); + + criteria.isDraft().ifPresent(isDraft -> { + sql.append(" AND is_draft = :is_draft"); + params.addValue("is_draft", isDraft); + }); + + return jdbc.sql(sql.toString()) + .paramSource(params) + .query((rs, rowNum) -> parseResultSetToMatchCycle(rs)) + .list(); + } +} diff --git a/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchRepo.java b/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchRepo.java index 8de182e..0d31f67 100644 --- a/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchRepo.java +++ b/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchRepo.java @@ -27,8 +27,6 @@ public interface MatchRepo { *
  • matchCycleId *
  • matchScore *
  • status - *
  • feedbackA - *
  • feedbackB * */ Optional updateMatch(Match match); @@ -39,7 +37,5 @@ public interface MatchRepo { Optional deleteMatchById(UUID id); - Optional recordFeedback(UUID id, UUID memberId, String feedback); - List filterMatches(MatchFilterCriteria criteria); } diff --git a/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchSqlRepo.java b/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchSqlRepo.java new file mode 100644 index 0000000..fa90dfd --- /dev/null +++ b/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchSqlRepo.java @@ -0,0 +1,169 @@ +package org.patinanetwork.patchats.api.match.db.repos; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.Instant; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.patinanetwork.patchats.api.match.db.models.Match; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.simple.JdbcClient; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +public class MatchSqlRepo implements MatchRepo { + private final JdbcClient jdbc; + + private Match parseResultSetToMatch(final ResultSet rs) throws SQLException { + return Match.builder() + .id(UUID.fromString(rs.getString("id"))) + .memberAId(UUID.fromString(rs.getString("member_a_id"))) + .memberBId(UUID.fromString(rs.getString("member_b_id"))) + .matchCycleId(rs.getInt("cycle_id")) + .matchScore(rs.getObject("match_score", Double.class)) + .status(rs.getString("status")) + .createdAt(rs.getObject("created_at", Instant.class)) + .build(); + } + + @Override + public Match createMatch(Match match) { + String sql = """ + INSERT INTO "matches" ( + "id", + "member_a_id", + "member_b_id", + "cycle_id", + "match_score", + "status" + ) + VALUES( + :id, + :member_a_id, + :member_b_id, + :cycle_id, + :match_score, + :status + ) + RETURNING + * + """; + return jdbc.sql(sql) + .param("id", match.getId()) + .param("member_a_id", match.getMemberAId()) + .param("member_b_id", match.getMemberBId()) + .param("cycle_id", match.getMatchCycleId()) + .param("match_score", match.getMatchScore()) + .param("status", match.getStatus()) + .query((rs, rowNum) -> parseResultSetToMatch(rs)) + .single(); + } + + @Override + public Optional updateMatch(Match match) { + String sql = """ + UPDATE "matches" SET + "member_a_id" = :member_a_id, + "member_b_id" = :member_b_id, + "cycle_id" = :cycle_id, + "match_score" = :match_score, + "status" = :status + WHERE "id" = :id + RETURNING * + """; + return jdbc.sql(sql) + .param("id", match.getId()) + .param("member_a_id", match.getMemberAId()) + .param("member_b_id", match.getMemberBId()) + .param("cycle_id", match.getMatchCycleId()) + .param("match_score", match.getMatchScore()) + .param("status", match.getStatus()) + .query((rs, rowNum) -> parseResultSetToMatch(rs)) + .optional(); + } + + @Override + public Optional getMatchById(UUID id) { + String sql = "SELECT * FROM matches WHERE id = :id"; + return jdbc.sql(sql) + .param("id", id) + .query((rs, rowNum) -> parseResultSetToMatch(rs)) + .optional(); + } + + @Override + public Optional setMatchStatus(UUID id, String status) { + String sql = """ + UPDATE "matches" SET "status" = :status + WHERE "id" = :id + RETURNING * + """; + return jdbc.sql(sql) + .param("id", id) + .param("status", status) + .query((rs, rowNum) -> parseResultSetToMatch(rs)) + .optional(); + } + + @Override + public Optional deleteMatchById(UUID id) { + String sql = "DELETE FROM matches WHERE id = :id RETURNING *"; + return jdbc.sql(sql) + .param("id", id) + .query((rs, rowNum) -> parseResultSetToMatch(rs)) + .optional(); + } + + @Override + public List filterMatches(MatchFilterCriteria criteria) { + StringBuilder sql = new StringBuilder("SELECT * FROM matches WHERE 1=1"); + MapSqlParameterSource params = new MapSqlParameterSource(); + + criteria.status().ifPresent(status -> { + sql.append(" AND status = :status"); + params.addValue("status", status); + }); + + criteria.memberId().ifPresent(memberId -> { + sql.append(" AND (member_a_id = :member_id OR member_b_id = :member_id)"); + params.addValue("member_id", memberId); + }); + + criteria.matchCycleId().ifPresent(cycleId -> { + sql.append(" AND cycle_id = :cycle_id"); + params.addValue("cycle_id", cycleId); + }); + + criteria.startTime().ifPresent(start -> { + sql.append(" AND created_at >= :start_time"); + params.addValue("start_time", start); + }); + + criteria.endTime().ifPresent(end -> { + sql.append(" AND created_at <= :end_time"); + params.addValue("end_time", end); + }); + + criteria.period().ifPresent(period -> { + sql.append(" AND cycle_id IN (SELECT id FROM match_cycles WHERE period = :period)"); + params.addValue("period", period); + }); + + criteria.memberIndustry().ifPresent(memberIndustry -> { + sql.append(" AND ("); + sql.append("member_a_id IN (SELECT id FROM members WHERE industry_pref = :member_industry)"); + sql.append(" OR "); + sql.append("member_b_id IN (SELECT id FROM members WHERE industry_pref = :member_industry)"); + sql.append(")"); + params.addValue("member_industry", memberIndustry); + }); + + return jdbc.sql(sql.toString()) + .paramSource(params) + .query((rs, rowNum) -> parseResultSetToMatch(rs)) + .list(); + } +}