# Sessions Endpoints

Sessions represent a gaming night. Only one session can be active at a time. Games are added to the active session as they're played. Sessions track game status, room codes, player counts, and chat logs for voting.

**IMPORTANT:** In session game sub-routes like `/api/sessions/{sessionId}/games/{sessionGameId}/status`, the `sessionGameId` parameter refers to the `session_games.id` row ID, NOT `games.id`.

## Endpoint Summary

| Method | Path | Auth | Description |
|--------|------|------|-------------|
| GET | `/api/sessions` | No | List all sessions with games_played count |
| GET | `/api/sessions/active` | No | Get the active session (or null) |
| GET | `/api/sessions/{id}` | No | Get a session by ID |
| POST | `/api/sessions` | Bearer | Create a new session |
| POST | `/api/sessions/{id}/close` | Bearer | Close a session |
| DELETE | `/api/sessions/{id}` | Bearer | Delete a closed session |
| GET | `/api/sessions/{id}/games` | No | List games in a session |
| GET | `/api/sessions/{id}/votes` | No | Get per-game vote breakdown for a session |
| POST | `/api/sessions/{id}/games` | Bearer | Add a game to a session |
| POST | `/api/sessions/{id}/chat-import` | Bearer | Import chat log for vote processing |
| GET | `/api/sessions/{id}/export` | Bearer | Export session (JSON or TXT) |
| PATCH | `/api/sessions/{sessionId}/games/{sessionGameId}/status` | Bearer | Update session game status |
| DELETE | `/api/sessions/{sessionId}/games/{sessionGameId}` | Bearer | Remove game from session |
| PATCH | `/api/sessions/{sessionId}/games/{sessionGameId}/room-code` | Bearer | Update room code for session game |
| GET | `/api/sessions/{sessionId}/games/{sessionGameId}/status-live` | No | Get live game status from shard monitor |
| POST | `/api/sessions/{sessionId}/games/{sessionGameId}/start-player-check` | Bearer | Start room monitor |
| POST | `/api/sessions/{sessionId}/games/{sessionGameId}/stop-player-check` | Bearer | Stop room monitor |
| PATCH | `/api/sessions/{sessionId}/games/{sessionGameId}/player-count` | Bearer | Update player count for session game |

---

## GET /api/sessions

List all sessions with a `games_played` count. Ordered by `created_at` DESC.

### Authentication

None.

### Response

**200 OK**

```json
[
  {
    "id": 5,
    "notes": "Friday game night",
    "is_active": 1,
    "created_at": "2026-03-15T19:00:00.000Z",
    "closed_at": null,
    "games_played": 3
  },
  {
    "id": 4,
    "notes": "Last week's session",
    "is_active": 0,
    "created_at": "2026-03-08T18:30:00.000Z",
    "closed_at": "2026-03-08T23:15:00.000Z",
    "games_played": 5
  }
]
```

### Example

```bash
curl "http://localhost:5000/api/sessions"
```

---

## GET /api/sessions/active

Get the active session. Returns the session object directly if one is active, or a wrapper with `session: null` if none.

### Authentication

None.

### Response

**200 OK** (active session exists)

```json
{
  "id": 5,
  "notes": "Friday game night",
  "is_active": 1,
  "created_at": "2026-03-15T19:00:00.000Z",
  "closed_at": null,
  "games_played": 3
}
```

**200 OK** (no active session)

```json
{
  "session": null,
  "message": "No active session"
}
```

### Example

```bash
curl "http://localhost:5000/api/sessions/active"
```

---

## GET /api/sessions/{id}

Get a single session by ID.

### Authentication

None.

### Path Parameters

| Name | Type | Description |
|------|------|-------------|
| id | integer | Session ID |

### Response

**200 OK**

```json
{
  "id": 5,
  "notes": "Friday game night",
  "is_active": 1,
  "created_at": "2026-03-15T19:00:00.000Z",
  "closed_at": null,
  "games_played": 3
}
```

