Update REST endpoint docs (votes.md, sessions.md), WebSocket protocol (websocket.md), OpenAPI spec, and voting guide with the new GET /api/votes, GET /api/sessions/:id/votes, and vote.received event. Made-with: Cursor
153 lines
4.8 KiB
Markdown
153 lines
4.8 KiB
Markdown
# Votes Endpoints
|
|
|
|
Real-time popularity voting. Bots or integrations send votes during live gaming sessions. Votes are matched to the currently-playing game using timestamp intervals.
|
|
|
|
## Endpoint Summary
|
|
|
|
| Method | Path | Auth | Description |
|
|
|--------|------|------|-------------|
|
|
| GET | `/api/votes` | None | Paginated vote history with filtering |
|
|
| POST | `/api/votes/live` | Bearer | Submit a live vote (up/down) |
|
|
|
|
---
|
|
|
|
## GET /api/votes
|
|
|
|
Paginated vote history with filtering. Use query parameters to filter by session, game, username, or vote type.
|
|
|
|
### Authentication
|
|
|
|
None.
|
|
|
|
### Query Parameters
|
|
|
|
| Parameter | Type | Required | Default | Description |
|
|
|-----------|------|----------|---------|-------------|
|
|
| session_id | int | No | — | Filter by session ID |
|
|
| game_id | int | No | — | Filter by game ID |
|
|
| username | string | No | — | Filter by voter username |
|
|
| vote_type | string | No | — | `"up"` or `"down"` |
|
|
| page | int | No | 1 | Page number |
|
|
| limit | int | No | 50 | Items per page (max 100) |
|
|
|
|
### Response
|
|
|
|
**200 OK**
|
|
|
|
Results are ordered by `timestamp DESC`. The `vote_type` field is returned as `"up"` or `"down"` (not raw integers).
|
|
|
|
```json
|
|
{
|
|
"votes": [
|
|
{
|
|
"id": 891,
|
|
"session_id": 5,
|
|
"game_id": 42,
|
|
"game_title": "Quiplash 3",
|
|
"pack_name": "Party Pack 7",
|
|
"username": "viewer123",
|
|
"vote_type": "up",
|
|
"timestamp": "2026-03-15T20:29:55.000Z",
|
|
"created_at": "2026-03-15T20:29:56.000Z"
|
|
}
|
|
],
|
|
"pagination": {
|
|
"page": 1,
|
|
"limit": 50,
|
|
"total": 237,
|
|
"total_pages": 5
|
|
}
|
|
}
|
|
```
|
|
|
|
### Error Responses
|
|
|
|
| Status | Body | When |
|
|
|--------|------|------|
|
|
| 400 | `{ "error": "..." }` | Invalid `session_id`, `game_id`, or `vote_type` |
|
|
| 200 | `{ "votes": [], "pagination": { "page": 1, "limit": 50, "total": 0, "total_pages": 0 } }` | No results match the filters |
|
|
|
|
---
|
|
|
|
## POST /api/votes/live
|
|
|
|
Submit a real-time up/down vote for the game currently being played. Automatically finds the active session and matches the vote to the correct game using the provided timestamp and session game intervals.
|
|
|
|
### Authentication
|
|
|
|
Bearer token required. Include in header: `Authorization: Bearer <token>`.
|
|
|
|
### Request Body
|
|
|
|
| Field | Type | Required | Description |
|
|
|-------|------|----------|-------------|
|
|
| username | string | Yes | Identifier for the voter (used for deduplication) |
|
|
| vote | string | Yes | `"up"` or `"down"` |
|
|
| timestamp | string | Yes | ISO 8601 timestamp when the vote occurred |
|
|
|
|
```json
|
|
{
|
|
"username": "viewer123",
|
|
"vote": "up",
|
|
"timestamp": "2026-03-15T20:30:00Z"
|
|
}
|
|
```
|
|
|
|
### Behavior
|
|
|
|
- Finds the active session (single session with `is_active = 1`).
|
|
- Matches the vote timestamp to the game being played at that time (uses interval between consecutive `played_at` timestamps).
|
|
- Updates game `upvotes`, `downvotes`, and `popularity_score` atomically in a transaction.
|
|
- **Deduplication:** Rejects votes from the same username within a 1-second window (409 Conflict).
|
|
- Broadcasts a `vote.received` WebSocket event to all clients subscribed to the active session. See [WebSocket Protocol](../websocket.md#votereceived) for event payload.
|
|
|
|
### Response
|
|
|
|
**200 OK**
|
|
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "Vote recorded successfully",
|
|
"session": { "id": 3, "games_played": 5 },
|
|
"game": {
|
|
"id": 42,
|
|
"title": "Quiplash 3",
|
|
"upvotes": 11,
|
|
"downvotes": 2,
|
|
"popularity_score": 9
|
|
},
|
|
"vote": {
|
|
"username": "viewer123",
|
|
"type": "up",
|
|
"timestamp": "2026-03-15T20:30:00Z"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Error Responses
|
|
|
|
| Status | Body | When |
|
|
|--------|------|------|
|
|
| 400 | `{ "error": "Missing required fields: username, vote, timestamp" }` | Missing required fields |
|
|
| 400 | `{ "error": "vote must be either \"up\" or \"down\"" }` | Invalid vote value |
|
|
| 400 | `{ "error": "Invalid timestamp format. Use ISO 8601 format (e.g., 2025-11-01T20:30:00Z)" }` | Invalid timestamp |
|
|
| 404 | `{ "error": "No active session found" }` | No session with `is_active = 1` |
|
|
| 404 | `{ "error": "No games have been played in the active session yet" }` | Active session has no games |
|
|
| 404 | `{ "error": "Vote timestamp does not match any game in the active session", "debug": { "voteTimestamp": "2026-03-15T20:30:00Z", "sessionGames": [{ "title": "Quiplash 3", "played_at": "..." }] } }` | Timestamp outside any game interval |
|
|
| 409 | `{ "error": "Duplicate vote detected (within 1 second of previous vote)", "message": "Please wait at least 1 second between votes", "timeSinceLastVote": 0.5 }` | Same username voted within 1 second |
|
|
| 500 | `{ "error": "..." }` | Server error |
|
|
|
|
### Example
|
|
|
|
```bash
|
|
curl -X POST "http://localhost:5000/api/votes/live" \
|
|
-H "Authorization: Bearer $TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"username": "viewer123",
|
|
"vote": "up",
|
|
"timestamp": "2026-03-15T20:30:00Z"
|
|
}'
|
|
```
|