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 new file mode 100644 index 0000000..2604bd5 --- /dev/null +++ b/src/main/java/org/patinanetwork/patchats/api/match/db/models/Match.java @@ -0,0 +1,41 @@ +package org.patinanetwork.patchats.api.match.db.models; + +import java.time.Instant; +import java.util.UUID; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Builder +@ToString +@EqualsAndHashCode(of = "id") +public class Match { + + private UUID id; + + @Setter + private UUID memberAId; + + @Setter + private UUID memberBId; + + @Setter + private Integer matchCycleId; + + @Setter + private Double matchScore; + + @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 new file mode 100644 index 0000000..07dd0af --- /dev/null +++ b/src/main/java/org/patinanetwork/patchats/api/match/db/models/MatchCycle.java @@ -0,0 +1,34 @@ +package org.patinanetwork.patchats.api.match.db.models; + +import java.time.Instant; +import java.util.List; +import java.util.UUID; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Builder +@ToString +@EqualsAndHashCode(of = "id") +public class MatchCycle { + + private Integer id; + + @Setter + private String period; + + @Setter + private Instant runAt; + + @Setter + private Integer totalMembers; + + @Setter + private Integer totalMatched; + + @Setter + private List unmatchedIds; +} 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 new file mode 100644 index 0000000..6654f1a --- /dev/null +++ b/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchCycleFilterCriteria.java @@ -0,0 +1,12 @@ +package org.patinanetwork.patchats.api.match.db.repos; + +import java.time.Instant; +import java.util.Optional; + +public record MatchCycleFilterCriteria( + Optional period, Optional startTime, Optional endTime) { + + public static MatchCycleFilterCriteria empty() { + return new MatchCycleFilterCriteria(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 new file mode 100644 index 0000000..76519f9 --- /dev/null +++ b/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchCycleRepo.java @@ -0,0 +1,44 @@ +package org.patinanetwork.patchats.api.match.db.repos; + +import java.util.List; +import java.util.Optional; +import org.patinanetwork.patchats.api.match.db.models.MatchCycle; + +public interface MatchCycleRepo { + + /** + * @note - The provided object's methods will be overridden with any returned data from the database. + * @param matchCycle - required fields: + *
    + *
  • runAt + *
+ * Optional fields: + *
    + *
  • period + *
  • totalMembers + *
  • totalMatched + *
  • unmatchedIds + *
+ * The id field will be auto-generated by the database. + */ + MatchCycle createMatchCycle(MatchCycle matchCycle); + + /** + * @note - The provided object's methods will be overridden with any returned data from the database. + * @param matchCycle - overridden fields: + *
    + *
  • period + *
  • runAt + *
  • totalMembers + *
  • totalMatched + *
  • unmatchedIds + *
+ */ + Optional updateMatchCycle(MatchCycle matchCycle); + + Optional getMatchCycleById(Integer id); + + Optional deleteMatchCycleById(Integer id); + + List filterMatchCycles(MatchCycleFilterCriteria criteria); +} diff --git a/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchFilterCriteria.java b/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchFilterCriteria.java new file mode 100644 index 0000000..1e4cc12 --- /dev/null +++ b/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchFilterCriteria.java @@ -0,0 +1,26 @@ +package org.patinanetwork.patchats.api.match.db.repos; + +import java.time.Instant; +import java.util.Optional; +import java.util.UUID; + +public record MatchFilterCriteria( + Optional startTime, + Optional endTime, + Optional period, + Optional memberId, + Optional matchCycleId, + Optional memberIndustry, + Optional status) { + + public static MatchFilterCriteria empty() { + return new MatchFilterCriteria( + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty()); + } +} 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 new file mode 100644 index 0000000..8de182e --- /dev/null +++ b/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchRepo.java @@ -0,0 +1,45 @@ +package org.patinanetwork.patchats.api.match.db.repos; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import org.patinanetwork.patchats.api.match.db.models.Match; + +public interface MatchRepo { + /** + * @note - The provided object's methods will be overridden with any returned data from the database. + * @param match - required fields: + *
    + *
  • id + *
  • memberAId + *
  • memberBId + *
  • matchCycleId + *
+ */ + Match createMatch(Match match); + + /** + * @note - The provided object's methods will be overridden with any returned data from the database. + * @param match - overridden fields: + *
    + *
  • memberAId + *
  • memberBId + *
  • matchCycleId + *
  • matchScore + *
  • status + *
  • feedbackA + *
  • feedbackB + *
+ */ + Optional updateMatch(Match match); + + Optional getMatchById(UUID id); + + Optional setMatchStatus(UUID id, String status); + + 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/design.md b/src/main/java/org/patinanetwork/patchats/api/match/design.md new file mode 100644 index 0000000..c269709 --- /dev/null +++ b/src/main/java/org/patinanetwork/patchats/api/match/design.md @@ -0,0 +1,198 @@ +# Matches Spec Design + +## UI Pages +- **UI /matches/{:match_id}** -> single match for both members to view + - **GET /api/matches/{:match_id}** -> to get match info + - **PATCH /api/admin/matches/{:match_id}** -> to modify status of the match + - **DELETE /api/admin/matches/{:match_id}** -> to delete erroneous match + - **PATCH /api/matches/{:match_id}** -> to modify feedback of the match + +- **UI /matches** -> All matches for a member to view with optional filtering + - **GET /api/matches?query=** + - by start & end time + - by period + - by member id + - by status + +- **UI /admin/match_cycles** -> All match cycles for an admin to view with optional filtering + - **GET /api/match_cycles?query=** + - by time period + - by start & end time + - **POST /api/admin/match_cycles** -> to create a new match cycle + +- **UI /admin/match_cycles/{:match_cycle_id}** -> Detailed individual match cycle with its matches + - **GET /api/admin/match_cycles/{:match_cycle_id}?query=** + - by status + - **GET /api/matches?query=** + - by member industry + - **PATCH /api/admin/match_cycles/{:match_cycle_id}** -> to update a match cycle + - **DELETE /api/admin/match_cycles/{:match_cycle_id}** -> to delete match cycle and cascade to matches + - **POST /api/admin/matches** → to create one match for the cycle + - **POST /api/admin/matches/bulk** -> to create many matches for the cycle + - With link to all matches filtered to its period + + +## DTO + +`MatchStatus`: PENDING | CONFIRMED | COMPLETED | CANCELLED | SKIPPED + +### Admin Match Cycles + +| Method | Endpoint | Request | Response | +|--------|---------------------------------------------|---------------------------|------------------------------| +| POST | `/api/admin/match_cycles` | `CreateMatchCycleRequest` | `MatchCycleResponse` | +| GET | `/api/admin/match_cycles` | `MatchCycleListQuery` | `List` | +| GET | `/api/admin/match_cycles/{:match_cycle_id}` | `MatchCycleDetailQuery` | `MatchCycleDetailResponse` | +| PATCH | `/api/admin/match_cycles/{:match_cycle_id}` | `UpdateMatchCycleRequest` | `MatchCycleResponse` | +| DELETE | `/api/admin/match_cycles/{:match_cycle_id}` | — | `Void` | + + +```java +// admin list view — no embedded matches +public class MatchCycleResponse { + private Integer matchCycleId; + private String period; // e.g. "2026-07" + private Instant runAt; + private Integer totalMembers; + private Integer totalMatched; + private List unmatchedIds; // UUIDs +} + +// admin detail — cycle + filtered matches +public class MatchCycleDetailResponse { + private MatchCycleResponse cycle; + private List matches; +} + +// POST /api/admin/match_cycles +public class CreateMatchCycleRequest { + private Instant runAt; + private String period; // optional + private Integer totalMembers; + private Integer totalMatched; + private List unmatchedIds; +} + +// PATCH /api/admin/match_cycles/{id} +public class UpdateMatchCycleRequest { + private Instant runAt; // all optional (partial update) + private String period; + private Integer totalMembers; + private Integer totalMatched; + private List unmatchedIds; +} + +// GET /api/admin/match_cycles (query params) +public class MatchCycleListQuery { + private String period; + private Instant startTime; + private Instant endTime; +} + +// GET /api/admin/match_cycles/{id} (filters inner matches) +public class MatchCycleDetailQuery { + private String memberIndustry; // either member's industry + private String status; +} +``` + + +### Admin Matches + +| Method | Endpoint | Request | Response | +|--------|----------------------------------|----------------------------|--------------------------| +| POST | `/api/admin/matches` | `CreateMatchRequest` | `AdminMatchResponse` | +| POST | `/api/admin/matches/bulk` | `BulkCreateMatchesRequest` | `List` | +| PATCH | `/api/admin/matches/{:match_id}` | `UpdateMatchStatusRequest` | `AdminMatchResponse` | +| DELETE | `/api/admin/matches/{:match_id}` | — | `Void` | + +```java +// admin — adds matchCycleId, matchScore, createdAt +public class AdminMatchResponse { + private String matchId; // UUID + private String memberAId; // UUID + private String memberBId; // UUID + private Integer matchCycleId; + private String month; // "YYYY-MM" + private String status; + private Double matchScore; // nullable, internal + private String feedbackA; + private String feedbackB; + private Instant createdAt; +} + +// POST /api/admin/matches +public class CreateMatchRequest { + private String memberAId; // UUID + private String memberBId; // UUID + private Integer matchCycleId; + private Double matchScore; // optional + private String status; // defaults to "PENDING" +} + +// POST /api/admin/matches/bulk +public class BulkCreateMatchesRequest { + private List matches; +} + +// PATCH /api/admin/matches/{match_id} +public class UpdateMatchStatusRequest { + private String status; +} +``` + + +### Member Matches + +| Method | Endpoint | Request | Response | +|--------|----------------------------|------------------------------|---------------------------| +| GET | `/api/matches` | `MatchListQuery` | `List` | +| GET | `/api/matches/{:match_id}` | — | `MatchResponse` | +| PATCH | `/api/matches/{:match_id}` | `UpdateMatchFeedbackRequest` | `MatchResponse` | + +```java +// public — no matchCycleId, no matchScore +public class MatchResponse { + private String matchId; // UUID + private String memberAId; // UUID + private String memberBId; // UUID + private String month; // "YYYY-MM", derived from cycle.runAt + private String status; + private String feedbackA; // free-text, nullable + private String feedbackB; // free-text, nullable +} + +// GET /api/matches (query params) +public class MatchListQuery { + private String memberId; // UUID + private String period; // "YYYY-MM" + private Instant startTime; // paired with endTime + private Instant endTime; + private String status; +} + +// PATCH /api/matches/{match_id} +public class UpdateMatchFeedbackRequest { + private String feedback; // free-text (which side = from auth context) +} +``` + + +### Layering + +**Controllers** (`api/match/`): +- `MatchController` (`/api/matches`) +- `MatchCycleController` (`/api/admin/match_cycles`) + +**Services**: orchestrate DTO ↔ model conversion, call repos. + +**Repos** (Spring JDBC, `JdbcClient`): +- `MatchRepo` — owns `matches` SQL; returns `Match` / `List` +- `MatchCycleRepo` — owns `match_cycles` SQL; returns `MatchCycle` / `List` + +**Models**: `Match`, `MatchCycle` (Lombok `@Builder`). + +**DTOs**: `MatchResponse`, `AdminMatchResponse`, `MatchCycleResponse`, etc. +Wrapped in `ApiResponder` at the controller boundary. + +