From 882c45adcefa81ab4359df61193927629a926a3e Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 21 Jul 2026 16:06:02 -0400 Subject: [PATCH 1/8] Align match schema types: cycle_id INT, feedback TEXT From b7455136d626443972f947b7098092f8fa63700b Mon Sep 17 00:00:00 2001 From: Andrew Date: Sat, 11 Jul 2026 11:57:26 -0400 Subject: [PATCH 2/8] Init design doc --- .../patchats/api/matches/design.md | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 src/main/java/org/patinanetwork/patchats/api/matches/design.md diff --git a/src/main/java/org/patinanetwork/patchats/api/matches/design.md b/src/main/java/org/patinanetwork/patchats/api/matches/design.md new file mode 100644 index 0000000..fe6a73f --- /dev/null +++ b/src/main/java/org/patinanetwork/patchats/api/matches/design.md @@ -0,0 +1,73 @@ +### Overview + + - This API lists matches from database + +### Endpoints + +| Method | Path | Description | +|--------|------|-------------| +| GET | /api/matches/{year} | List pairings by year | +| GET | /api/matches/{year}/{month} | List pairings by month | +| GET | /api/matches/member/{id} | List pairings by member id | + + + Response: `ApiResponder` + ```typescript + interface Match { + match_id: string // UUID + member_a_id: string // UUID + member_b_id: string // UUID + month: string // YYYY-MM, from match_cycles.run_at + status: string + } + ``` +* Figured a flat list is more flexible for rendering instead of grouping by month server side + +## /api/matches/{year} +* Join on match_cycles, filtered by run_at within year +```sql + SELECT m.id, m.member_a_id, m.member_b_id, m.status, + to_char(c.run_at, 'YYYY-MM') AS month + FROM matches m + JOIN match_cycles c ON m.cycle_id = c.id + WHERE c.run_at >= TIMESTAMPTZ '2026-01-01' + AND c.run_at < TIMESTAMPTZ '2027-01-01'; + ``` + +## /api/matches/{year}/{month} +* Join on match_cycles, filtered by run_at within month +```sql + SELECT m.id, m.member_a_id, m.member_b_id, m.status, + to_char(c.run_at, 'YYYY-MM') AS month + FROM matches m + JOIN match_cycles c ON m.cycle_id = c.id + WHERE c.run_at >= TIMESTAMPTZ '2026-01-01' + AND c.run_at < TIMESTAMPTZ '2026-02-01'; +``` + +## /api/matches/member/{id} +* Filter matches by member id +```sql + SELECT m.id, m.member_a_id, m.member_b_id, m.status, + to_char(c.run_at, 'YYYY-MM') AS month + FROM matches m + JOIN match_cycles c ON m.cycle_id = c.id + WHERE m.member_a_id = :id + OR m.member_b_id = :id +``` + +### Layering + +Controller: +- `MatchController` (`/api/matches`) + * `listByYear(year)` + * `listByMonth(year, month)` + * `listByMember(id)` + +Repository: `MatchRepository` (Spring JDBC, `NamedParameterJdbcTemplate`) +- owns the SQL above; returns `List` + +DTO: `Match` (interface Match) +- `ApiResponder>` + + From fc2b8c9773a5385d4e85f40aa207b8cf654c3f09 Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 17 Jul 2026 13:17:51 -0400 Subject: [PATCH 3/8] Comments on design of api --- .../java/org/patinanetwork/patchats/api/matches/design.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/org/patinanetwork/patchats/api/matches/design.md b/src/main/java/org/patinanetwork/patchats/api/matches/design.md index fe6a73f..5ee3ef1 100644 --- a/src/main/java/org/patinanetwork/patchats/api/matches/design.md +++ b/src/main/java/org/patinanetwork/patchats/api/matches/design.md @@ -10,6 +10,12 @@ | GET | /api/matches/{year}/{month} | List pairings by month | | GET | /api/matches/member/{id} | List pairings by member id | +/api/matches/list?query +- The /year/month is too hardcoded + * Start time and end time + * period + * match cycles should be int + * search params instead of url Response: `ApiResponder` ```typescript From 44a3dff4b771a5e2efd9a52ebd023bcd0f6bc5c7 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 20 Jul 2026 15:34:39 -0400 Subject: [PATCH 4/8] Create domain models for matches --- .../patchats/api/matches/db/models/Match.java | 41 ++++ .../api/matches/db/models/MatchCycle.java | 34 +++ .../patchats/api/matches/design.md | 214 +++++++++++++++--- 3 files changed, 256 insertions(+), 33 deletions(-) create mode 100644 src/main/java/org/patinanetwork/patchats/api/matches/db/models/Match.java create mode 100644 src/main/java/org/patinanetwork/patchats/api/matches/db/models/MatchCycle.java diff --git a/src/main/java/org/patinanetwork/patchats/api/matches/db/models/Match.java b/src/main/java/org/patinanetwork/patchats/api/matches/db/models/Match.java new file mode 100644 index 0000000..bad815c --- /dev/null +++ b/src/main/java/org/patinanetwork/patchats/api/matches/db/models/Match.java @@ -0,0 +1,41 @@ +package org.patinanetwork.patchats.api.matches.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 cycleId; + + @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/matches/db/models/MatchCycle.java b/src/main/java/org/patinanetwork/patchats/api/matches/db/models/MatchCycle.java new file mode 100644 index 0000000..5886fd9 --- /dev/null +++ b/src/main/java/org/patinanetwork/patchats/api/matches/db/models/MatchCycle.java @@ -0,0 +1,34 @@ +package org.patinanetwork.patchats.api.matches.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 UUID 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/matches/design.md b/src/main/java/org/patinanetwork/patchats/api/matches/design.md index 5ee3ef1..f5e8547 100644 --- a/src/main/java/org/patinanetwork/patchats/api/matches/design.md +++ b/src/main/java/org/patinanetwork/patchats/api/matches/design.md @@ -1,33 +1,181 @@ -### Overview - - - This API lists matches from database - -### Endpoints - -| Method | Path | Description | -|--------|------|-------------| -| GET | /api/matches/{year} | List pairings by year | -| GET | /api/matches/{year}/{month} | List pairings by month | -| GET | /api/matches/member/{id} | List pairings by member id | - -/api/matches/list?query -- The /year/month is too hardcoded - * Start time and end time - * period - * match cycles should be int - * search params instead of url - - Response: `ApiResponder` - ```typescript - interface Match { - match_id: string // UUID - member_a_id: string // UUID - member_b_id: string // UUID - month: string // YYYY-MM, from match_cycles.run_at - status: string - } - ``` -* Figured a flat list is more flexible for rendering instead of grouping by month server side +# Matches Spec Design + +## UI Pages +- **UI /matches/{:match_id}** -> single match for both members to view + - **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 + - **GET /api/matches/{:match_id}** -> to get match info + +- **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 cycleId; + 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; // optiona:l + 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 MatchStatus 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 cycleId, matchScore, createdAt +public class AdminMatchResponse { + private String matchId; // UUID + private String memberAId; // UUID + private String memberBId; // UUID + private String cycleId; // UUID + private String month; // "YYYY-MM" + private MatchStatus status; + private Double matchScore; // nullable, internal + private Integer feedbackA; + private Integer feedbackB; + private Instant createdAt; +} + +// POST /api/admin/matches +public class CreateMatchRequest { + private String memberAId; // UUID + private String memberBId; // UUID + private String cycleId; // UUID + private Double matchScore; // optional + private MatchStatus 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 MatchStatus 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 cycle_id, no match_score +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 MatchStatus status; + private Integer feedbackA; // 1-5, nullable + private Integer feedbackB; // 1-5, 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 MatchStatus status; +} + +// PATCH /api/matches/{match_id} +public class UpdateMatchFeedbackRequest { + private Integer feedback; // 1-5 (which side = from auth context) +} +``` ## /api/matches/{year} * Join on match_cycles, filtered by run_at within year @@ -71,9 +219,9 @@ Controller: * `listByMember(id)` Repository: `MatchRepository` (Spring JDBC, `NamedParameterJdbcTemplate`) -- owns the SQL above; returns `List` +- owns the SQL above; returns `List` -DTO: `Match` (interface Match) -- `ApiResponder>` +DTO: `MatchResponse` (interface Match) +- `ApiResponder>` From c967b65741696a64fc632c9e16be05fe91517d19 Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 21 Jul 2026 16:27:24 -0400 Subject: [PATCH 5/8] Remove stale api/matches/ directory --- .../patchats/api/matches/db/models/Match.java | 41 ---- .../api/matches/db/models/MatchCycle.java | 34 --- .../patchats/api/matches/design.md | 227 ------------------ 3 files changed, 302 deletions(-) delete mode 100644 src/main/java/org/patinanetwork/patchats/api/matches/db/models/Match.java delete mode 100644 src/main/java/org/patinanetwork/patchats/api/matches/db/models/MatchCycle.java delete mode 100644 src/main/java/org/patinanetwork/patchats/api/matches/design.md diff --git a/src/main/java/org/patinanetwork/patchats/api/matches/db/models/Match.java b/src/main/java/org/patinanetwork/patchats/api/matches/db/models/Match.java deleted file mode 100644 index bad815c..0000000 --- a/src/main/java/org/patinanetwork/patchats/api/matches/db/models/Match.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.patinanetwork.patchats.api.matches.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 cycleId; - - @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/matches/db/models/MatchCycle.java b/src/main/java/org/patinanetwork/patchats/api/matches/db/models/MatchCycle.java deleted file mode 100644 index 5886fd9..0000000 --- a/src/main/java/org/patinanetwork/patchats/api/matches/db/models/MatchCycle.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.patinanetwork.patchats.api.matches.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 UUID 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/matches/design.md b/src/main/java/org/patinanetwork/patchats/api/matches/design.md deleted file mode 100644 index f5e8547..0000000 --- a/src/main/java/org/patinanetwork/patchats/api/matches/design.md +++ /dev/null @@ -1,227 +0,0 @@ -# Matches Spec Design - -## UI Pages -- **UI /matches/{:match_id}** -> single match for both members to view - - **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 - - **GET /api/matches/{:match_id}** -> to get match info - -- **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 cycleId; - 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; // optiona:l - 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 MatchStatus 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 cycleId, matchScore, createdAt -public class AdminMatchResponse { - private String matchId; // UUID - private String memberAId; // UUID - private String memberBId; // UUID - private String cycleId; // UUID - private String month; // "YYYY-MM" - private MatchStatus status; - private Double matchScore; // nullable, internal - private Integer feedbackA; - private Integer feedbackB; - private Instant createdAt; -} - -// POST /api/admin/matches -public class CreateMatchRequest { - private String memberAId; // UUID - private String memberBId; // UUID - private String cycleId; // UUID - private Double matchScore; // optional - private MatchStatus 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 MatchStatus 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 cycle_id, no match_score -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 MatchStatus status; - private Integer feedbackA; // 1-5, nullable - private Integer feedbackB; // 1-5, 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 MatchStatus status; -} - -// PATCH /api/matches/{match_id} -public class UpdateMatchFeedbackRequest { - private Integer feedback; // 1-5 (which side = from auth context) -} -``` - -## /api/matches/{year} -* Join on match_cycles, filtered by run_at within year -```sql - SELECT m.id, m.member_a_id, m.member_b_id, m.status, - to_char(c.run_at, 'YYYY-MM') AS month - FROM matches m - JOIN match_cycles c ON m.cycle_id = c.id - WHERE c.run_at >= TIMESTAMPTZ '2026-01-01' - AND c.run_at < TIMESTAMPTZ '2027-01-01'; - ``` - -## /api/matches/{year}/{month} -* Join on match_cycles, filtered by run_at within month -```sql - SELECT m.id, m.member_a_id, m.member_b_id, m.status, - to_char(c.run_at, 'YYYY-MM') AS month - FROM matches m - JOIN match_cycles c ON m.cycle_id = c.id - WHERE c.run_at >= TIMESTAMPTZ '2026-01-01' - AND c.run_at < TIMESTAMPTZ '2026-02-01'; -``` - -## /api/matches/member/{id} -* Filter matches by member id -```sql - SELECT m.id, m.member_a_id, m.member_b_id, m.status, - to_char(c.run_at, 'YYYY-MM') AS month - FROM matches m - JOIN match_cycles c ON m.cycle_id = c.id - WHERE m.member_a_id = :id - OR m.member_b_id = :id -``` - -### Layering - -Controller: -- `MatchController` (`/api/matches`) - * `listByYear(year)` - * `listByMonth(year, month)` - * `listByMember(id)` - -Repository: `MatchRepository` (Spring JDBC, `NamedParameterJdbcTemplate`) -- owns the SQL above; returns `List` - -DTO: `MatchResponse` (interface Match) -- `ApiResponder>` - - From 9ef9d63ea1749d0b9ea410ccbfadb186ac76d8cd Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 21 Jul 2026 16:27:36 -0400 Subject: [PATCH 6/8] Add match repos, models, and filter criteria --- .gitignore | 4 +- .../patchats/api/match/db/models/Match.java | 41 ++++ .../api/match/db/models/MatchCycle.java | 34 +++ .../db/repos/MatchCycleFilterCriteria.java | 12 + .../api/match/db/repos/MatchCycleRepo.java | 43 ++++ .../match/db/repos/MatchFilterCriteria.java | 26 ++ .../api/match/db/repos/MatchRepo.java | 45 ++++ .../patchats/api/match/design.md | 230 ++++++++++++++++++ 8 files changed, 434 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/patinanetwork/patchats/api/match/db/models/Match.java create mode 100644 src/main/java/org/patinanetwork/patchats/api/match/db/models/MatchCycle.java create mode 100644 src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchCycleFilterCriteria.java create mode 100644 src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchCycleRepo.java create mode 100644 src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchFilterCriteria.java create mode 100644 src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchRepo.java create mode 100644 src/main/java/org/patinanetwork/patchats/api/match/design.md diff --git a/.gitignore b/.gitignore index 007fd18..c45de3a 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,8 @@ ivy-cache argfile* activemq-data/ classes/ +example.md + # Log files jxl.log @@ -28,7 +30,7 @@ spring-test/test-output/ # Maven artifacts /target/ -target_test*/ +/target_test*/ # Eclipse artifacts, including WTP generated manifests bin 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..4ecf690 --- /dev/null +++ b/src/main/java/org/patinanetwork/patchats/api/match/db/repos/MatchCycleRepo.java @@ -0,0 +1,43 @@ +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: + *
    + *
  • id + *
  • runAt + *