### Error Responses

| Status | Body | When |
|--------|------|------|
| 404 | `{ "error": "Session not found" }` | Invalid session ID |

### Example

```bash
curl "http://localhost:5000/api/sessions/5"
```

---

## POST /api/sessions

Create a new session. Only one active session is allowed at a time. Triggers WebSocket `session.started` broadcast to all authenticated clients.

### Authentication

Bearer token required.

### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| notes | string | No | Optional notes (e.g., "Friday game night") |

```json
{
  "notes": "Friday game night"
}
```

### Response

**201 Created**

```json
{
  "id": 5,
  "notes": "Friday game night",
  "is_active": 1,
  "created_at": "2026-03-15T19:00:00.000Z",
  "closed_at": null
}
```

### Error Responses

| Status | Body | When |
|--------|------|------|
| 400 | `{ "error": "An active session already exists. Please close it before creating a new one.", "activeSessionId": 5 }` | An active session already exists |

### Example

```bash
curl -X POST "http://localhost:5000/api/sessions" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"notes": "Friday game night"}'
```

---

## POST /api/sessions/{id}/close

Close a session. Auto-sets all games with status `playing` to `played`. Optional body updates session notes. Triggers WebSocket `session.ended` broadcast to session subscribers.

### Authentication

Bearer token required.

### Path Parameters

| Name | Type | Description |
|------|------|-------------|
| id | integer | Session ID |

### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| notes | string | No | Optional notes (updates session notes) |

```json
{
  "notes": "Great session!"
}
```

### Response

**200 OK**

```json
{
  "id": 5,
  "notes": "Great session!",
  "is_active": 0,
  "created_at": "2026-03-15T19:00:00.000Z",
  "closed_at": "2026-03-15T23:30:00.000Z",
  "games_played": 4
}
```

### Error Responses

| Status | Body | When |
|--------|------|------|
| 400 | `{ "error": "Session is already closed" }` | Session was already closed |
| 404 | `{ "error": "Session not found" }` | Invalid session ID |

### Example

```bash
curl -X POST "http://localhost:5000/api/sessions/5/close" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"notes": "Great session!"}'
```

---

## DELETE /api/sessions/{id}

Delete a session. Cannot delete active sessions — close first. Cascades: deletes `chat_logs` and `session_games`.

### Authentication

Bearer token required.

### Path Parameters

| Name | Type | Description |
|------|------|-------------|
| id | integer | Session ID |

### Response

**200 OK**

```json
{
  "message": "Session deleted successfully",
  "sessionId": 5
}
```

### Error Responses

| Status | Body | When |
|--------|------|------|
| 400 | `{ "error": "Cannot delete an active session. Please close it first." }` | Session is active |
| 404 | `{ "error": "Session not found" }` | Invalid session ID |

### Example

```bash
curl -X DELETE "http://localhost:5000/api/sessions/5" \
  -H "Authorization: Bearer $TOKEN"
```

---

## GET /api/sessions/{id}/games

List all games in a session. Returns SessionGame objects joined with game data. Ordered by `played_at` ASC.

### Authentication

None.

### Path Parameters

| Name | Type | Description |
|------|------|-------------|
| id | integer | Session ID |

### Response

**200 OK**

```json
[
  {
    "id": 12,
    "session_id": 5,
    "game_id": 42,
    "manually_added": 1,
    "status": "played",
    "room_code": "ABCD",
    "played_at": "2026-03-15T19:15:00.000Z",
    "player_count": 6,
    "pack_name": "Jackbox Party Pack 7",
    "title": "Quiplash 3",
    "game_type": "Writing",
    "min_players": 3,
    "max_players": 8,
    "popularity_score": 12,
    "upvotes": 15,
    "downvotes": 3
  },
  {
    "id": 13,
    "session_id": 5,
    "game_id": 38,
    "manually_added": 0,
    "status": "playing",
    "room_code": "XY9Z",
    "played_at": "2026-03-15T20:00:00.000Z",
    "player_count": null,
    "pack_name": "Jackbox Party Pack 6",
    "title": "Trivia Murder Party 2",
    "game_type": "Trivia",
    "min_players": 1,
    "max_players": 8,
    "popularity_score": 8,
    "upvotes": 10,
    "downvotes": 2
  }
]
```

