feat: ticker symbol voting for live votes
- Add ticker column to games table with migration - Bootstrap tickers from tickers.json config on startup - POST /api/votes/live accepts optional ticker field for direct game lookup (bypasses timestamp-interval matching) - Ticker votes work for any game, not just session games - Update API docs and add e2e tests for ticker voting - Version bump to 0.6.5 Made-with: Cursor
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
# 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.
|
||||
Real-time popularity voting. Bots or integrations send votes during live gaming sessions. Two voting mechanisms are supported:
|
||||
|
||||
- **`thisgame++`/`thisgame--`** — votes for the game currently being played, matched via timestamp intervals.
|
||||
- **Ticker voting** — votes for a specific game by its ticker symbol (e.g. `QPL3` for Quiplash 3), regardless of what is currently being played.
|
||||
|
||||
## Endpoint Summary
|
||||
|
||||
@@ -71,7 +74,10 @@ Results are ordered by `timestamp DESC`. The `vote_type` field is returned as `"
|
||||
|
||||
## 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.
|
||||
Submit a real-time up/down vote. Supports two independent voting mechanisms:
|
||||
|
||||
- **Ticker voting** — include a `ticker` field to vote for a specific game by symbol. The game is resolved globally and does not need to be in the active session.
|
||||
- **`thisgame++`/`thisgame--` voting** — omit `ticker` to vote for the game currently being played, matched via timestamp intervals against `session_games`.
|
||||
|
||||
### Authentication
|
||||
|
||||
@@ -84,6 +90,20 @@ Bearer token required. Include in header: `Authorization: Bearer <token>`.
|
||||
| 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 |
|
||||
| ticker | string | No | Ticker symbol identifying the game (e.g. `QPL3`, `TMP2`). When provided, the game is resolved by ticker and timestamp matching is skipped. |
|
||||
|
||||
**Ticker vote:**
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "viewer123",
|
||||
"vote": "up",
|
||||
"timestamp": "2026-03-15T20:30:00Z",
|
||||
"ticker": "QPL3"
|
||||
}
|
||||
```
|
||||
|
||||
**`thisgame++`/`thisgame--` vote (no ticker):**
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -96,7 +116,8 @@ Bearer token required. Include in header: `Authorization: Bearer <token>`.
|
||||
### 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).
|
||||
- **With `ticker`:** Looks up the game globally by ticker symbol. The game does not need to be part of the active session.
|
||||
- **Without `ticker`:** 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.
|
||||
@@ -105,6 +126,8 @@ Bearer token required. Include in header: `Authorization: Bearer <token>`.
|
||||
|
||||
**200 OK**
|
||||
|
||||
The `ticker` field is included in the response when the vote was submitted with a ticker.
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
@@ -120,7 +143,8 @@ Bearer token required. Include in header: `Authorization: Bearer <token>`.
|
||||
"vote": {
|
||||
"username": "viewer123",
|
||||
"type": "up",
|
||||
"timestamp": "2026-03-15T20:30:00Z"
|
||||
"timestamp": "2026-03-15T20:30:00Z",
|
||||
"ticker": "QPL3"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -133,12 +157,29 @@ Bearer token required. Include in header: `Authorization: Bearer <token>`.
|
||||
| 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 |
|
||||
| 404 | `{ "error": "Unknown ticker 'XYZ'" }` | Ticker does not match any game |
|
||||
| 404 | `{ "error": "No games have been played in the active session yet" }` | Active session has no games (timestamp voting only) |
|
||||
| 404 | `{ "error": "Vote timestamp does not match any game in the active session", "debug": { ... } }` | Timestamp outside any game interval (timestamp voting only) |
|
||||
| 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
|
||||
### Examples
|
||||
|
||||
**Ticker vote:**
|
||||
|
||||
```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",
|
||||
"ticker": "QPL3"
|
||||
}'
|
||||
```
|
||||
|
||||
**`thisgame++` vote (no ticker):**
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:5000/api/votes/live" \
|
||||
|
||||
Reference in New Issue
Block a user