1694 lines
51 KiB
YAML
1694 lines
51 KiB
YAML
|
|
openapi: 3.1.0
|
||
|
|
|
||
|
|
info:
|
||
|
|
title: Jackbox Game Picker API
|
||
|
|
description: API for managing Jackbox Party Pack games, sessions, voting, and integrations.
|
||
|
|
version: "1.0"
|
||
|
|
|
||
|
|
servers:
|
||
|
|
- url: http://localhost:5000
|
||
|
|
description: Local development (backend direct)
|
||
|
|
- url: http://localhost:3000/api
|
||
|
|
description: Docker Compose (via Vite/Nginx proxy)
|
||
|
|
|
||
|
|
tags:
|
||
|
|
- name: Health
|
||
|
|
description: Server health check
|
||
|
|
- name: Auth
|
||
|
|
description: Authentication and token verification
|
||
|
|
- name: Games
|
||
|
|
description: Game management, filtering, and pack metadata
|
||
|
|
- name: Sessions
|
||
|
|
description: Session lifecycle and game tracking
|
||
|
|
- name: Picker
|
||
|
|
description: Weighted random game selection
|
||
|
|
- name: Stats
|
||
|
|
description: Aggregate statistics
|
||
|
|
- name: Votes
|
||
|
|
description: Real-time popularity voting
|
||
|
|
- name: Webhooks
|
||
|
|
description: Webhook management for external integrations
|
||
|
|
|
||
|
|
components:
|
||
|
|
responses:
|
||
|
|
Unauthorized:
|
||
|
|
description: No access token provided
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [error]
|
||
|
|
properties:
|
||
|
|
error: { type: string, example: "Access token required" }
|
||
|
|
Forbidden:
|
||
|
|
description: Invalid or expired token
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [error]
|
||
|
|
properties:
|
||
|
|
error: { type: string, example: "Invalid or expired token" }
|
||
|
|
securitySchemes:
|
||
|
|
bearerAuth:
|
||
|
|
type: http
|
||
|
|
scheme: bearer
|
||
|
|
bearerFormat: JWT
|
||
|
|
description: >
|
||
|
|
JWT token obtained from POST /api/auth/login. Pass as Authorization Bearer <token>.
|
||
|
|
Tokens expire after 24 hours.
|
||
|
|
|
||
|
|
schemas:
|
||
|
|
Error:
|
||
|
|
type: object
|
||
|
|
required:
|
||
|
|
- error
|
||
|
|
properties:
|
||
|
|
error:
|
||
|
|
type: string
|
||
|
|
|
||
|
|
Game:
|
||
|
|
type: object
|
||
|
|
properties:
|
||
|
|
id:
|
||
|
|
type: integer
|
||
|
|
pack_name:
|
||
|
|
type: string
|
||
|
|
title:
|
||
|
|
type: string
|
||
|
|
min_players:
|
||
|
|
type: integer
|
||
|
|
max_players:
|
||
|
|
type: integer
|
||
|
|
length_minutes:
|
||
|
|
type:
|
||
|
|
- integer
|
||
|
|
- "null"
|
||
|
|
has_audience:
|
||
|
|
type: integer
|
||
|
|
enum: [0, 1]
|
||
|
|
family_friendly:
|
||
|
|
type: integer
|
||
|
|
enum: [0, 1]
|
||
|
|
game_type:
|
||
|
|
type:
|
||
|
|
- string
|
||
|
|
- "null"
|
||
|
|
secondary_type:
|
||
|
|
type:
|
||
|
|
- string
|
||
|
|
- "null"
|
||
|
|
play_count:
|
||
|
|
type: integer
|
||
|
|
popularity_score:
|
||
|
|
type: integer
|
||
|
|
upvotes:
|
||
|
|
type: integer
|
||
|
|
downvotes:
|
||
|
|
type: integer
|
||
|
|
enabled:
|
||
|
|
type: integer
|
||
|
|
enum: [0, 1]
|
||
|
|
favor_bias:
|
||
|
|
type: integer
|
||
|
|
enum: [-1, 0, 1]
|
||
|
|
created_at:
|
||
|
|
type: string
|
||
|
|
format: date-time
|
||
|
|
|
||
|
|
Session:
|
||
|
|
type: object
|
||
|
|
properties:
|
||
|
|
id:
|
||
|
|
type: integer
|
||
|
|
created_at:
|
||
|
|
type: string
|
||
|
|
format: date-time
|
||
|
|
closed_at:
|
||
|
|
type:
|
||
|
|
- string
|
||
|
|
- "null"
|
||
|
|
format: date-time
|
||
|
|
is_active:
|
||
|
|
type: integer
|
||
|
|
enum: [0, 1]
|
||
|
|
notes:
|
||
|
|
type:
|
||
|
|
- string
|
||
|
|
- "null"
|
||
|
|
games_played:
|
||
|
|
type: integer
|
||
|
|
|
||
|
|
SessionGame:
|
||
|
|
type: object
|
||
|
|
properties:
|
||
|
|
id:
|
||
|
|
type: integer
|
||
|
|
session_id:
|
||
|
|
type: integer
|
||
|
|
game_id:
|
||
|
|
type: integer
|
||
|
|
played_at:
|
||
|
|
type: string
|
||
|
|
format: date-time
|
||
|
|
manually_added:
|
||
|
|
type: integer
|
||
|
|
enum: [0, 1]
|
||
|
|
status:
|
||
|
|
type: string
|
||
|
|
enum: [playing, played, skipped]
|
||
|
|
room_code:
|
||
|
|
type:
|
||
|
|
- string
|
||
|
|
- "null"
|
||
|
|
player_count:
|
||
|
|
type:
|
||
|
|
- integer
|
||
|
|
- "null"
|
||
|
|
player_count_check_status:
|
||
|
|
type:
|
||
|
|
- string
|
||
|
|
- "null"
|
||
|
|
pack_name:
|
||
|
|
type: string
|
||
|
|
title:
|
||
|
|
type: string
|
||
|
|
game_type:
|
||
|
|
type:
|
||
|
|
- string
|
||
|
|
- "null"
|
||
|
|
min_players:
|
||
|
|
type: integer
|
||
|
|
max_players:
|
||
|
|
type: integer
|
||
|
|
popularity_score:
|
||
|
|
type: integer
|
||
|
|
upvotes:
|
||
|
|
type: integer
|
||
|
|
downvotes:
|
||
|
|
type: integer
|
||
|
|
|
||
|
|
Pack:
|
||
|
|
type: object
|
||
|
|
properties:
|
||
|
|
id:
|
||
|
|
type: integer
|
||
|
|
name:
|
||
|
|
type: string
|
||
|
|
favor_bias:
|
||
|
|
type: integer
|
||
|
|
enum: [-1, 0, 1]
|
||
|
|
created_at:
|
||
|
|
type: string
|
||
|
|
format: date-time
|
||
|
|
|
||
|
|
PackMeta:
|
||
|
|
type: object
|
||
|
|
properties:
|
||
|
|
name:
|
||
|
|
type: string
|
||
|
|
total_count:
|
||
|
|
type: integer
|
||
|
|
enabled_count:
|
||
|
|
type: integer
|
||
|
|
total_plays:
|
||
|
|
type: integer
|
||
|
|
|
||
|
|
Webhook:
|
||
|
|
type: object
|
||
|
|
properties:
|
||
|
|
id:
|
||
|
|
type: integer
|
||
|
|
name:
|
||
|
|
type: string
|
||
|
|
url:
|
||
|
|
type: string
|
||
|
|
format: uri
|
||
|
|
events:
|
||
|
|
type: array
|
||
|
|
items:
|
||
|
|
type: string
|
||
|
|
enabled:
|
||
|
|
type: boolean
|
||
|
|
created_at:
|
||
|
|
type: string
|
||
|
|
format: date-time
|
||
|
|
|
||
|
|
WebhookLog:
|
||
|
|
type: object
|
||
|
|
properties:
|
||
|
|
id:
|
||
|
|
type: integer
|
||
|
|
webhook_id:
|
||
|
|
type: integer
|
||
|
|
event_type:
|
||
|
|
type: string
|
||
|
|
payload:
|
||
|
|
type: object
|
||
|
|
response_status:
|
||
|
|
type:
|
||
|
|
- integer
|
||
|
|
- "null"
|
||
|
|
error_message:
|
||
|
|
type:
|
||
|
|
- string
|
||
|
|
- "null"
|
||
|
|
created_at:
|
||
|
|
type: string
|
||
|
|
format: date-time
|
||
|
|
|
||
|
|
paths:
|
||
|
|
/health:
|
||
|
|
get:
|
||
|
|
operationId: getHealth
|
||
|
|
summary: Health check
|
||
|
|
tags: [Health]
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: API is running
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [status, message]
|
||
|
|
properties:
|
||
|
|
status: { type: string, example: ok }
|
||
|
|
message: { type: string, example: "Jackbox Game Picker API is running" }
|
||
|
|
|
||
|
|
/api/auth/login:
|
||
|
|
post:
|
||
|
|
operationId: loginWithAdminKey
|
||
|
|
summary: Authenticate with admin key
|
||
|
|
tags: [Auth]
|
||
|
|
requestBody:
|
||
|
|
required: true
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [key]
|
||
|
|
properties:
|
||
|
|
key: { type: string }
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Authentication successful
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [token, message, expiresIn]
|
||
|
|
properties:
|
||
|
|
token: { type: string }
|
||
|
|
message: { type: string, example: "Authentication successful" }
|
||
|
|
expiresIn: { type: string, example: "24h" }
|
||
|
|
"400":
|
||
|
|
description: Admin key is required
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [error]
|
||
|
|
properties:
|
||
|
|
error: { type: string, example: "Admin key is required" }
|
||
|
|
"401":
|
||
|
|
description: Invalid admin key
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [error]
|
||
|
|
properties:
|
||
|
|
error: { type: string, example: "Invalid admin key" }
|
||
|
|
|
||
|
|
/api/auth/verify:
|
||
|
|
post:
|
||
|
|
operationId: verifyToken
|
||
|
|
summary: Verify JWT token
|
||
|
|
tags: [Auth]
|
||
|
|
security: [{ bearerAuth: [] }]
|
||
|
|
responses:
|
||
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
||
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
||
|
|
"200":
|
||
|
|
description: Token is valid
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [valid, user]
|
||
|
|
properties:
|
||
|
|
valid: { type: boolean, example: true }
|
||
|
|
user:
|
||
|
|
type: object
|
||
|
|
properties:
|
||
|
|
role: { type: string, example: admin }
|
||
|
|
timestamp: { type: number }
|
||
|
|
|
||
|
|
/api/games:
|
||
|
|
get:
|
||
|
|
operationId: listGames
|
||
|
|
summary: List games with optional filters
|
||
|
|
tags: [Games]
|
||
|
|
parameters:
|
||
|
|
- name: enabled
|
||
|
|
in: query
|
||
|
|
schema: { type: string, enum: ["true", "false"] }
|
||
|
|
- name: playerCount
|
||
|
|
in: query
|
||
|
|
schema: { type: integer }
|
||
|
|
- name: drawing
|
||
|
|
in: query
|
||
|
|
schema: { type: string, enum: ["only", "exclude"] }
|
||
|
|
- name: length
|
||
|
|
in: query
|
||
|
|
schema: { type: string, enum: ["short", "medium", "long"] }
|
||
|
|
- name: familyFriendly
|
||
|
|
in: query
|
||
|
|
schema: { type: string, enum: ["true", "false"] }
|
||
|
|
- name: pack
|
||
|
|
in: query
|
||
|
|
schema: { type: string }
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Array of games ordered by pack_name, title
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: array
|
||
|
|
items: { $ref: "#/components/schemas/Game" }
|
||
|
|
post:
|
||
|
|
operationId: createGame
|
||
|
|
summary: Create a new game
|
||
|
|
tags: [Games]
|
||
|
|
security: [{ bearerAuth: [] }]
|
||
|
|
requestBody:
|
||
|
|
required: true
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [pack_name, title, min_players, max_players]
|
||
|
|
properties:
|
||
|
|
pack_name: { type: string }
|
||
|
|
title: { type: string }
|
||
|
|
min_players: { type: integer }
|
||
|
|
max_players: { type: integer }
|
||
|
|
length_minutes: { type: integer }
|
||
|
|
has_audience: { type: boolean }
|
||
|
|
family_friendly: { type: boolean }
|
||
|
|
game_type: { type: string }
|
||
|
|
secondary_type: { type: string }
|
||
|
|
responses:
|
||
|
|
"201":
|
||
|
|
description: Game created
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Game" }
|
||
|
|
"400":
|
||
|
|
description: Missing required fields
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [error]
|
||
|
|
properties:
|
||
|
|
error: { type: string, example: "Missing required fields" }
|
||
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
||
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
||
|
|
|
||
|
|
/api/games/packs:
|
||
|
|
get:
|
||
|
|
operationId: listPacks
|
||
|
|
summary: List all packs
|
||
|
|
tags: [Games]
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Array of packs
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: array
|
||
|
|
items: { $ref: "#/components/schemas/Pack" }
|
||
|
|
|
||
|
|
/api/games/meta/packs:
|
||
|
|
get:
|
||
|
|
operationId: listPackMeta
|
||
|
|
summary: List pack metadata
|
||
|
|
tags: [Games]
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Array of pack metadata
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: array
|
||
|
|
items: { $ref: "#/components/schemas/PackMeta" }
|
||
|
|
|
||
|
|
/api/games/export/csv:
|
||
|
|
get:
|
||
|
|
operationId: exportGamesCsv
|
||
|
|
summary: Export games as CSV
|
||
|
|
tags: [Games]
|
||
|
|
security: [{ bearerAuth: [] }]
|
||
|
|
responses:
|
||
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
||
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
||
|
|
"200":
|
||
|
|
description: CSV file download
|
||
|
|
headers:
|
||
|
|
Content-Disposition:
|
||
|
|
schema:
|
||
|
|
type: string
|
||
|
|
example: 'attachment; filename="jackbox-games.csv"'
|
||
|
|
content:
|
||
|
|
text/csv:
|
||
|
|
schema:
|
||
|
|
type: string
|
||
|
|
description: CSV with columns Pack Name, Title, Min Players, Max Players, Length (minutes), Audience, Family Friendly, Game Type, Secondary Type
|
||
|
|
|
||
|
|
/api/games/import/csv:
|
||
|
|
post:
|
||
|
|
operationId: importGamesCsv
|
||
|
|
summary: Import games from CSV
|
||
|
|
tags: [Games]
|
||
|
|
security: [{ bearerAuth: [] }]
|
||
|
|
requestBody:
|
||
|
|
required: true
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [csvData]
|
||
|
|
properties:
|
||
|
|
csvData: { type: string }
|
||
|
|
mode: { type: string, enum: ["append", "replace"], description: Optional; defaults to append if absent }
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Import successful
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [message, count, mode]
|
||
|
|
properties:
|
||
|
|
message: { type: string }
|
||
|
|
count: { type: integer }
|
||
|
|
mode: { type: string }
|
||
|
|
"400":
|
||
|
|
description: CSV data required
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [error]
|
||
|
|
properties:
|
||
|
|
error: { type: string, example: "CSV data required" }
|
||
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
||
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
||
|
|
|
||
|
|
/api/games/packs/{name}/favor:
|
||
|
|
patch:
|
||
|
|
operationId: updatePackFavor
|
||
|
|
summary: Update pack favor bias
|
||
|
|
tags: [Games]
|
||
|
|
security: [{ bearerAuth: [] }]
|
||
|
|
parameters:
|
||
|
|
- name: name
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
schema: { type: string }
|
||
|
|
requestBody:
|
||
|
|
required: true
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [favor_bias]
|
||
|
|
properties:
|
||
|
|
favor_bias: { type: integer, enum: [-1, 0, 1] }
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Pack favor bias updated
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [message, favor_bias]
|
||
|
|
properties:
|
||
|
|
message: { type: string, example: "Pack favor bias updated successfully" }
|
||
|
|
favor_bias: { type: integer }
|
||
|
|
"400":
|
||
|
|
description: Invalid favor_bias
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [error]
|
||
|
|
properties:
|
||
|
|
error: { type: string, example: "favor_bias must be 1 (favor), -1 (disfavor), or 0 (neutral)" }
|
||
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
||
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
||
|
|
|
||
|
|
/api/games/packs/{name}/toggle:
|
||
|
|
patch:
|
||
|
|
operationId: togglePack
|
||
|
|
summary: Enable or disable a pack
|
||
|
|
tags: [Games]
|
||
|
|
security: [{ bearerAuth: [] }]
|
||
|
|
parameters:
|
||
|
|
- name: name
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
schema: { type: string }
|
||
|
|
requestBody:
|
||
|
|
required: true
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [enabled]
|
||
|
|
properties:
|
||
|
|
enabled: { type: boolean }
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Pack enabled/disabled
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [message, gamesAffected]
|
||
|
|
properties:
|
||
|
|
message: { type: string }
|
||
|
|
gamesAffected: { type: integer }
|
||
|
|
"400":
|
||
|
|
description: enabled status required
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [error]
|
||
|
|
properties:
|
||
|
|
error: { type: string, example: "enabled status required" }
|
||
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
||
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
||
|
|
|
||
|
|
/api/games/{id}:
|
||
|
|
get:
|
||
|
|
operationId: getGame
|
||
|
|
summary: Get a game by ID
|
||
|
|
tags: [Games]
|
||
|
|
parameters:
|
||
|
|
- name: id
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
schema: { type: integer }
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Game found
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Game" }
|
||
|
|
"404":
|
||
|
|
description: Game not found
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [error]
|
||
|
|
properties:
|
||
|
|
error: { type: string, example: "Game not found" }
|
||
|
|
put:
|
||
|
|
operationId: updateGame
|
||
|
|
summary: Update a game
|
||
|
|
tags: [Games]
|
||
|
|
security: [{ bearerAuth: [] }]
|
||
|
|
parameters:
|
||
|
|
- name: id
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
schema: { type: integer }
|
||
|
|
requestBody:
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
properties:
|
||
|
|
pack_name: { type: string }
|
||
|
|
title: { type: string }
|
||
|
|
min_players: { type: integer }
|
||
|
|
max_players: { type: integer }
|
||
|
|
length_minutes: { type: integer }
|
||
|
|
has_audience: { type: boolean }
|
||
|
|
family_friendly: { type: boolean }
|
||
|
|
game_type: { type: string }
|
||
|
|
secondary_type: { type: string }
|
||
|
|
enabled: { type: boolean }
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Game updated
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Game" }
|
||
|
|
"404":
|
||
|
|
description: Game not found
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Error" }
|
||
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
||
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
||
|
|
delete:
|
||
|
|
operationId: deleteGame
|
||
|
|
summary: Delete a game
|
||
|
|
tags: [Games]
|
||
|
|
security: [{ bearerAuth: [] }]
|
||
|
|
parameters:
|
||
|
|
- name: id
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
schema: { type: integer }
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Game deleted
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [message]
|
||
|
|
properties:
|
||
|
|
message: { type: string, example: "Game deleted successfully" }
|
||
|
|
"404":
|
||
|
|
description: Game not found
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Error" }
|
||
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
||
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
||
|
|
|
||
|
|
/api/games/{id}/toggle:
|
||
|
|
patch:
|
||
|
|
operationId: toggleGame
|
||
|
|
summary: Toggle game enabled status
|
||
|
|
tags: [Games]
|
||
|
|
security: [{ bearerAuth: [] }]
|
||
|
|
parameters:
|
||
|
|
- name: id
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
schema: { type: integer }
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Game updated
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Game" }
|
||
|
|
"404":
|
||
|
|
description: Game not found
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Error" }
|
||
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
||
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
||
|
|
|
||
|
|
/api/games/{id}/favor:
|
||
|
|
patch:
|
||
|
|
operationId: updateGameFavor
|
||
|
|
summary: Update game favor bias
|
||
|
|
tags: [Games]
|
||
|
|
security: [{ bearerAuth: [] }]
|
||
|
|
parameters:
|
||
|
|
- name: id
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
schema: { type: integer }
|
||
|
|
requestBody:
|
||
|
|
required: true
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [favor_bias]
|
||
|
|
properties:
|
||
|
|
favor_bias: { type: integer, enum: [-1, 0, 1] }
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Favor bias updated
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [message, favor_bias]
|
||
|
|
properties:
|
||
|
|
message: { type: string, example: "Favor bias updated successfully" }
|
||
|
|
favor_bias: { type: integer }
|
||
|
|
"400":
|
||
|
|
description: Invalid favor_bias
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Error" }
|
||
|
|
"404":
|
||
|
|
description: Game not found
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Error" }
|
||
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
||
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
||
|
|
|
||
|
|
/api/sessions:
|
||
|
|
get:
|
||
|
|
operationId: listSessions
|
||
|
|
summary: List all sessions
|
||
|
|
tags: [Sessions]
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Array of sessions with games_played
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: array
|
||
|
|
items: { $ref: "#/components/schemas/Session" }
|
||
|
|
post:
|
||
|
|
operationId: createSession
|
||
|
|
summary: Create a new session
|
||
|
|
tags: [Sessions]
|
||
|
|
security: [{ bearerAuth: [] }]
|
||
|
|
requestBody:
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
properties:
|
||
|
|
notes: { type: string }
|
||
|
|
responses:
|
||
|
|
"201":
|
||
|
|
description: Session created
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Session" }
|
||
|
|
"400":
|
||
|
|
description: Active session already exists
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [error, activeSessionId]
|
||
|
|
properties:
|
||
|
|
error: { type: string }
|
||
|
|
activeSessionId: { type: integer }
|
||
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
||
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
||
|
|
|
||
|
|
/api/sessions/active:
|
||
|
|
get:
|
||
|
|
operationId: getActiveSession
|
||
|
|
summary: Get the active session
|
||
|
|
tags: [Sessions]
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Active session (raw object) or null with message when no session
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
oneOf:
|
||
|
|
- $ref: "#/components/schemas/Session"
|
||
|
|
- type: object
|
||
|
|
required: [session, message]
|
||
|
|
properties:
|
||
|
|
session: { type: "null" }
|
||
|
|
message: { type: string, example: "No active session" }
|
||
|
|
|
||
|
|
/api/sessions/{id}:
|
||
|
|
get:
|
||
|
|
operationId: getSession
|
||
|
|
summary: Get a session by ID
|
||
|
|
tags: [Sessions]
|
||
|
|
parameters:
|
||
|
|
- name: id
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
schema: { type: integer }
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Session found
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Session" }
|
||
|
|
"404":
|
||
|
|
description: Session not found
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Error" }
|
||
|
|
delete:
|
||
|
|
operationId: deleteSession
|
||
|
|
summary: Delete a session
|
||
|
|
tags: [Sessions]
|
||
|
|
security: [{ bearerAuth: [] }]
|
||
|
|
parameters:
|
||
|
|
- name: id
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
schema: { type: integer }
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Session deleted
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [message, sessionId]
|
||
|
|
properties:
|
||
|
|
message: { type: string, example: "Session deleted successfully" }
|
||
|
|
sessionId: { type: integer }
|
||
|
|
"404":
|
||
|
|
description: Session not found
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Error" }
|
||
|
|
"400":
|
||
|
|
description: Cannot delete active session
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [error]
|
||
|
|
properties:
|
||
|
|
error: { type: string, example: "Cannot delete an active session. Please close it first." }
|
||
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
||
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
||
|
|
|
||
|
|
/api/sessions/{id}/close:
|
||
|
|
post:
|
||
|
|
operationId: closeSession
|
||
|
|
summary: Close a session
|
||
|
|
tags: [Sessions]
|
||
|
|
security: [{ bearerAuth: [] }]
|
||
|
|
parameters:
|
||
|
|
- name: id
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
schema: { type: integer }
|
||
|
|
requestBody:
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
properties:
|
||
|
|
notes: { type: string }
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Session closed with games_played
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Session" }
|
||
|
|
"404":
|
||
|
|
description: Session not found
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Error" }
|
||
|
|
"400":
|
||
|
|
description: Session already closed
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [error]
|
||
|
|
properties:
|
||
|
|
error: { type: string, example: "Session is already closed" }
|
||
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
||
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
||
|
|
|
||
|
|
/api/sessions/{id}/games:
|
||
|
|
get:
|
||
|
|
operationId: listSessionGames
|
||
|
|
summary: List games in a session
|
||
|
|
tags: [Sessions]
|
||
|
|
parameters:
|
||
|
|
- name: id
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
schema: { type: integer }
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Array of session games
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: array
|
||
|
|
items: { $ref: "#/components/schemas/SessionGame" }
|
||
|
|
"404":
|
||
|
|
description: Session not found
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Error" }
|
||
|
|
post:
|
||
|
|
operationId: addGameToSession
|
||
|
|
summary: Add a game to a session
|
||
|
|
tags: [Sessions]
|
||
|
|
security: [{ bearerAuth: [] }]
|
||
|
|
parameters:
|
||
|
|
- name: id
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
schema: { type: integer }
|
||
|
|
requestBody:
|
||
|
|
required: true
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [game_id]
|
||
|
|
properties:
|
||
|
|
game_id: { type: integer }
|
||
|
|
manually_added: { type: boolean }
|
||
|
|
room_code: { type: string }
|
||
|
|
responses:
|
||
|
|
"201":
|
||
|
|
description: Game added to session
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/SessionGame" }
|
||
|
|
"400":
|
||
|
|
description: Closed session or missing game_id
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Error" }
|
||
|
|
"404":
|
||
|
|
description: Session or game not found
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Error" }
|
||
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
||
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
||
|
|
|
||
|
|
/api/sessions/{id}/chat-import:
|
||
|
|
post:
|
||
|
|
operationId: importSessionChat
|
||
|
|
summary: Import chat log for vote processing
|
||
|
|
tags: [Sessions]
|
||
|
|
security: [{ bearerAuth: [] }]
|
||
|
|
parameters:
|
||
|
|
- name: id
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
schema: { type: integer }
|
||
|
|
requestBody:
|
||
|
|
required: true
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [chatData]
|
||
|
|
properties:
|
||
|
|
chatData:
|
||
|
|
type: array
|
||
|
|
items:
|
||
|
|
type: object
|
||
|
|
required: [username, message, timestamp]
|
||
|
|
properties:
|
||
|
|
username: { type: string }
|
||
|
|
message: { type: string }
|
||
|
|
timestamp: { type: string }
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Chat imported and processed
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [message, messagesImported, duplicatesSkipped, votesProcessed, votesByGame, debug]
|
||
|
|
properties:
|
||
|
|
message: { type: string }
|
||
|
|
messagesImported: { type: integer }
|
||
|
|
duplicatesSkipped: { type: integer }
|
||
|
|
votesProcessed: { type: integer }
|
||
|
|
votesByGame: { type: object }
|
||
|
|
debug: { type: object }
|
||
|
|
"400":
|
||
|
|
description: |
|
||
|
|
Two distinct cases:
|
||
|
|
- "chatData must be an array" — when chatData is missing or not an array
|
||
|
|
- "No games played in this session to match votes against" — when session has no games
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Error" }
|
||
|
|
"404":
|
||
|
|
description: Session not found
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Error" }
|
||
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
||
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
||
|
|
|
||
|
|
/api/sessions/{id}/export:
|
||
|
|
get:
|
||
|
|
operationId: exportSession
|
||
|
|
summary: Export session
|
||
|
|
tags: [Sessions]
|
||
|
|
security: [{ bearerAuth: [] }]
|
||
|
|
parameters:
|
||
|
|
- name: id
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
schema: { type: integer }
|
||
|
|
- name: format
|
||
|
|
in: query
|
||
|
|
schema: { type: string, enum: ["json", "txt"], default: "txt" }
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: File download
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { type: object }
|
||
|
|
text/plain:
|
||
|
|
schema: { type: string }
|
||
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
||
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
||
|
|
|
||
|
|
/api/sessions/{sessionId}/games/{sessionGameId}/status:
|
||
|
|
patch:
|
||
|
|
operationId: updateSessionGameStatus
|
||
|
|
summary: Update session game status
|
||
|
|
tags: [Sessions]
|
||
|
|
security: [{ bearerAuth: [] }]
|
||
|
|
parameters:
|
||
|
|
- name: sessionId
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
schema: { type: integer }
|
||
|
|
- name: sessionGameId
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
description: Session-game row ID (session_games.id), not games.id
|
||
|
|
schema: { type: integer }
|
||
|
|
requestBody:
|
||
|
|
required: true
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [status]
|
||
|
|
properties:
|
||
|
|
status: { type: string, enum: [playing, played, skipped] }
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Status updated
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [message, status]
|
||
|
|
properties:
|
||
|
|
message: { type: string, example: "Status updated successfully" }
|
||
|
|
status: { type: string }
|
||
|
|
"400":
|
||
|
|
description: Invalid status
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Error" }
|
||
|
|
"404":
|
||
|
|
description: Not found
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Error" }
|
||
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
||
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
||
|
|
|
||
|
|
/api/sessions/{sessionId}/games/{sessionGameId}:
|
||
|
|
delete:
|
||
|
|
operationId: removeGameFromSession
|
||
|
|
summary: Remove game from session
|
||
|
|
tags: [Sessions]
|
||
|
|
security: [{ bearerAuth: [] }]
|
||
|
|
parameters:
|
||
|
|
- name: sessionId
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
schema: { type: integer }
|
||
|
|
- name: sessionGameId
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
description: Session-game row ID (session_games.id), not games.id
|
||
|
|
schema: { type: integer }
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Game removed
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [message]
|
||
|
|
properties:
|
||
|
|
message: { type: string, example: "Game removed from session successfully" }
|
||
|
|
"404":
|
||
|
|
description: Not found
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Error" }
|
||
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
||
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
||
|
|
|
||
|
|
/api/sessions/{sessionId}/games/{sessionGameId}/room-code:
|
||
|
|
patch:
|
||
|
|
operationId: updateSessionGameRoomCode
|
||
|
|
summary: Update room code for session game
|
||
|
|
tags: [Sessions]
|
||
|
|
security: [{ bearerAuth: [] }]
|
||
|
|
parameters:
|
||
|
|
- name: sessionId
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
schema: { type: integer }
|
||
|
|
- name: sessionGameId
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
description: Session-game row ID (session_games.id), not games.id
|
||
|
|
schema: { type: integer }
|
||
|
|
requestBody:
|
||
|
|
required: true
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [room_code]
|
||
|
|
properties:
|
||
|
|
room_code: { type: string, pattern: "^[A-Z0-9]{4}$" }
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Room code updated
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/SessionGame" }
|
||
|
|
"400":
|
||
|
|
description: Missing or invalid room code format
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Error" }
|
||
|
|
"404":
|
||
|
|
description: Not found
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Error" }
|
||
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
||
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
||
|
|
|
||
|
|
/api/sessions/{sessionId}/games/{sessionGameId}/start-player-check:
|
||
|
|
post:
|
||
|
|
operationId: startPlayerCheck
|
||
|
|
summary: Start room monitor for player count
|
||
|
|
tags: [Sessions]
|
||
|
|
security: [{ bearerAuth: [] }]
|
||
|
|
parameters:
|
||
|
|
- name: sessionId
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
schema: { type: integer }
|
||
|
|
- name: sessionGameId
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
description: Session-game row ID (session_games.id), not games.id
|
||
|
|
schema: { type: integer }
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Room monitor started
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [message, status]
|
||
|
|
properties:
|
||
|
|
message: { type: string, example: "Room monitor started" }
|
||
|
|
status: { type: string, example: monitoring }
|
||
|
|
"400":
|
||
|
|
description: Game does not have a room code
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [error]
|
||
|
|
properties:
|
||
|
|
error: { type: string, example: "Game does not have a room code" }
|
||
|
|
"404":
|
||
|
|
description: Not found
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Error" }
|
||
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
||
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
||
|
|
|
||
|
|
/api/sessions/{sessionId}/games/{sessionGameId}/stop-player-check:
|
||
|
|
post:
|
||
|
|
operationId: stopPlayerCheck
|
||
|
|
summary: Stop room monitor
|
||
|
|
tags: [Sessions]
|
||
|
|
security: [{ bearerAuth: [] }]
|
||
|
|
parameters:
|
||
|
|
- name: sessionId
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
schema: { type: integer }
|
||
|
|
- name: sessionGameId
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
description: Session-game row ID (session_games.id), not games.id
|
||
|
|
schema: { type: integer }
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Monitor stopped
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [message, status]
|
||
|
|
properties:
|
||
|
|
message: { type: string, example: "Room monitor and player count check stopped" }
|
||
|
|
status: { type: string, example: stopped }
|
||
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
||
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
||
|
|
|
||
|
|
/api/sessions/{sessionId}/games/{sessionGameId}/player-count:
|
||
|
|
patch:
|
||
|
|
operationId: updateSessionGamePlayerCount
|
||
|
|
summary: Update player count for session game
|
||
|
|
tags: [Sessions]
|
||
|
|
security: [{ bearerAuth: [] }]
|
||
|
|
parameters:
|
||
|
|
- name: sessionId
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
schema: { type: integer }
|
||
|
|
- name: sessionGameId
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
description: Session-game row ID (session_games.id), not games.id
|
||
|
|
schema: { type: integer }
|
||
|
|
requestBody:
|
||
|
|
required: true
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [player_count]
|
||
|
|
properties:
|
||
|
|
player_count: { type: integer, minimum: 0 }
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Player count updated
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [message, player_count]
|
||
|
|
properties:
|
||
|
|
message: { type: string, example: "Player count updated successfully" }
|
||
|
|
player_count: { type: integer }
|
||
|
|
"400":
|
||
|
|
description: Missing or invalid player count
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Error" }
|
||
|
|
"404":
|
||
|
|
description: Not found
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Error" }
|
||
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
||
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
||
|
|
|
||
|
|
/api/pick:
|
||
|
|
post:
|
||
|
|
operationId: pickRandomGame
|
||
|
|
summary: Pick a random game with optional filters
|
||
|
|
tags: [Picker]
|
||
|
|
requestBody:
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
properties:
|
||
|
|
playerCount: { type: integer }
|
||
|
|
drawing: { type: string }
|
||
|
|
length: { type: string }
|
||
|
|
familyFriendly: { type: boolean }
|
||
|
|
sessionId: { type: integer }
|
||
|
|
excludePlayed: { type: boolean }
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Random game picked
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [game, poolSize, totalEnabled]
|
||
|
|
properties:
|
||
|
|
game: { $ref: "#/components/schemas/Game" }
|
||
|
|
poolSize: { type: integer }
|
||
|
|
totalEnabled: { type: integer }
|
||
|
|
"404":
|
||
|
|
description: No matching games
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [error]
|
||
|
|
properties:
|
||
|
|
error: { type: string }
|
||
|
|
suggestion: { type: string }
|
||
|
|
recentlyPlayed: { type: array, items: { type: integer } }
|
||
|
|
|
||
|
|
/api/stats:
|
||
|
|
get:
|
||
|
|
operationId: getStats
|
||
|
|
summary: Get aggregate statistics
|
||
|
|
tags: [Stats]
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Stats object
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [games, gamesEnabled, packs, sessions, activeSessions, totalGamesPlayed, mostPlayedGames, topRatedGames]
|
||
|
|
properties:
|
||
|
|
games: { type: object, properties: { count: { type: integer } } }
|
||
|
|
gamesEnabled: { type: object, properties: { count: { type: integer } } }
|
||
|
|
packs: { type: object, properties: { count: { type: integer } } }
|
||
|
|
sessions: { type: object, properties: { count: { type: integer } } }
|
||
|
|
activeSessions: { type: object, properties: { count: { type: integer } } }
|
||
|
|
totalGamesPlayed: { type: object, properties: { count: { type: integer } } }
|
||
|
|
mostPlayedGames:
|
||
|
|
type: array
|
||
|
|
items:
|
||
|
|
type: object
|
||
|
|
properties:
|
||
|
|
id: { type: integer }
|
||
|
|
title: { type: string }
|
||
|
|
pack_name: { type: string }
|
||
|
|
play_count: { type: integer }
|
||
|
|
popularity_score: { type: integer }
|
||
|
|
upvotes: { type: integer }
|
||
|
|
downvotes: { type: integer }
|
||
|
|
topRatedGames:
|
||
|
|
type: array
|
||
|
|
items:
|
||
|
|
type: object
|
||
|
|
properties:
|
||
|
|
id: { type: integer }
|
||
|
|
title: { type: string }
|
||
|
|
pack_name: { type: string }
|
||
|
|
play_count: { type: integer }
|
||
|
|
popularity_score: { type: integer }
|
||
|
|
upvotes: { type: integer }
|
||
|
|
downvotes: { type: integer }
|
||
|
|
|
||
|
|
/api/votes/live:
|
||
|
|
post:
|
||
|
|
operationId: recordLiveVote
|
||
|
|
summary: Record a live vote (up/down)
|
||
|
|
tags: [Votes]
|
||
|
|
security: [{ bearerAuth: [] }]
|
||
|
|
requestBody:
|
||
|
|
required: true
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [username, vote, timestamp]
|
||
|
|
properties:
|
||
|
|
username: { type: string }
|
||
|
|
vote: { type: string, enum: [up, down] }
|
||
|
|
timestamp: { type: string, format: date-time, description: ISO 8601 }
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Vote recorded
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [success, message, session, game, vote]
|
||
|
|
properties:
|
||
|
|
success: { type: boolean, example: true }
|
||
|
|
message: { type: string, example: "Vote recorded successfully" }
|
||
|
|
session:
|
||
|
|
type: object
|
||
|
|
properties:
|
||
|
|
id: { type: integer }
|
||
|
|
games_played: { type: integer }
|
||
|
|
game:
|
||
|
|
type: object
|
||
|
|
properties:
|
||
|
|
id: { type: integer }
|
||
|
|
title: { type: string }
|
||
|
|
upvotes: { type: integer }
|
||
|
|
downvotes: { type: integer }
|
||
|
|
popularity_score: { type: integer }
|
||
|
|
vote:
|
||
|
|
type: object
|
||
|
|
properties:
|
||
|
|
username: { type: string }
|
||
|
|
type: { type: string }
|
||
|
|
timestamp: { type: string }
|
||
|
|
"400":
|
||
|
|
description: Missing fields, invalid vote, or invalid timestamp
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Error" }
|
||
|
|
"404":
|
||
|
|
description: No active session, no games, or no matching game (vote timestamp doesn't match)
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [error]
|
||
|
|
properties:
|
||
|
|
error: { type: string }
|
||
|
|
debug:
|
||
|
|
type: object
|
||
|
|
description: Present when vote timestamp doesn't match any game
|
||
|
|
properties:
|
||
|
|
voteTimestamp: { type: string }
|
||
|
|
sessionGames:
|
||
|
|
type: array
|
||
|
|
items:
|
||
|
|
type: object
|
||
|
|
properties:
|
||
|
|
title: { type: string }
|
||
|
|
played_at: { type: string }
|
||
|
|
"409":
|
||
|
|
description: Duplicate vote within 1 second
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [error]
|
||
|
|
properties:
|
||
|
|
error: { type: string }
|
||
|
|
message: { type: string }
|
||
|
|
timeSinceLastVote: { type: number }
|
||
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
||
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
||
|
|
|
||
|
|
/api/webhooks:
|
||
|
|
get:
|
||
|
|
operationId: listWebhooks
|
||
|
|
summary: List all webhooks
|
||
|
|
tags: [Webhooks]
|
||
|
|
security: [{ bearerAuth: [] }]
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Array of webhooks
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: array
|
||
|
|
items: { $ref: "#/components/schemas/Webhook" }
|
||
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
||
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
||
|
|
post:
|
||
|
|
operationId: createWebhook
|
||
|
|
summary: Create a webhook
|
||
|
|
tags: [Webhooks]
|
||
|
|
security: [{ bearerAuth: [] }]
|
||
|
|
requestBody:
|
||
|
|
required: true
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [name, url, secret, events]
|
||
|
|
properties:
|
||
|
|
name: { type: string }
|
||
|
|
url: { type: string, format: uri }
|
||
|
|
secret: { type: string }
|
||
|
|
events:
|
||
|
|
type: array
|
||
|
|
items: { type: string }
|
||
|
|
responses:
|
||
|
|
"201":
|
||
|
|
description: Webhook created
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
allOf:
|
||
|
|
- { $ref: "#/components/schemas/Webhook" }
|
||
|
|
- type: object
|
||
|
|
properties:
|
||
|
|
message: { type: string, example: "Webhook created successfully" }
|
||
|
|
"400":
|
||
|
|
description: Invalid input
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Error" }
|
||
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
||
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
||
|
|
|
||
|
|
/api/webhooks/{id}:
|
||
|
|
get:
|
||
|
|
operationId: getWebhook
|
||
|
|
summary: Get a webhook by ID
|
||
|
|
tags: [Webhooks]
|
||
|
|
security: [{ bearerAuth: [] }]
|
||
|
|
parameters:
|
||
|
|
- name: id
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
schema: { type: integer }
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Webhook found
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Webhook" }
|
||
|
|
"404":
|
||
|
|
description: Webhook not found
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Error" }
|
||
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
||
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
||
|
|
patch:
|
||
|
|
operationId: updateWebhook
|
||
|
|
summary: Update a webhook
|
||
|
|
tags: [Webhooks]
|
||
|
|
security: [{ bearerAuth: [] }]
|
||
|
|
parameters:
|
||
|
|
- name: id
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
schema: { type: integer }
|
||
|
|
requestBody:
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
properties:
|
||
|
|
name: { type: string }
|
||
|
|
url: { type: string, format: uri }
|
||
|
|
secret: { type: string }
|
||
|
|
events:
|
||
|
|
type: array
|
||
|
|
items: { type: string }
|
||
|
|
enabled: { type: boolean }
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Webhook updated
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
allOf:
|
||
|
|
- { $ref: "#/components/schemas/Webhook" }
|
||
|
|
- type: object
|
||
|
|
properties:
|
||
|
|
message: { type: string, example: "Webhook updated successfully" }
|
||
|
|
"400":
|
||
|
|
description: No fields, invalid URL, or events not array
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Error" }
|
||
|
|
"404":
|
||
|
|
description: Webhook not found
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Error" }
|
||
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
||
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
||
|
|
delete:
|
||
|
|
operationId: deleteWebhook
|
||
|
|
summary: Delete a webhook
|
||
|
|
tags: [Webhooks]
|
||
|
|
security: [{ bearerAuth: [] }]
|
||
|
|
parameters:
|
||
|
|
- name: id
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
schema: { type: integer }
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Webhook deleted
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [message, webhookId]
|
||
|
|
properties:
|
||
|
|
message: { type: string, example: "Webhook deleted successfully" }
|
||
|
|
webhookId: { type: integer }
|
||
|
|
"404":
|
||
|
|
description: Webhook not found
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Error" }
|
||
|
|
|
||
|
|
/api/webhooks/test/{id}:
|
||
|
|
post:
|
||
|
|
operationId: testWebhook
|
||
|
|
summary: Send test webhook
|
||
|
|
tags: [Webhooks]
|
||
|
|
security: [{ bearerAuth: [] }]
|
||
|
|
parameters:
|
||
|
|
- name: id
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
schema: { type: integer }
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Test webhook sent
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: object
|
||
|
|
required: [message, note]
|
||
|
|
properties:
|
||
|
|
message: { type: string, example: "Test webhook sent" }
|
||
|
|
note: { type: string, example: "Check webhook_logs table for delivery status" }
|
||
|
|
"404":
|
||
|
|
description: Webhook not found
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema: { $ref: "#/components/schemas/Error" }
|
||
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
||
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|
||
|
|
|
||
|
|
/api/webhooks/{id}/logs:
|
||
|
|
get:
|
||
|
|
operationId: listWebhookLogs
|
||
|
|
summary: List webhook delivery logs
|
||
|
|
tags: [Webhooks]
|
||
|
|
security: [{ bearerAuth: [] }]
|
||
|
|
parameters:
|
||
|
|
- name: id
|
||
|
|
in: path
|
||
|
|
required: true
|
||
|
|
schema: { type: integer }
|
||
|
|
- name: limit
|
||
|
|
in: query
|
||
|
|
schema: { type: integer, default: 50 }
|
||
|
|
responses:
|
||
|
|
"200":
|
||
|
|
description: Array of webhook logs
|
||
|
|
content:
|
||
|
|
application/json:
|
||
|
|
schema:
|
||
|
|
type: array
|
||
|
|
items: { $ref: "#/components/schemas/WebhookLog" }
|
||
|
|
"401": { $ref: "#/components/responses/Unauthorized" }
|
||
|
|
"403": { $ref: "#/components/responses/Forbidden" }
|