+ * Optional fields: + *
    + *
  • period + *
  • totalMembers + *
  • totalMatched + *
  • unmatchedIds + *
+ */ + 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..8cfd091 --- /dev/null +++ b/src/main/java/org/patinanetwork/patchats/api/match/design.md @@ -0,0 +1,230 @@ +# 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) +} +``` + +## /api/matches/{year} +* Join on match_cycles, filtered by run_at within year +```sql + SELECT m.id, m.member_a_id, m.member_b_id, m.status, + to_char(c.run_at, 'YYYY-MM') AS month + FROM matches m + JOIN match_cycles c ON m.cycle_id = c.id + WHERE c.run_at >= TIMESTAMPTZ '2026-01-01' + AND c.run_at < TIMESTAMPTZ '2027-01-01'; + ``` + +## /api/matches/{year}/{month} +* Join on match_cycles, filtered by run_at within month +```sql + SELECT m.id, m.member_a_id, m.member_b_id, m.status, + to_char(c.run_at, 'YYYY-MM') AS month + FROM matches m + JOIN match_cycles c ON m.cycle_id = c.id + WHERE c.run_at >= TIMESTAMPTZ '2026-01-01' + AND c.run_at < TIMESTAMPTZ '2026-02-01'; +``` + +## /api/matches/member/{id} +* Filter matches by member id +```sql + SELECT m.id, m.member_a_id, m.member_b_id, m.status, + to_char(c.run_at, 'YYYY-MM') AS month + FROM matches m + JOIN match_cycles c ON m.cycle_id = c.id + WHERE m.member_a_id = :id + OR m.member_b_id = :id +``` + +### 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. + + From 197bc1c3ba7aed3102406158747c9b8ef9d31db7 Mon Sep 17 00:00:00 2001 From: andrew <52230726+rootandroo@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:25:11 -0400 Subject: [PATCH 7/8] Update MatchCycleRepo.java --- .../patchats/api/match/db/repos/MatchCycleRepo.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 4ecf690..aa031df 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 @@ -5,11 +5,11 @@ 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: *
    - *
  • id *
  • runAt *
