diff --git a/docs/api/README.md b/docs/api/README.md index d8c2615..1a78919 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -179,4 +179,4 @@ Most list endpoints return full result sets. The exception is `GET /api/votes`, - [OpenAPI Spec](openapi.yaml) - **Endpoint docs**: [Auth](endpoints/auth.md), [Games](endpoints/games.md), [Sessions](endpoints/sessions.md), [Picker](endpoints/picker.md), [Stats](endpoints/stats.md), [Votes](endpoints/votes.md), [Webhooks](endpoints/webhooks.md) - [WebSocket Protocol](websocket.md) -- **Guides**: [Getting Started](guides/getting-started.md), [Session Lifecycle](guides/session-lifecycle.md), [Voting & Popularity](guides/voting-and-popularity.md), [Webhooks & Events](guides/webhooks-and-events.md) +- **Guides**: [Getting Started](guides/getting-started.md), [Session Lifecycle](guides/session-lifecycle.md), [Game Status by Session](guides/game-status-by-session.md), [Voting & Popularity](guides/voting-and-popularity.md), [Webhooks & Events](guides/webhooks-and-events.md) diff --git a/docs/api/guides/game-status-by-session.md b/docs/api/guides/game-status-by-session.md new file mode 100644 index 0000000..c67e657 --- /dev/null +++ b/docs/api/guides/game-status-by-session.md @@ -0,0 +1,187 @@ +# Game Status by Session + +How to build a complete view of all enabled games—with popularity data and whether each game has been played in the current session. No single endpoint returns all three pieces today; this guide shows how to combine two public endpoints and join the results client-side. + +--- + +## 1. Fetch Enabled Games + +`GET /api/games?enabled=true` returns every enabled game in the catalog. Each row includes `popularity_score`, `upvotes`, `downvotes`, `play_count`, and `favor_bias`. + +**Why:** This gives you the full list of games that are available for play, along with their cumulative popularity data across all sessions. + +See [Games endpoints](../endpoints/games.md) for all available filters (`playerCount`, `drawing`, `length`, `familyFriendly`, `pack`). + +```bash +curl "http://localhost:5000/api/games?enabled=true" +``` + +**Sample response (200 OK):** + +```json +[ + { + "id": 1, + "pack_name": "Jackbox Party Pack 7", + "title": "Quiplash 3", + "min_players": 3, + "max_players": 8, + "length_minutes": 15, + "has_audience": 1, + "family_friendly": 0, + "game_type": "Writing", + "secondary_type": null, + "play_count": 4, + "popularity_score": 7, + "upvotes": 10, + "downvotes": 3, + "enabled": 1, + "favor_bias": 0, + "created_at": "2024-01-15T12:00:00.000Z" + }, + { + "id": 5, + "pack_name": "Jackbox Party Pack 9", + "title": "Fibbage 4", + "min_players": 2, + "max_players": 8, + "length_minutes": 15, + "has_audience": 1, + "family_friendly": 1, + "game_type": "Trivia", + "secondary_type": null, + "play_count": 2, + "popularity_score": 3, + "upvotes": 5, + "downvotes": 2, + "enabled": 1, + "favor_bias": 1, + "created_at": "2024-01-15T12:00:00.000Z" + } +] +``` + +--- + +## 2. Fetch Session Games + +`GET /api/sessions/{id}/games` returns games that have been added to a specific session. Each row includes `status` (`playing`, `played`, or `skipped`), `played_at`, and joined popularity fields from the game catalog. + +**Why:** This tells you which games have already been played (or are currently playing) in the session, so you can mark them in the catalog. + +See [Sessions endpoints](../endpoints/sessions.md) for full details on session game fields. + +```bash +curl "http://localhost:5000/api/sessions/5/games" +``` + +**Sample response (200 OK):** + +```json +[ + { + "id": 14, + "session_id": 5, + "game_id": 1, + "played_at": "2026-03-15T20:30:00.000Z", + "manually_added": 0, + "status": "played", + "room_code": "LSBN", + "player_count": 6, + "player_count_check_status": "completed", + "pack_name": "Jackbox Party Pack 7", + "title": "Quiplash 3", + "game_type": "Writing", + "min_players": 3, + "max_players": 8, + "popularity_score": 7, + "upvotes": 10, + "downvotes": 3 + } +] +``` + +Key fields for the join: + +| Field | Purpose | +|-------|---------| +| `game_id` | Matches `id` in the games catalog | +| `status` | `playing`, `played`, or `skipped` | +| `played_at` | When the game was added to the session | + +--- + +## 3. Combine Client-Side + +Join the two responses by matching `game_id` (from session games) to `id` (from the catalog). This produces a single list of enabled games annotated with their session play status. + +```javascript +const gamesRes = await fetch('/api/games?enabled=true'); +const games = await gamesRes.json(); + +const sessionGamesRes = await fetch('/api/sessions/5/games'); +const sessionGames = await sessionGamesRes.json(); + +const sessionGameMap = new Map( + sessionGames.map(sg => [sg.game_id, sg]) +); + +const combined = games.map(game => { + const sessionEntry = sessionGameMap.get(game.id); + return { + ...game, + playedInSession: !!sessionEntry, + sessionStatus: sessionEntry?.status ?? null, + sessionPlayedAt: sessionEntry?.played_at ?? null, + }; +}); +``` + +**Sample merged output (one entry):** + +```json +{ + "id": 1, + "pack_name": "Jackbox Party Pack 7", + "title": "Quiplash 3", + "min_players": 3, + "max_players": 8, + "length_minutes": 15, + "has_audience": 1, + "family_friendly": 0, + "game_type": "Writing", + "play_count": 4, + "popularity_score": 7, + "upvotes": 10, + "downvotes": 3, + "enabled": 1, + "favor_bias": 0, + "playedInSession": true, + "sessionStatus": "played", + "sessionPlayedAt": "2026-03-15T20:30:00.000Z" +} +``` + +Games that have not been played in the session will have `playedInSession: false`, `sessionStatus: null`, and `sessionPlayedAt: null`. + +--- + +## Quick Reference + +| Data point | `GET /api/games` | `GET /api/sessions/{id}/games` | +|------------|------------------|-------------------------------| +| Enabled status | `enabled` field + `?enabled=true` filter | Not included | +| Popularity score | `popularity_score`, `upvotes`, `downvotes` | `popularity_score`, `upvotes`, `downvotes` (joined) | +| All-time play count | `play_count` | Not included | +| Played in session | Not included | `status` field (`playing`/`played`/`skipped`) | +| Auth required | No | No | + +--- + +## Related Documentation + +- [Games endpoints](../endpoints/games.md) — full catalog CRUD, filters, favor bias +- [Sessions endpoints](../endpoints/sessions.md) — session games, status updates, room codes +- [Picker endpoint](../endpoints/picker.md) — weighted random selection with session exclusion +- [Stats endpoint](../endpoints/stats.md) — `mostPlayedGames`, `topRatedGames` +- [Voting & Popularity guide](voting-and-popularity.md) — how votes and popularity scores work diff --git a/docs/external-downstream-clients.md b/docs/external-downstream-clients.md new file mode 100644 index 0000000..d717410 --- /dev/null +++ b/docs/external-downstream-clients.md @@ -0,0 +1,84 @@ +# Manual Poll Start — Upstream Integration Guide + +## Overview + +The vote-app now supports a `poll.start` WebSocket message that explicitly triggers poll generation. This replaces the previous behavior where polls were always auto-generated on `game.ended`. + +## Poll Modes + +The vote-app has two poll modes: + +| Mode | `game.ended` behavior | `poll.start` behavior | +|------|----------------------|----------------------| +| **manual** (default) | Logged but no poll generated | Generates a new poll | +| **auto** | Generates a new poll (legacy behavior) | Generates a new poll | + +The mode is persisted in the vote-app database and survives restarts. It can be toggled from the debug panel (Game Control section). + +## Message Format + +Send this JSON message over the existing upstream WebSocket connection: + +```json +{ + "type": "poll.start", + "sessionId": 3 +} +``` + +### Fields + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `type` | string | yes | Must be `"poll.start"` | +| `sessionId` | number | no | Included for consistency; the vote-app uses its internally tracked session ID | + +The `sessionId` field is optional in practice — the vote-app tracks the active session from `session.started` / subscription events and uses that to determine which games to exclude from the poll. Including it is recommended for protocol consistency. + +## When to Send + +- **After `game.ended`** — if the vote-app is in `manual` mode, `game.ended` alone will not create a poll. Send `poll.start` when you're ready for viewers to vote on the next game. +- **At any time** — you can send `poll.start` to force a new poll regardless of mode. Any existing active poll is deactivated and replaced. + +## What Happens on Receipt + +1. The vote-app fetches enabled games from the upstream API (`GET /api/games?enabled=true`) +2. It fetches the current session's games (`GET /api/sessions/{id}/games`) to exclude recently played titles +3. It picks 3 random games (weighted by favor bias) plus an "Other" option +4. The previous active poll (if any) is deactivated +5. The new poll is created and broadcast to all connected browser clients via WebSocket + +## Example (Node.js) + +```javascript +// Assuming `ws` is your existing WebSocket connection to the vote-app upstream +// and you've already authenticated and subscribed to the session + +function startPoll(sessionId) { + ws.send(JSON.stringify({ + type: 'poll.start', + sessionId: sessionId + })); +} + +// Typical flow: game ends, wait for the right moment, then start the poll +ws.on('message', (raw) => { + const msg = JSON.parse(raw); + + if (msg.type === 'game.ended') { + // Game just ended — start poll when ready + // (in manual mode, vote-app won't auto-generate one) + startPoll(msg.data.sessionId); + } +}); +``` + +## Existing Messages (Unchanged) + +These upstream messages continue to work as before: + +- `game.ended` — in `auto` mode, still triggers poll generation; in `manual` mode, logged but no poll created +- `voting.ended` — deactivates the active poll and broadcasts the winner +- `game.started` — deactivates the active poll and hides the overlay +- `room.connected` — deactivates the active poll and broadcasts room info +- `poll.leading` — still sent by the vote-app to upstream when the leading vote changes