Skip to content

feat: Add latest-session endpoint#305

Open
Amund211 wants to merge 2 commits into
mainfrom
latest-session-endpoint
Open

feat: Add latest-session endpoint#305
Amund211 wants to merge 2 commits into
mainfrom
latest-session-endpoint

Conversation

@Amund211

Copy link
Copy Markdown
Owner

What

Adds POST /v1/session-at/latest, which returns a player's most recent session given only a uuid. The response body is byte-identical to POST /v1/session-at.

POST /v1/session-at/latest
{ "uuid": "..." }

How

The endpoint reuses the entire session-at pipeline rather than reimplementing session logic:

  1. A cheap preliminary query — GetMostRecentPlayerPIT (SELECT … ORDER BY queried_at DESC LIMIT 1) — finds the timestamp of the player's last stored stat.
  2. It then delegates to the existing GetSessionAt anchored at that timestamp, which does the windowed ±24h fetch (with live refresh), session computation, and per-game segmentation.

The SessionAtResult → rainbow response 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 is Ongoing) 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 be Ongoing returns "no session" — exactly what session-at itself returns at that time, degrading to the frontend's existing NoSession state.

Changes

  • playerrepository: new GetMostRecentPlayerPIT on the interface, Postgres repo, and stub.
  • app: BuildGetLatestSession (validate uuid → most-recent query → ErrPlayerNotFound yields empty result → else delegate to GetSessionAt).
  • ports: MakeGetLatestSessionHandler (same middleware/rate-limit stack as session-at, {uuid}-only body); shared marshalling helper extracted from session_at.go.
  • main.go: wiring + route + OPTIONS CORS handler.

Tests

  • App: T_last threaded to GetSessionAt, not-found → empty, error/invalid-uuid short-circuit.
  • Ports: mirrors session_at_test.go.
  • Repository (integration): latest wins regardless of insert order, ErrPlayerNotFound, cross-player isolation.

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings July 11, 2026 20:57

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/latest route wiring and a new ports handler with the same middleware stack as session-at.
  • Introduces app.BuildGetLatestSession to look up the player’s latest stored stat and delegate to GetSessionAt.
  • Extends PlayerRepository with GetMostRecentPlayerPIT (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>
@Amund211 Amund211 force-pushed the latest-session-endpoint branch from 480270d to 243f01a Compare July 12, 2026 00:04
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants