6.1 KiB
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_playerstables) — 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 (0–3) 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
// 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 |