### Example

```bash
curl "http://localhost:5000/api/sessions/5/games"
```

---

## GET /api/sessions/{id}/votes

Get per-game vote breakdown for a session. Aggregates votes from the `live_votes` table by game. Results ordered by `net_score` DESC.

### Authentication

None.

### Path Parameters

| Name | Type | Description |
|------|------|-------------|
| id | integer | Session ID |

### Response

**200 OK**

```json
{
  "session_id": 5,
  "votes": [
    {
      "game_id": 42,
      "title": "Quiplash 3",
      "pack_name": "Party Pack 7",
      "upvotes": 14,
      "downvotes": 3,
      "net_score": 11,
      "total_votes": 17
    }
  ]
}
```

Returns 200 with an empty `votes` array when the session has no votes.

### Error Responses

| Status | Body | When |
|--------|------|------|
| 404 | `{ "error": "Session not found" }` | Invalid session ID |

### Example

```bash
curl "http://localhost:5000/api/sessions/5/votes"
```

---

## POST /api/sessions/{id}/games

Add a game to a session. Side effects: increments game `play_count`, sets previous `playing` games to `played` (skipped games stay skipped), triggers `game.added` webhook and WebSocket event, and auto-starts room monitor if `room_code` is provided.

### Authentication

Bearer token required.

### Path Parameters

| Name | Type | Description |
|------|------|-------------|
| id | integer | Session ID |

### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| game_id | integer | Yes | Game ID (from games table) |
| manually_added | boolean | No | Whether the game was manually added (default: false) |
| room_code | string | No | 4-character room code; if provided, auto-starts room monitor |

```json
{
  "game_id": 42,
  "manually_added": true,
  "room_code": "ABCD"
}
```

### Response

**201 Created**

```json
{
  "id": 14,
  "session_id": 5,
  "game_id": 42,
  "manually_added": 1,
  "status": "playing",
  "room_code": "ABCD",
  "played_at": "2026-03-15T20:30:00.000Z",
  "pack_name": "Jackbox Party Pack 7",
  "title": "Quiplash 3",
  "game_type": "Writing",
  "min_players": 3,
  "max_players": 8
}
```

### Error Responses

| Status | Body | When |
|--------|------|------|
| 400 | `{ "error": "game_id is required" }` | Missing game_id |
| 400 | `{ "error": "Cannot add games to a closed session" }` | Session is closed |
| 404 | `{ "error": "Session not found" }` | Invalid session ID |
| 404 | `{ "error": "Game not found" }` | Invalid game_id |

### Example

```bash
curl -X POST "http://localhost:5000/api/sessions/5/games" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"game_id": 42, "manually_added": true, "room_code": "ABCD"}'
```

---

## POST /api/sessions/{id}/chat-import

Import chat log and process votes. Matches votes to games by timestamp intervals. `"thisgame++"` = upvote, `"thisgame--"` = downvote. Deduplicates by SHA-256 hash of `username:message:timestamp`.

### Authentication

Bearer token required.

### Path Parameters

| Name | Type | Description |
|------|------|-------------|
| id | integer | Session ID |

### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| chatData | array | Yes | Array of `{ username, message, timestamp }` objects |

```json
{
  "chatData": [
    {
      "username": "viewer1",
      "message": "thisgame++",
      "timestamp": "2026-03-15T20:30:00Z"
    },
    {
      "username": "viewer2",
      "message": "thisgame--",
      "timestamp": "2026-03-15T20:31:00Z"
    }
  ]
}
```

