updating documentation

This commit is contained in:
lila 2026-05-16 01:59:43 +02:00
parent 1ba57c7e9d
commit 7e0311683f
25 changed files with 2660 additions and 226 deletions

View file

@ -0,0 +1,367 @@
# 03 — API Contract
> **Purpose:** REST and WebSocket endpoint reference with exact Zod schemas. Concatenate with 00-project-overview.md and 99-current-task.md.
> **Last updated:** 2026-05-15
> **Depends on:** 00-project-overview.md, 02-data-model.md
---
## REST Endpoints
### Health
```
GET /api/v1/health
```
**Response:** `{ "status": "ok" }`
**Auth:** None (public)
---
### Game — Start Session
```
POST /api/v1/game/start
```
**Request body** (GameRequestSchema):
```typescript
{
source_language: SupportedLanguageCode, // "en" | "it" | "de" | "es" | "fr"
target_language: SupportedLanguageCode,
pos: SupportedPos, // "noun" | "verb" | "adjective" | "adverb"
difficulty: DifficultyLevel, // "easy" | "intermediate" | "hard"
rounds: GameRounds // "3" | "10" (string enum, converted to number in service)
}
```
**Validation rules:**
- `source_language` !== `target_language`
- Both languages in `SUPPORTED_LANGUAGE_CODES`
- `pos` in `SUPPORTED_POS`
- `difficulty` in `DIFFICULTY_LEVELS`
- `rounds` in `GAME_ROUNDS`
**Response** (GameSessionSchema):
```typescript
{
sessionId: string, // UUID
questions: GameQuestion[]
}
```
**GameQuestionSchema:**
```typescript
{
questionId: string, // UUID
prompt: string, // Word in source language
gloss: string | null, // Definition (falls back to English if target lang gloss missing)
options: AnswerOption[] // 4 items, shuffled
}
```
**AnswerOptionSchema:**
```typescript
{
optionId: number, // 03
text: string // Translation in target language
}
```
**Note:** The correct answer is NOT included in the response. The frontend only sees `optionId` and `text`. The server stores `questionId → correctOptionId` mapping in the GameSessionStore.
**Auth:** Required (session middleware)
---
### Game — Submit Answer
```
POST /api/v1/game/answer
```
**Request body** (AnswerSubmissionSchema):
```typescript
{
sessionId: string, // UUID
questionId: string, // UUID
selectedOptionId: number // 03
}
```
**Response** (AnswerResultSchema):
```typescript
{
questionId: string,
isCorrect: boolean,
correctOptionId: number, // 03
selectedOptionId: number // 03
}
```
**Error cases:**
- Session not found → 404 NotFoundError
- Question not in session → 404 NotFoundError
- Invalid optionId → 400 ValidationError
**Auth:** Required
---
### Lobby — Create
```
POST /api/v1/lobbies
```
**Request body:** None (host's auth session determines host_id)
**Response:**
```typescript
{
id: string, // UUID
code: string, // Human-readable room code (e.g. "WOLF-42")
host_id: string,
status: "waiting",
max_players: number,
settings: object | null,
created_at: string
}
```
**Auth:** Required
---
### Lobby — Join
```
POST /api/v1/lobbies/:code/join
```
**Path param:** `code` — room code (e.g. "WOLF-42")
**Response:** Same as create (the lobby object)
**Error cases:**
- Lobby not found → 404
- Lobby full → 400
- Already joined → 200 (idempotent)
**Auth:** Required
---
### Auth
```
ALL /api/auth/* — Better Auth handlers (public)
```
Better Auth mounts its own router at `/api/auth/*`. Handles:
- `/api/auth/signin/social` — initiate social login
- `/api/auth/callback/:provider` — OAuth callback
- `/api/auth/signout` — clear session
- `/api/auth/session` — get current session
**Auth:** Mixed (some public, some require valid session)
---
## WebSocket Protocol
All WS messages are JSON objects with a `type` field. The `type` is a discriminated union — the router validates the payload against the schema for that type.
### Connection
1. Client opens WebSocket to `wss://api.lilastudy.com/ws`
2. Server validates Better Auth session from cookie on upgrade
3. Connection established
### Client → Server Messages
#### `lobby:join`
```typescript
{
type: "lobby:join",
payload: {
code: string // Room code (e.g. "WOLF-42")
}
}
```
#### `lobby:leave`
```typescript
{
type: "lobby:leave",
payload: {
code: string
}
}
```
#### `lobby:start`
```typescript
{
type: "lobby:start",
payload: {
code: string
}
}
```
Only the host can send this. Triggers game start.
#### `game:answer`
```typescript
{
type: "game:answer",
payload: {
code: string,
questionId: string,
optionId: number // 03
}
}
```
Must be sent within the 15-second server timer.
---
### Server → Client Messages
#### `lobby:state`
```typescript
{
type: "lobby:state",
payload: {
code: string,
players: {
id: string,
display_name: string,
is_host: boolean
}[],
status: "waiting" | "in_progress" | "finished",
settings: object | null
}
}
```
Broadcast to all players in the lobby on any membership change.
#### `game:question`
```typescript
{
type: "game:question",
payload: {
questionId: string,
prompt: string,
gloss: string | null,
options: { optionId: number, text: string }[],
timeLimit: number // seconds (15)
}
}
```
Broadcast when the game starts or a new round begins.
#### `game:answer_result`
```typescript
{
type: "game:answer_result",
payload: {
questionId: string,
results: {
playerId: string,
displayName: string,
isCorrect: boolean,
selectedOptionId: number,
score: number
}[]
}
}
```
Broadcast after all players answer or timer expires.
#### `game:finished`
```typescript
{
type: "game:finished",
payload: {
finalScores: {
playerId: string,
displayName: string,
score: number
}[],
winner: {
playerId: string,
displayName: string
} | null // null for ties
}
}
```
Broadcast after all rounds complete.
---
## Zod Schema Locations
All schemas live in `packages/shared/src/schemas/`:
| Schema | File | Used by |
| ---------------------- | ---------- | --------------------------------------- |
| GameRequestSchema | `game.ts` | API controller, frontend GameSetup |
| GameSessionSchema | `game.ts` | API service, frontend quiz flow |
| GameQuestionSchema | `game.ts` | API service, frontend QuestionCard |
| AnswerOptionSchema | `game.ts` | API service, frontend OptionButton |
| AnswerSubmissionSchema | `game.ts` | API controller, frontend submit handler |
| AnswerResultSchema | `game.ts` | API controller, frontend ScoreScreen |
| LobbyCreateSchema | `lobby.ts` | API controller |
| LobbyJoinSchema | `lobby.ts` | API controller |
| LobbyStateSchema | `lobby.ts` | WS handler, frontend lobby UI |
| WebSocketMessageSchema | `lobby.ts` | WS router (discriminated union) |
**Rule:** Never duplicate these schemas. Import from `packages/shared` in both API and frontend.
---
## Error Responses
All errors follow this shape:
```typescript
{
error: string, // Human-readable message
statusCode: number // HTTP status
}
```
**Common status codes:**
- 400 — ValidationError (bad input, schema mismatch)
- 401 — Unauthorized (no valid session)
- 404 — NotFoundError (session, question, or lobby not found)
- 500 — Unknown error (logged, generic message to client)