feat: Add latest-session endpoint#305
Open
Amund211 wants to merge 2 commits into
Open
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new API endpoint, POST /v1/session-at/latest, which resolves a player’s most recent stored stat timestamp from the repository and then delegates to the existing session-at pipeline to compute and return the corresponding session in the same wire format.
Changes:
- Adds
POST /v1/session-at/latestroute wiring and a new ports handler with the same middleware stack assession-at. - Introduces
app.BuildGetLatestSessionto look up the player’s latest stored stat and delegate toGetSessionAt. - Extends
PlayerRepositorywithGetMostRecentPlayerPIT(Postgres + stub) and adds unit/integration tests; extracts shared JSON marshalling helper for session-at responses.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| main.go | Wires getLatestSession and registers OPTIONS + POST /v1/session-at/latest routes. |
| internal/ports/session_at.go | Extracts shared marshalRainbowSessionAtResponse helper and reuses it in the existing handler. |
| internal/ports/latest_session.go | Adds the new MakeGetLatestSessionHandler that parses {uuid} and returns the same response shape as session-at. |
| internal/ports/latest_session_test.go | Adds handler tests mirroring session-at response behavior and error handling. |
| internal/app/latest_session.go | Adds BuildGetLatestSession to anchor GetSessionAt at the most recent stored stat time. |
| internal/app/latest_session_test.go | Adds app-layer tests for anchoring behavior, not-found behavior, and validation/error short-circuiting. |
| internal/adapters/playerrepository/repository.go | Implements GetMostRecentPlayerPIT in Postgres repo and stub. |
| internal/adapters/playerrepository/repository_test.go | Adds integration tests for “latest wins”, not-found, and cross-player isolation. |
| internal/adapters/playerrepository/interface.go | Extends repository interface with GetMostRecentPlayerPIT. |
Comment on lines
+581
to
+587
| `select | ||
| id, data_format_version, player_uuid, queried_at, player_data | ||
| from stats | ||
| where | ||
| player_uuid = $1 | ||
| order by queried_at desc | ||
| limit 1`, |
Comment on lines
+600
to
+606
| player, err := dbStatToPlayerPIT(stat) | ||
| if err != nil { | ||
| err := fmt.Errorf("failed to convert db stat to playerpit: %w", err) | ||
| reporting.Report(ctx, err, map[string]string{ | ||
| "statID": stat.ID, | ||
| }) | ||
| return nil, err |
Add POST /v1/session-at/latest, which returns a player's most recent session in the same shape as /v1/session-at given only a uuid. It runs a preliminary GetMostRecentPlayerPIT query for the player's last stored stat, then reuses GetSessionAt anchored at that timestamp so the windowed fetch, session computation, and game-segmentation are shared. The response marshalling is extracted into a shared helper so both handlers emit identical bodies. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
480270d to
243f01a
Compare
GetLatestSession anchored GetSessionAt at the most recently stored stat. An inactive player re-viewed more than 1h after their last game gets a fresh duplicate stat appended (StorePlayer only dedups within a trailing 1h window). That trailing stat isn't part of any session, so the bracket in GetSessionAt looked past the real session's end and returned "no session" despite a recent session existing. Instead, run a cheap read-only discovery pass over the player's most recent stats, compute their sessions, and anchor GetSessionAt at the latest session's End. The End is an eventful stat, so it always falls inside the bracket, and GetSessionAt still handles the refresh and game-segment derivation. The discovery pass uses a new GetRecentPlayerPITs query — full-fidelity and recency-limited, unlike the downsampling GetHistory — so reaching back is bounded by row count rather than a fixed time window, and an older-but-real last session is still found. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds
POST /v1/session-at/latest, which returns a player's most recent session given only auuid. The response body is byte-identical toPOST /v1/session-at.How
The endpoint reuses the entire
session-atpipeline rather than reimplementing session logic:GetMostRecentPlayerPIT(SELECT … ORDER BY queried_at DESC LIMIT 1) — finds the timestamp of the player's last stored stat.GetSessionAtanchored at that timestamp, which does the windowed ±24h fetch (with live refresh), session computation, and per-game segmentation.The
SessionAtResult → rainbowresponse marshalling is extracted into a shared helper (marshalRainbowSessionAtResponse) used by both handlers, so the two endpoints can't drift.Behavioural note
GetSessionAt(T_last)resolves the session bracketing the last stat. In the common case (last stat is the session's end, or the player is recently active so the session isOngoing) this is correct, and the internal refresh pulls in a just-finished game. The narrow gap: a player whose most-recent stored stat is non-eventful and old enough not to beOngoingreturns "no session" — exactly whatsession-atitself returns at that time, degrading to the frontend's existing NoSession state.Changes
playerrepository: newGetMostRecentPlayerPITon the interface, Postgres repo, and stub.app:BuildGetLatestSession(validate uuid → most-recent query →ErrPlayerNotFoundyields empty result → else delegate toGetSessionAt).ports:MakeGetLatestSessionHandler(same middleware/rate-limit stack as session-at,{uuid}-only body); shared marshalling helper extracted fromsession_at.go.main.go: wiring + route +OPTIONSCORS handler.Tests
T_lastthreaded toGetSessionAt, not-found → empty, error/invalid-uuid short-circuit.session_at_test.go.ErrPlayerNotFound, cross-player isolation.🤖 Generated with Claude Code