### Response

**200 OK**

```json
{
  "message": "Chat log imported and processed successfully",
  "messagesImported": 150,
  "duplicatesSkipped": 3,
  "votesProcessed": 25,
  "votesByGame": {
    "42": {
      "title": "Quiplash 3",
      "upvotes": 15,
      "downvotes": 2
    }
  },
  "debug": {
    "sessionGamesTimeline": [
      {
        "title": "Quiplash 3",
        "played_at": "2026-03-15T20:00:00.000Z",
        "played_at_ms": 1742068800000
      }
    ],
    "voteMatches": []
  }
}
```

### Error Responses

| Status | Body | When |
|--------|------|------|
| 400 | `{ "error": "chatData must be an array" }` | chatData missing or not an array |
| 400 | `{ "error": "No games played in this session to match votes against" }` | Session has no games |
| 404 | `{ "error": "Session not found" }` | Invalid session ID |

### Example

```bash
curl -X POST "http://localhost:5000/api/sessions/5/chat-import" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"chatData":[{"username":"viewer1","message":"thisgame++","timestamp":"2026-03-15T20:30:00Z"}]}'
```

---

## PATCH /api/sessions/{sessionId}/games/{sessionGameId}/status

Update the status of a session game. Valid values: `playing`, `played`, `skipped`. If setting to `playing`, auto-sets other `playing` games to `played`.

**Note:** `sessionGameId` is the `session_games.id` row ID, NOT `games.id`.

### Authentication

Bearer token required.

### Path Parameters

| Name | Type | Description |
|------|------|-------------|
| sessionId | integer | Session ID |
| sessionGameId | integer | Session game ID (`session_games.id`) |

### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| status | string | Yes | `"playing"`, `"played"`, or `"skipped"` |

```json
{
  "status": "played"
}
```

### Response

**200 OK**

```json
{
  "message": "Status updated successfully",
  "status": "played"
}
```

### Error Responses

| Status | Body | When |
|--------|------|------|
| 400 | `{ "error": "Invalid status. Must be playing, played, or skipped" }` | Invalid status value |
| 404 | `{ "error": "Session game not found" }` | Invalid sessionId or sessionGameId |

### Example

```bash
curl -X PATCH "http://localhost:5000/api/sessions/5/games/14/status" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"status": "played"}'
```

---

## DELETE /api/sessions/{sessionId}/games/{sessionGameId}

Remove a game from a session. Stops room monitor and player count check.

**Note:** `sessionGameId` is the `session_games.id` row ID, NOT `games.id`.

### Authentication

Bearer token required.

### Path Parameters

| Name | Type | Description |
|------|------|-------------|
| sessionId | integer | Session ID |
| sessionGameId | integer | Session game ID (`session_games.id`) |

### Response

**200 OK**

```json
{
  "message": "Game removed from session successfully"
}
```

### Error Responses

| Status | Body | When |
|--------|------|------|
| 404 | `{ "error": "Session game not found" }` | Invalid sessionId or sessionGameId |

### Example

```bash
curl -X DELETE "http://localhost:5000/api/sessions/5/games/14" \
  -H "Authorization: Bearer $TOKEN"
```

---

## PATCH /api/sessions/{sessionId}/games/{sessionGameId}/room-code

Update the room code for a session game. Room code must be exactly 4 characters, uppercase A–Z and 0–9 only (regex: `^[A-Z0-9]{4}$`).

**Note:** `sessionGameId` is the `session_games.id` row ID, NOT `games.id`.

### Authentication

Bearer token required.

### Path Parameters

| Name | Type | Description |
|------|------|-------------|
| sessionId | integer | Session ID |
| sessionGameId | integer | Session game ID (`session_games.id`) |

### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| room_code | string | Yes | 4 uppercase alphanumeric chars (A-Z, 0-9) |