* Optional fields: @@ -19,9 +19,11 @@ public interface MatchCycleRepo { *
  • 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: From d748d19060d7af81ed7817c0194b51c6bd9870c8 Mon Sep 17 00:00:00 2001 From: andrew <52230726+rootandroo@users.noreply.github.com> Date: Fri, 24 Jul 2026 13:57:40 -0400 Subject: [PATCH 8/8] Apply changes from code browser Apply changes from code browser --- .gitignore | 4 +-- .../api/match/db/repos/MatchCycleRepo.java | 1 - .../patchats/api/match/design.md | 32 ------------------- 3 files changed, 1 insertion(+), 36 deletions(-) diff --git a/.gitignore b/.gitignore index c45de3a..007fd18 100644 --- a/.gitignore +++ b/.gitignore @@ -9,8 +9,6 @@ ivy-cache argfile* activemq-data/ classes/ -example.md - # Log files jxl.log @@ -30,7 +28,7 @@ spring-test/test-output/ # Maven artifacts /target/ -/target_test*/ +target_test*/ # Eclipse artifacts, including WTP generated manifests bin 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 aa031df..76519f9 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 @@ -23,7 +23,6 @@ public interface MatchCycleRepo { */ MatchCycle createMatchCycle(MatchCycle matchCycle); - /** * @note - The provided object's methods will be overridden with any returned data from the database. * @param matchCycle - overridden fields: diff --git a/src/main/java/org/patinanetwork/patchats/api/match/design.md b/src/main/java/org/patinanetwork/patchats/api/match/design.md index 8cfd091..c269709 100644 --- a/src/main/java/org/patinanetwork/patchats/api/match/design.md +++ b/src/main/java/org/patinanetwork/patchats/api/match/design.md @@ -177,38 +177,6 @@ public class UpdateMatchFeedbackRequest { } ``` -## /api/matches/{year} -* Join on match_cycles, filtered by run_at within year -```sql - SELECT m.id, m.member_a_id, m.member_b_id, m.status, - to_char(c.run_at, 'YYYY-MM') AS month - FROM matches m - JOIN match_cycles c ON m.cycle_id = c.id - WHERE c.run_at >= TIMESTAMPTZ '2026-01-01' - AND c.run_at < TIMESTAMPTZ '2027-01-01'; - ``` - -## /api/matches/{year}/{month} -* Join on match_cycles, filtered by run_at within month -```sql - SELECT m.id, m.member_a_id, m.member_b_id, m.status, - to_char(c.run_at, 'YYYY-MM') AS month - FROM matches m - JOIN match_cycles c ON m.cycle_id = c.id - WHERE c.run_at >= TIMESTAMPTZ '2026-01-01' - AND c.run_at < TIMESTAMPTZ '2026-02-01'; -``` - -## /api/matches/member/{id} -* Filter matches by member id -```sql - SELECT m.id, m.member_a_id, m.member_b_id, m.status, - to_char(c.run_at, 'YYYY-MM') AS month - FROM matches m - JOIN match_cycles c ON m.cycle_id = c.id - WHERE m.member_a_id = :id - OR m.member_b_id = :id -``` ### Layering