lila/documentation/ai-context/01-architecture.md
2026-05-16 01:59:43 +02:00

156 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 01 — Architecture
> **Purpose:** Give an LLM the structural context needed to navigate the codebase and understand data flow. Concatenate with 00-project-overview.md and 99-current-task.md.
> **Last updated:** 2026-05-15
> **Depends on:** 00-project-overview.md
---
## Monorepo Boundaries
```
lila/
├── apps/
│ ├── api/ — Express backend: routers, controllers, services, WS handlers
│ └── web/ — React frontend: routes, components, hooks, client state
├── packages/
│ ├── shared/ — Zod schemas, constants, derived types. THE CONTRACT.
│ └── db/ — Drizzle schema, migrations, models (termModel, lobbyModel), seeding
├── data-pipeline/ — Kaikki extraction → enrichment → sync to PostgreSQL
└── documentation/ — Human docs + ai-context/
```
**Critical rule:** `apps/api` never imports `drizzle-orm` for queries. It only calls functions exported from `packages/db`. All database code lives in `packages/db`.
---
## Layered Architecture (HTTP)
```
HTTP Request
Router — maps URL + method to controller (Express Router)
Controller — validates input (Zod safeParse), calls service, sends response
or next(error) for errorHandler middleware
Service — business logic only. No HTTP, no direct DB access.
Calls model functions from packages/db.
Model — database queries only. No business logic.
Lives in packages/db/src/models/
Database — PostgreSQL via Drizzle ORM
```
**Error flow:** Controller throws `ValidationError` (400) or `NotFoundError` (404) → caught by `errorHandler` middleware in `app.ts` → mapped to HTTP status. Unknown errors → 500.
---
## WebSocket Architecture
The WS server attaches to the same Express HTTP server. Upgrades on `/ws` path.
```
WS Connection Upgrade
Auth middleware — validates Better Auth session from cookie on upgrade
Message Router — dispatches by `type` field (Zod discriminated union)
Handler (lobby or game) — business logic, broadcasts state to room
In-memory stores (lobby game state, game session state)
```
**Message protocol:** All WS messages validated against Zod schemas in `packages/shared/src/schemas/lobby.ts` and `packages/shared/src/schemas/game.ts`. Router switches on `type` field.
**State storage:**
- Lobby membership → PostgreSQL (`lobbies`, `lobby_players` tables) — durable
- Game/room state → in-memory (`InMemoryLobbyGameStore`, `InMemoryGameSessionStore`) — ephemeral, lost on restart. Valkey migration planned.
---
## Data Flow: Singleplayer Quiz
```
POST /api/v1/game/start (GameRequestSchema)
Controller validates → Service.createGameSession
termModel.getGameTerms(filters) + termModel.getDistractors(filters)
Service shuffles options, stores session in GameSessionStore
Returns GameSession { sessionId, questions[] }
[frontend] User selects option → confirms → POST /api/v1/game/answer
Service evaluates server-side (correct answer NEVER sent to frontend)
Returns AnswerResult { isCorrect, correctOptionId, selectedOptionId }
```
**Key design:** Correct answer is stored server-side only (in GameSessionStore). Frontend only sees `optionId` (03) and `text`. Prevents cheating.
---
## Data Flow: Multiplayer Game
```
Host creates lobby → POST /api/v1/lobbies → returns room code (e.g. WOLF-42)
Players join via code → POST /api/v1/lobbies/:code/join
All players WS connect → send lobby:join with room code
Server broadcasts lobby:state (player list) to all in room
Host clicks "Start" → WS lobby:start
MultiplayerGameService generates questions, broadcasts game:question
Players submit answers via WS game:answer within 15s server timer
On all-answered or timeout → evaluate, broadcast game:answer_result
After N rounds → broadcast game:finished with final scores
```
---
## GameSessionStore Abstraction
```typescript
// packages/shared/src/schemas/game.ts (interface defined in apps/api)
interface GameSessionStore {
createSession(session: GameSession): Promise<void>;
getSession(sessionId: string): Promise<GameSession | null>;
// ...
}
```
**Current:** `InMemoryGameSessionStore` — Map-based, process memory, lost on restart.
**Planned:** `ValkeyGameSessionStore` — Redis-compatible, persists across restarts.
Same pattern for `LobbyGameStore`.
---
## Key Files by Concern
| Concern | Key Files |
| --------------- | -------------------------------------------------------------------------------------- |
| HTTP routing | `apps/api/src/routes/apiRouter.ts`, `gameRouter.ts`, `lobbyRouter.ts` |
| Controllers | `apps/api/src/controllers/gameController.ts`, `lobbyController.ts` |
| Services | `apps/api/src/services/gameService.ts`, `multiplayerGameService.ts`, `lobbyService.ts` |
| Models | `packages/db/src/models/termModel.ts`, `lobbyModel.ts` |
| WS handlers | `apps/api/src/ws/handlers/gameHandlers.ts`, `lobbyHandlers.ts` |
| WS router | `apps/api/src/ws/router.ts` |
| WS auth | `apps/api/src/ws/auth.ts` |
| Shared schemas | `packages/shared/src/schemas/game.ts`, `lobby.ts`, `auth.ts` |
| Constants | `packages/shared/src/constants.ts` |
| DB schema | `packages/db/src/db/schema.ts` |
| Auth config | `apps/api/src/lib/auth.ts` |
| Auth middleware | `apps/api/src/middleware/authMiddleware.ts` |