```json
{
  "room_code": "XY9Z"
}
```

### Response

**200 OK**

Returns the updated SessionGame object with joined game data.

```json
{
  "id": 14,
  "session_id": 5,
  "game_id": 42,
  "manually_added": 1,
  "status": "playing",
  "room_code": "XY9Z",
  "played_at": "2026-03-15T20:30:00.000Z",
  "pack_name": "Jackbox Party Pack 7",
  "title": "Quiplash 3",
  "game_type": "Writing",
  "min_players": 3,
  "max_players": 8,
  "popularity_score": 12,
  "upvotes": 15,
  "downvotes": 3
}
```

### Error Responses

| Status | Body | When |
|--------|------|------|
| 400 | `{ "error": "room_code is required" }` | Missing room_code |
| 400 | `{ "error": "room_code must be exactly 4 alphanumeric characters (A-Z, 0-9)" }` | Invalid format |
| 404 | `{ "error": "Session game not found" }` | Invalid sessionId or sessionGameId |

### Example

```bash
curl -X PATCH "http://localhost:5000/api/sessions/5/games/14/room-code" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"room_code": "XY9Z"}'
```

---

## GET /api/sessions/{id}/export

Export session data as a file download. JSON format includes structured session, games, and chat_logs. TXT format is human-readable plaintext.

### Authentication

Bearer token required.

### Path Parameters

| Name | Type | Description |
|------|------|-------------|
| id | integer | Session ID |

### Query Parameters

| Name | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| format | string | No | `txt` | `"json"` or `"txt"` |

### Response

**200 OK**

- **JSON format**: Content-Type `application/json`, filename `session-{id}.json`

```json
{
  "session": {
    "id": 5,
    "created_at": "2026-03-15T19:00:00.000Z",
    "closed_at": "2026-03-15T23:30:00.000Z",
    "is_active": false,
    "notes": "Friday game night",
    "games_played": 4
  },
  "games": [
    {
      "title": "Quiplash 3",
      "pack": "Jackbox Party Pack 7",
      "players": "3-8",
      "type": "Writing",
      "played_at": "2026-03-15T19:15:00.000Z",
      "manually_added": true,
      "status": "played"
    }
  ],
  "chat_logs": [
    {
      "username": "viewer1",
      "message": "thisgame++",
      "timestamp": "2026-03-15T19:20:00.000Z",
      "vote": "thisgame++"
    }
  ]
}
```

- **TXT format**: Content-Type `text/plain`, filename `session-{id}.txt` — human-readable sections with headers.

### Error Responses

| Status | Body | When |
|--------|------|------|
| 404 | `{ "error": "Session not found" }` | Invalid session ID |

### Example

```bash
# JSON export
curl -o session-5.json "http://localhost:5000/api/sessions/5/export?format=json" \
  -H "Authorization: Bearer $TOKEN"

# TXT export (default)
curl -o session-5.txt "http://localhost:5000/api/sessions/5/export" \
  -H "Authorization: Bearer $TOKEN"
```

---

## GET /api/sessions/{sessionId}/games/{sessionGameId}/status-live

Get the live game status from an active shard monitor. If no monitor is running, falls back to data from the database. No authentication required.

The same data is broadcast every 20 seconds via the `game.status` WebSocket event to subscribed clients.

**Note:** `sessionGameId` is the `session_games.id` row ID, NOT `games.id`.

### Authentication

None required.

### Path Parameters

| Name | Type | Description |
|------|------|-------------|
| sessionId | integer | Session ID |
| sessionGameId | integer | Session game ID (`session_games.id`) |

### Response

**200 OK** — Live shard data (when monitor is active):

```json
{
  "sessionId": 1,
  "gameId": 5,
  "roomCode": "LSBN",
  "appTag": "drawful2international",
  "maxPlayers": 8,
  "playerCount": 4,
  "players": ["Alice", "Bob", "Charlie", "Diana"],
  "lobbyState": "CanStart",
  "gameState": "Lobby",
  "gameStarted": false,
  "gameFinished": false,
  "monitoring": true
}
```

