Commit graph

74 commits

Author SHA1 Message Date
lila
32ee1edf80 feat: add AuthModal component with login, register and social tabs
- Add AuthModal with login/register tabs and social buttons
- Add forgot-password and reset-password routes
- Add Sonner toaster to root layout
- Add auth search schemas to @lila/shared
- Add ESLint overrides for TanStack Router generics
2026-04-30 19:38:43 +02:00
lila
6297dff399 feat: add email/password auth backend + forgot/reset password routes
- Configure Better Auth emailAndPassword plugin with Resend
- Add email verification and password reset email sending
- Create forgot-password and reset-password frontend routes
- Add auth schemas to @lila/shared
2026-04-30 18:30:20 +02:00
lila
4d64d50598 removing comment that tested pre-commit/pre-push hook 2026-04-30 01:14:53 +02:00
lila
1bfc0606c3 test: verify pre-commit hook 2026-04-30 01:13:01 +02:00
lila
648c5d2979 fix: improve error semantics, clarify answer key type 2026-04-28 16:07:19 +02:00
lila
6eaf282651 fix: sanitise Zod validation error messages in game controller 2026-04-28 15:51:57 +02:00
lila
c081e632cf fix: explicit store update in evaluateAnswer, remove mutation through reference 2026-04-28 15:47:53 +02:00
lila
a02d3b3335 fix: deduplicate distractors against each other, guard thin distractor pool 2026-04-28 15:44:29 +02:00
lila
a02f3b139d fix: deduplicate distractors, replace tautological test, add distractor failure test 2026-04-28 15:17:31 +02:00
lila
3d16ab0fff feat: guard against empty terms in createGameSession 2026-04-28 15:08:06 +02:00
lila
1e30f04e81 feat: add ownership check to evaluateAnswer, AuthenticatedRequest type 2026-04-28 14:39:13 +02:00
lila
fdeb769640 feat: add TTL to GameSessionStore, replay protection and session cleanup to evaluateAnswer 2026-04-28 14:03:15 +02:00
lila
a4a4bfff57 refactor: dependency injection for GameSessionStore via composition root 2026-04-28 13:48:50 +02:00
lila
4f59f3bc14 formatting 2026-04-28 13:18:18 +02:00
lila
2ff7d1759e refactor: extract shuffleArray to lib/utils, rename correctAnswers to terms 2026-04-28 13:17:24 +02:00
lila
02ccc88d24 fix: change GAME_ROUNDS from strings to numbers 2026-04-28 12:29:46 +02:00
lila
768ca24eb2 fix: remove unfrozen pnpm install from builder stages
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m3s
2026-04-26 09:03:23 +02:00
lila
0da8397940 feat: add root and route-level error boundaries 2026-04-26 08:45:18 +02:00
lila
4de2c40482 feat: add 404 catch-all route and NotFound page 2026-04-24 18:28:22 +02:00
lila
3971642848 Merge branch 'dev'
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m51s
2026-04-24 10:15:23 +02:00
lila
ee719aaa58 test: add test file for multiplayerGameService
Covers generateMultiplayerQuestions: question count, option structure,
correct answer inclusion, correctOptionId integrity, prompt/gloss
passthrough, DB call arguments, and error propagation.
2026-04-24 10:14:28 +02:00
lila
4ece995385 test: fill coverage gaps in lobbyService and gameService
- joinLobby: addPlayer returns falsy (race condition fallback)
- joinLobby: lobby disappears between addPlayer and final fetch
- createLobby: non-unique-violation errors re-thrown immediately
- createGameSession: unexpected DB errors propagate correctly
2026-04-24 10:11:36 +02:00
lila
59049002fc fix(api): skip rate limiting for non-sensitive auth endpoints
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m50s
The authLimiter was blocking legitimate users because Better Auth's
client polls /get-session frequently (on mount, route changes, focus),
and /sign-out was also getting blocked after repeated session polls.

