docs: add game-status-by-session guide and external downstream clients reference
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -179,4 +179,4 @@ Most list endpoints return full result sets. The exception is `GET /api/votes`,
|
|||||||
- [OpenAPI Spec](openapi.yaml)
|
- [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)
|
- **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)
|
- [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)
|
||||||
|
|||||||
187
docs/api/guides/game-status-by-session.md
Normal file
187
docs/api/guides/game-status-by-session.md
Normal file
@@ -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
|
||||||
84
docs/external-downstream-clients.md
Normal file
84
docs/external-downstream-clients.md
Normal file
@@ -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
|
||||||
Reference in New Issue
Block a user