# 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 |
| 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"
```

---

## 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}'
```