Skip rate limiting for:
- /get-session — read-only, requires valid cookie, no attack surface
- /sign-out — no attack value in blocking logout
- /callback/* — OAuth callbacks from providers

Brute force protection remains on /sign-in, /sign-up, and other
sensitive endpoints.
2026-04-23 22:12:38 +02:00
lila
9ab2bc3d0e feat(api): apply rate limiters to game and lobby routes
Wire gameLimiter into gameRouter and lobbyLimiter into lobbyRouter.
Both run after requireAuth since they key by req.session.user.id.
2026-04-23 20:36:36 +02:00
lila
9893ead689 feat(api): add helmet security headers and rate limiting
- Add helmet middleware for secure HTTP response headers
- Add express-rate-limit with three limiters:
  - authLimiter: per-IP, 20 req/15min on /api/auth/*
  - gameLimiter: per-user, 150 req/15min (not yet wired)
  - lobbyLimiter: per-user, 20 req/15min (not yet wired)
- Set trust proxy for correct client IP behind Caddy
- Add tests for all three limiters and helmet headers
2026-04-23 11:13:11 +02:00
lila
1a50f73c74 updated docker pipeline to include database migrations, added dummy table to verify the pipeline works
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m52s
2026-04-23 09:19:57 +02:00
lila
0dba68904e adding labels
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m12s
2026-04-21 14:44:14 +02:00
lila
0a0bafa0ec complete design overhaul 2026-04-19 19:25:55 +02:00
lila
4f514a4e99 feat(landing): add landing page with Hero, HowItWorks and FeatureCards 2026-04-19 18:24:42 +02:00
lila
767970b6e6 renaming signin to login 2026-04-19 17:57:47 +02:00
lila
6c4ef371c1 feat(navbar): add modular navbar components and color variables 2026-04-19 17:51:43 +02:00
lila
6dbc16f23d style(global): add color variables with dark theme support 2026-04-19 17:27:16 +02:00
lila
8aaafea3fc feat: multiplayer slice — end to end working
WebSocket server:
- WS auth via Better Auth session on upgrade request
- Router with discriminated union dispatch and two-layer error handling
- In-memory connections map with broadcastToLobby
- Lobby handlers: join, leave, start
- Game handlers: answer, resolve round, end game, game:ready for state sync
- Shared game state store (LobbyGameStore interface + InMemory impl)
- Timer map separate from store for Valkey-readiness

REST API:
- POST /api/v1/lobbies — create lobby + add host as first player
- POST /api/v1/lobbies/:code/join — atomic join with capacity/status checks
- getLobbyWithPlayers added to model for id-based lookup

Frontend:
- WsClient class with typed on/off, connect/disconnect, isConnected
- WsProvider owns connection lifecycle (connect/disconnect/isConnected state)
- WsConnector component triggers connection at multiplayer layout mount
- Lobby waiting room: live player list, copyable code, host Start button
- Game view: reuses QuestionCard, game:ready on mount, round results
- MultiplayerScoreScreen: sorted scores, winner highlight, tie handling
- Vite proxy: /ws and /api proxied to localhost:3000 for dev cookie fix

Tests:
- lobbyService.test.ts: create, join, retry, idempotency, full lobby
- auth.test.ts: 401 reject, upgrade success, 500 on error
- router.test.ts: dispatch all message types, error handling
- vitest.config.ts: exclude dist folder

Fixes:
- server.ts: server.listen() instead of app.listen() for WS support
- StrictMode removed from main.tsx (incompatible with WS lifecycle)
- getLobbyWithPlayers(id) added for handleLobbyStart lookup
2026-04-18 23:32:21 +02:00
lila
540155788a fix(api): use server.listen instead of app.listen for WebSocket support
- server.ts: switch from app.listen() to server.listen() so WebSocket
  upgrade handler is on the same server as HTTP requests
- lobbyService: add host as first player on lobby creation
- ws-client: guard against reconnect when already connecting
- ws-provider: skip connect if already connected
2026-04-18 21:57:58 +02:00
lila
974646ebfb feat(web): update navigation with Play and Multiplayer links
- Add Play link to /play
- Add Multiplayer link to /multiplayer
- Remove About link (route kept, just not linked)
- Simplify signOut onClick to .then() chain
2026-04-18 10:59:50 +02:00
lila
f2eb6ce17f feat(web): add multiplayer lobby, game, and score screen routes
- lobby.$code.tsx: waiting room with live player list via lobby:state,
  copyable lobby code, host Start Game button (disabled until 2+ players),
  sends lobby:join on connect, lobby:leave on unmount
- game.$code.tsx: in-game view, sends game:ready on mount to get current
  question, handles game:question/answer_result/finished messages,
  reuses QuestionCard component, shows round results after each answer
- MultiplayerScoreScreen: final score screen sorted by score, highlights
  winner(s) with crown, handles ties via winnerIds array, Play Again
  navigates back to lobby, Leave goes to multiplayer landing
- GameRouteSearchSchema added to shared for typed lobbyId search param
  without requiring Zod in apps/web
2026-04-18 10:33:48 +02:00
lila
d064338145 feat(web): add multiplayer lobby waiting room
- connects WebSocket on mount, sends lobby:join after connection open
- registers handlers for lobby:state, game:question, error messages
- lobby:state updates player list in real time
- game:question navigates to game route (server re-sends via game:ready)
- displays lobby code as copyable button
- host sees Start Game button, disabled until 2+ players connected
- non-host sees waiting message
- cleanup sends lobby:leave and disconnects on unmount
- lobbyIdRef tracks lobby id for reliable cleanup before lobby state arrives
2026-04-18 10:10:25 +02:00
lila
6975384751 feat(api): add game:ready message for client state sync
- WsGameReadySchema added to shared schemas and WsClientMessageSchema
- handleGameReady sends current game:question directly to requesting
  client socket (not broadcast) — foundation for reconnection slice
- router dispatches game:ready to handleGameReady handler
2026-04-18 09:54:31 +02:00
lila
4d4715b4ee feat(web): add multiplayer layout route and landing page
- multiplayer.tsx: layout route wrapping all multiplayer children
  with WsProvider, auth guard via beforeLoad
- multiplayer/index.tsx: create/join landing page
  - POST /api/v1/lobbies to create, navigates to lobby waiting room
  - POST /api/v1/lobbies/:code/join to join, normalizes code to
    uppercase before sending
  - loading states per action, error display, Enter key on join input
  - imports Lobby type from @lila/shared (single source of truth)
2026-04-17 21:33:40 +02:00
lila
9affe339c6 feat(web): add WebSocket client and context infrastructure
- WsClient class: connect/disconnect/send/on/off/isConnected/clearCallbacks
- connect() derives wss:// from https:// automatically, returns Promise<void>
- on/off typed with Extract<WsServerMessage, { type: T }> for precise
  callback narrowing, callbacks stored as Map<string, Set<fn>>
- ws-context.ts: WsContextValue type + WsContext definition
- ws-provider.tsx: WsProvider with module-level wsClient singleton,
  owns connection lifecycle (connect/disconnect/isConnected state)
- ws-hooks.ts: useWsClient, useWsConnected, useWsConnect, useWsDisconnect
2026-04-17 21:12:15 +02:00
lila
d60b0da9df feat(web): add WsClient class for multiplayer WebSocket communication
- connect(apiUrl) derives wss:// from https:// automatically, returns
  Promise<void> resolving on open, rejecting on error
- disconnect() closes connection, no-op if already closed
- isConnected() checks readyState === OPEN
- send(message) typed to WsClientMessage discriminated union
- on/off typed with Extract<WsServerMessage, { type: T }> for
  precise callback narrowing per message type
- callbacks stored as Map<string, Set<fn>> supporting multiple
  listeners per message type
- clearCallbacks() for explicit cleanup on provider unmount
- onError/onClose as separate lifecycle properties distinct
  from message handlers
2026-04-17 20:44:33 +02:00
lila
ce19740cc8 fix(lint): resolve all eslint errors across monorepo
- Type response bodies in gameController.test.ts to fix no-unsafe-member-access
- Replace async methods with Promise.resolve() in InMemoryGameSessionStore
  and InMemoryLobbyGameStore to satisfy require-await rule
- Add argsIgnorePattern and varsIgnorePattern to eslint config so
  underscore-prefixed params are globally ignored
- Fix no-misused-promises in ws/index.ts, lobbyHandlers, gameHandlers,
  __root.tsx, login.tsx and play.tsx by using void + .catch()
- Fix no-floating-promises on navigate calls in login.tsx
- Move API_URL outside Play component to fix useCallback dependency warning
- Type fetch response bodies in play.tsx to fix no-unsafe-assignment
- Add only-throw-error: off for route files (TanStack Router throw redirect)
- Remove unused WebSocket import from express.d.ts
- Fix unsafe return in connections.ts by typing empty Map constructor
- Exclude scripts/ folder from eslint
- Add targeted override for better-auth auth-client.ts (upstream typing issue)
2026-04-17 16:46:33 +02:00
lila
7f56ad89e6 feat(api): add WebSocket handlers and game state management
- handleLobbyJoin: validates DB membership and waiting status,
  registers connection, tags ws.lobbyId, broadcasts lobby:state
- handleLobbyLeave: host leave deletes lobby, non-host leave
  removes player and broadcasts updated state
- handleLobbyStart: validates host + connected players >= 2,
  generates questions, initializes LobbyGameData, broadcasts
  first game:question, starts 15s round timer
- handleGameAnswer: stores answer, resolves round when all
  players answered or timer fires
- resolveRound: evaluates answers, updates scores, broadcasts
  game:answer_result, advances to next question or ends game
- endGame: persists final scores via finishGame transaction,
  determines winnerIds handling ties, broadcasts game:finished
- gameState.ts: shared lobbyGameStore singleton and timers Map
- LobbyGameData extended with code field to avoid mid-game
  DB lookups by ID
2026-04-17 15:50:08 +02:00
lila
745c5c4e3a feat(api): add WebSocket foundation and multiplayer game store
- Add ws/ directory: server setup, auth, router, connections map
- WebSocket auth rejects upgrade with 401 if no Better Auth session
- Router parses WsClientMessageSchema, dispatches to handlers,
  two-layer error handling (AppError -> WsErrorSchema, unknown -> 500)
- connections.ts: in-memory Map<lobbyId, Map<userId, WebSocket>>
  with addConnection, removeConnection, broadcastToLobby
- LobbyGameStore interface + InMemoryLobbyGameStore implementation
  following existing GameSessionStore pattern
- multiplayerGameService: generateMultiplayerQuestions() decoupled
  from single-player flow, hardcoded defaults en->it nouns easy 3 rounds
- handleLobbyJoin and handleLobbyLeave implemented
- WsErrorSchema added to shared schemas
- server.ts switched to createServer + setupWebSocket
2026-04-17 09:36:16 +02:00
lila
4d1ebe2450 feat(api): add REST endpoints for lobby create and join
- POST /api/v1/lobbies creates a lobby with a Crockford-Base32
  6-char code, retrying on unique violation up to 5 times
- POST /api/v1/lobbies/:code/join validates lobby state then
  calls the model's atomic addPlayer, idempotent for repeat
  joins from the same user
- Routes require authentication via requireAuth
2026-04-16 19:51:38 +02:00
lila
8c241636bf feat(api): attach session to request in requireAuth
- Add Express Request type augmentation for req.session
- requireAuth now sets req.session after session validation,
  so protected handlers can read the user without calling
  getSession again
- Add ConflictError (409) alongside existing AppError subclasses
2026-04-16 19:51:10 +02:00
lila
69d4cfde97 adding build step to dev script 2026-04-15 04:50:47 +02:00
lila
bc38137a12 feat: add production deployment config
- Add docker-compose.prod.yml and Caddyfile for Caddy reverse proxy
- Add production stages to frontend Dockerfile (nginx for static files)
- Fix monorepo package exports for production builds (dist/src paths)
- Add CORS_ORIGIN env var for cross-origin config
- Add Better Auth baseURL, cookie domain, and trusted origins from env
- Use VITE_API_URL for API calls in auth-client and play route
- Add credentials: include for cross-origin fetch requests
- Remove unused users table from schema
2026-04-14 11:38:40 +02:00
lila
3f7bc4111e chore: rename project from glossa to lila
- Update all package names from @glossa/* to @lila/*
- Update all imports, container names, volume names
- Update documentation references
- Recreate database with new credentials
2026-04-13 10:00:52 +02:00
lila
a3685a9e68 feat(api): add auth middleware to protect game endpoints
- Add requireAuth middleware using Better Auth session validation
- Apply to all game routes (start, answer)
- Unauthenticated requests return 401
2026-04-12 13:38:32 +02:00