**200 OK** — DB fallback (when no monitor is active):

```json
{
  "sessionId": 1,
  "gameId": 5,
  "roomCode": "LSBN",
  "appTag": null,
  "maxPlayers": 8,
  "playerCount": 4,
  "players": [],
  "lobbyState": null,
  "gameState": null,
  "gameStarted": false,
  "gameFinished": true,
  "monitoring": false,
  "title": "Drawful 2",
  "packName": "Jackbox Party Pack 8",
  "status": "completed"
}
```

### Error Responses

| Status | Body | When |
|--------|------|------|
| 404 | `{ "error": "Session game not found" }` | Invalid sessionId or sessionGameId |

### Example

```bash
curl "http://localhost:5000/api/sessions/5/games/14/status-live"
```

---

## POST /api/sessions/{sessionId}/games/{sessionGameId}/start-player-check

Start the room monitor for a session game. The game must have a room code.

**Note:** `sessionGameId` is the `session_games.id` row ID, NOT `games.id`.

### Authentication

Bearer token required.

### Path Parameters

| Name | Type | Description |
|------|------|-------------|
| sessionId | integer | Session ID |
| sessionGameId | integer | Session game ID (`session_games.id`) |

### Response

**200 OK**

```json
{
  "message": "Room monitor started",
  "status": "monitoring"
}
```

### Error Responses

| Status | Body | When |
|--------|------|------|
| 400 | `{ "error": "Game does not have a room code" }` | Session game has no room_code |
| 404 | `{ "error": "Session game not found" }` | Invalid sessionId or sessionGameId |

### Example

```bash
curl -X POST "http://localhost:5000/api/sessions/5/games/14/start-player-check" \
  -H "Authorization: Bearer $TOKEN"
```

---

## POST /api/sessions/{sessionId}/games/{sessionGameId}/stop-player-check

Stop the room monitor and player count check for a session game.

**Note:** `sessionGameId` is the `session_games.id` row ID, NOT `games.id`.

### Authentication

Bearer token required.

### Path Parameters

| Name | Type | Description |
|------|------|-------------|
| sessionId | integer | Session ID |
| sessionGameId | integer | Session game ID (`session_games.id`) |

### Response

**200 OK**

```json
{
  "message": "Room monitor and player count check stopped",
  "status": "stopped"
}
```

### Example

```bash
curl -X POST "http://localhost:5000/api/sessions/5/games/14/stop-player-check" \
  -H "Authorization: Bearer $TOKEN"
```

---

## PATCH /api/sessions/{sessionId}/games/{sessionGameId}/player-count

Manually update the player count for a session game. Sets `player_count_check_status` to `completed`. Broadcasts WebSocket `player-count.updated`.

**Note:** `sessionGameId` is the `session_games.id` row ID, NOT `games.id`.

### Authentication

Bearer token required.

### Path Parameters

| Name | Type | Description |
|------|------|-------------|
| sessionId | integer | Session ID |
| sessionGameId | integer | Session game ID (`session_games.id`) |

### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| player_count | integer | Yes | Non-negative player count |

```json
{
  "player_count": 6
}
```

### Response

**200 OK**

```json
{
  "message": "Player count updated successfully",
  "player_count": 6
}
```

### Error Responses

| Status | Body | When |
|--------|------|------|
| 400 | `{ "error": "player_count is required" }` | Missing player_count |
| 400 | `{ "error": "player_count must be a positive number" }` | Invalid (NaN or negative) |
| 404 | `{ "error": "Session game not found" }` | Invalid sessionId or sessionGameId |

### Example

```bash
curl -X PATCH "http://localhost:5000/api/sessions/5/games/14/player-count" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"player_count": 6}'
```
