updating documentation
This commit is contained in:
parent
1ba57c7e9d
commit
7e0311683f
25 changed files with 2660 additions and 226 deletions
118
documentation/archive/notes.md
Normal file
118
documentation/archive/notes.md
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
# notes
|
||||
|
||||
## prompt
|
||||
|
||||
ive attached the readme of my project. this is my current task:
|
||||
|
||||
task description.
|
||||
|
||||
1. tell me which files you need to see to get the full context of the problem
|
||||
2. walk me text-only through the problem and the solution
|
||||
3. if we need to update multiple files: lets go through them one by one, no matter how many files
|
||||
4. if we go through a file, we'll do it slowly section by section, no matter how many sections
|
||||
5. how to name the current feature branch? also tell me when its time to git commit and provide a commit message
|
||||
6. if we have multiple options to do something, also always provide options that reflect current industry standards and best practices
|
||||
7. never assume anything! always ask for clarification!
|
||||
8. For every completed task, produce a ticket file in documentation/tickets/. Use ADR format (adr-) for decisions between options with long-term consequences. Use feat-/fix-/chore- for routine tasks. Always include a setup guide or summary of what was done. Suggest the filename.
|
||||
|
||||
## tasks
|
||||
|
||||
- **IMPORTANT** db migrations have to be part of the deployment pipeline!!!!!!!!!!!!!!!!!!
|
||||
- put users in separate db
|
||||
- pinning dependencies in package.json files
|
||||
- rethink organisation of datafiles and wordlists
|
||||
- admin dashboard for user management, also overview of words and languages and all their stats
|
||||
|
||||
## problems+thoughts
|
||||
|
||||
### IMPORTANT
|
||||
|
||||
verify if hetzner domain needs to be pushed, theres a change on hetzner and some domains need to be migrated
|
||||
|
||||
### redirect or page not found
|
||||
|
||||
subdomains or pages that dont exist should have page not found or should redirect
|
||||
|
||||
### docker credential helper
|
||||
|
||||
WARNING! Your credentials are stored unencrypted in '/home/languagedev/.docker/config.json'.
|
||||
Configure a credential helper to remove this warning. See
|
||||
https://docs.docker.com/go/credential-store/
|
||||
|
||||
### docker containers on startup?
|
||||
|
||||
laptop: verify if docker containers run on startup (they shouldnt)
|
||||
|
||||
### vps setup
|
||||
|
||||
- monitoring and logging (eg via chrootkit or rkhunter, logwatch/monit => mails daily with summary)
|
||||
<<<<<<< HEAD
|
||||
- ~~keep the vps clean (e.g. old docker images/containers)~~ ✅ CI/CD pipeline runs `docker image prune -f` after deploy
|
||||
|
||||
### ~~cd/ci pipeline~~ ✅ RESOLVED
|
||||
|
||||
Forgejo Actions with runner on VPS, Forgejo built-in container registry. See `deployment.md`.
|
||||
|
||||
### ~~postgres backups~~ ✅ RESOLVED
|
||||
|
||||
# Daily pg_dump cron job, 7-day retention, dev laptop auto-sync via rsync. See `deployment.md`.
|
||||
|
||||
> > > > > > > dev
|
||||
|
||||
### try now option
|
||||
|
||||
there should be an option to try the app without an account so users can see what they would get when creating an account
|
||||
|
||||
### resolve deps problem
|
||||
|
||||
pnpm --filter web add better-auth
|
||||
WARN 2 deprecated subdependencies found: @esbuild-kit/core-utils@3.3.2, @esbuild-kit/esm-loader@2.6.5
|
||||
Progress: resolved 577, reused 0, downloaded 0, added 0, done
|
||||
WARN Issues with peer dependencies found
|
||||
.
|
||||
└─┬ eslint-plugin-react-hooks 7.0.1
|
||||
└── ✕ unmet peer eslint@"^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0": found 10.0.3
|
||||
. | +3 +
|
||||
Done in 5.6s using pnpm v10.33.0
|
||||
|
||||
### env managing
|
||||
|
||||
using env files is not uptodate, use a modern, proper approach
|
||||
|
||||
### apple login
|
||||
|
||||
add option to login with apple accounts
|
||||
|
||||
### mail login
|
||||
|
||||
add option to login with email+pw
|
||||
|
||||
### google login
|
||||
|
||||
credentials are saved in Downloads/lila/ json file
|
||||
|
||||
app publication/verification:
|
||||
|
||||
Branding and Data Access (Scope) Verification
|
||||
|
||||
In addition to brand verification, your app may also need to be verified to use certain scopes. You can view and track this on the Verification Center page:
|
||||
|
||||
Branding status: This tracks the verification of your app's public-facing brand (name, logo, etc.).
|
||||
Data access status: This tracks the verification of the specific data (scopes) your app is requesting to access.
|
||||
|
||||
Note: You must have a published branding status before you can request verification for data access (scopes).
|
||||
Manage App Audience Configuration
|
||||
Publishing Status
|
||||
Manage your app publishing status in the Audience page of the Google Auth Platform.
|
||||
User Type
|
||||
Manage your app audience in the Audience page of the Google Auth Platform.
|
||||
|
||||
[link](https://support.google.com/cloud/answer/15549049?visit_id=01775982668127-2568683599515917262&rd=1#publishing-status&zippy=%2Cpublishing-status%2Cuser-type)
|
||||
|
||||
## tipps
|
||||
|
||||
- backend advice: [backend](https://github.com/MohdOwaisShah/backend)
|
||||
- openapi
|
||||
- bruno for api testing
|
||||
- tailscale
|
||||
- musicforprogramming.net
|
||||
241
documentation/archive/roadmap.md
Normal file
241
documentation/archive/roadmap.md
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
# lila — Roadmap
|
||||
|
||||
Each phase produces a working increment. Nothing is built speculatively.
|
||||
|
||||
---
|
||||
|
||||
## Phase 0 — Foundation ✅
|
||||
|
||||
**Goal:** Empty repo that builds, lints, and runs end-to-end.
|
||||
**Done when:** `pnpm dev` starts both apps; `GET /api/health` returns 200; React renders a hello page.
|
||||
|
||||
- [x] Initialise pnpm workspace monorepo: `apps/web`, `apps/api`, `packages/shared`, `packages/db`
|
||||
- [x] Configure TypeScript project references across packages
|
||||
- [x] Set up ESLint + Prettier with shared configs in root
|
||||
- [x] Set up Vitest in `api` and `web` and both packages
|
||||
- [x] Scaffold Express app with `GET /api/health`
|
||||
- [x] Scaffold Vite + React app with TanStack Router (single root route)
|
||||
- [x] Configure Drizzle ORM + connection to local PostgreSQL
|
||||
- [x] Write first migration (empty — validates the pipeline works)
|
||||
- [x] `docker-compose.yml` for local dev: `api`, `web`, `postgres`, `valkey`
|
||||
- [x] Root `.env.example` for local dev (`docker-compose.yml` + API)
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — Vocabulary Data + API ✅
|
||||
|
||||
**Goal:** Word data lives in the DB and can be queried via the API.
|
||||
**Done when:** API returns quiz sessions with distractors, error handling and tests in place.
|
||||
|
||||
### Data pipeline
|
||||
|
||||
- [x] Run `extract-en-it-nouns.py` locally → generates JSON
|
||||
- [x] Write Drizzle schema: `terms`, `translations`, `term_glosses`, `decks`, `deck_terms`
|
||||
- [x] Write and run migration (includes CHECK constraints)
|
||||
- [x] Write `packages/db/src/seeding-datafiles.ts` (imports all terms + translations)
|
||||
- [x] Write `packages/db/src/generating-deck.ts` (idempotent deck generation)
|
||||
- [x] CEFR enrichment pipeline complete for English and Italian
|
||||
- [x] Expand data pipeline — import all OMW languages and POS
|
||||
|
||||
### Schemas
|
||||
|
||||
- [x] Define `GameRequestSchema` in `packages/shared`
|
||||
- [x] Define `AnswerOption`, `GameQuestion`, `GameSession`, `AnswerSubmission`, `AnswerResult` schemas
|
||||
- [x] Derived types exported from constants (`SupportedLanguageCode`, `SupportedPos`, `DifficultyLevel`)
|
||||
|
||||
### Model layer
|
||||
|
||||
- [x] `getGameTerms()` with POS / language / difficulty / limit filters
|
||||
- [x] Double join on `translations` (source + target language)
|
||||
- [x] Gloss left join
|
||||
- [x] `getDistractors()` with POS / difficulty / language / excludeTermId / excludeText filters
|
||||
- [x] Models correctly placed in `packages/db`
|
||||
|
||||
### Service layer
|
||||
|
||||
- [x] `createGameSession()` — fetches terms, fetches distractors, shuffles options, stores session
|
||||
- [x] `evaluateAnswer()` — looks up session, compares submitted optionId to stored correct answer
|
||||
- [x] `GameSessionStore` interface + `InMemoryGameSessionStore` (swappable to Valkey)
|
||||
|
||||
### API endpoints
|
||||
|
||||
- [x] `POST /api/v1/game/start` — route, controller, service
|
||||
- [x] `POST /api/v1/game/answer` — route, controller, service
|
||||
- [x] End-to-end pipeline verified with test script
|
||||
|
||||
### Error handling
|
||||
|
||||
- [x] Typed error classes: `AppError`, `ValidationError` (400), `NotFoundError` (404)
|
||||
- [x] Central error middleware in `app.ts`
|
||||
- [x] Controllers cleaned up: validate → call service → `next(error)` on failure
|
||||
|
||||
### Tests
|
||||
|
||||
- [x] Unit tests for `createGameSession` (question shape, options, distractors, gloss)
|
||||
- [x] Unit tests for `evaluateAnswer` (correct, incorrect, missing session, missing question)
|
||||
- [x] Integration tests for both endpoints via supertest (200, 400, 404)
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — Singleplayer Quiz UI ✅
|
||||
|
||||
**Goal:** A user can complete a full quiz in the browser.
|
||||
**Done when:** User visits `/play`, configures settings, answers questions, sees score screen, can play again.
|
||||
|
||||
- [x] `GameSetup` component (language, POS, difficulty, rounds)
|
||||
- [x] `QuestionCard` component (prompt word + 4 answer buttons)
|
||||
- [x] `OptionButton` component (idle / correct / wrong states)
|
||||
- [x] `ScoreScreen` component (final score + play again)
|
||||
- [x] Vite proxy configured for dev
|
||||
- [x] `selectedOptionId` added to `AnswerResult` (discovered during frontend work)
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — Auth ✅
|
||||
|
||||
**Goal:** Users can log in via Google or GitHub and stay logged in.
|
||||
**Done when:** Better Auth session is validated on protected routes; unauthenticated users are redirected to login; user row is created on first social login.
|
||||
|
||||
- [x] Install `better-auth` and configure with Drizzle adapter + PostgreSQL
|
||||
- [x] Mount Better Auth handler on `/api/auth/*` in `app.ts`
|
||||
- [x] Configure Google and GitHub social providers
|
||||
- [x] Run Better Auth CLI to generate and migrate auth tables (user, session, account, verification)
|
||||
- [x] Add session validation middleware for protected API routes
|
||||
- [x] Frontend: install `better-auth/react` client
|
||||
- [x] Frontend: login page with Google + GitHub buttons
|
||||
- [x] Frontend: TanStack Router auth guard using `useSession`
|
||||
- [x] Frontend: TanStack Query `api.ts` sends credentials with every request
|
||||
- [x] Unit tests for session middleware
|
||||
|
||||
---
|
||||
|
||||
## Phase 6 — Production Deployment ✅
|
||||
|
||||
**Goal:** App is live on Hetzner, accessible via HTTPS on all subdomains.
|
||||
**Done when:** `https://lilastudy.com` loads; `https://api.lilastudy.com` responds; auth flow works end-to-end; CI/CD deploys on push to main.
|
||||
|
||||
_Note: Deployment was moved ahead of multiplayer — the app is useful without multiplayer but not without deployment._
|
||||
|
||||
### Infrastructure
|
||||
|
||||
- [x] Hetzner VPS provisioned (Debian 13, ARM64, 4GB RAM)
|
||||
- [x] SSH hardening, ufw firewall, fail2ban
|
||||
- [x] Docker + Docker Compose installed
|
||||
- [x] Domain DNS: A record + wildcard `*.lilastudy.com` pointing to VPS
|
||||
|
||||
### Reverse proxy
|
||||
|
||||
- [x] Caddy container with automatic HTTPS (Let's Encrypt)
|
||||
- [x] Subdomain routing: `lilastudy.com` → web, `api.lilastudy.com` → API, `git.lilastudy.com` → Forgejo
|
||||
|
||||
### Docker stack
|
||||
|
||||
- [x] Production `docker-compose.yml` with all services on shared network
|
||||
- [x] No ports exposed on internal services — only Caddy (80/443) and Forgejo SSH (2222)
|
||||
- [x] Production Dockerfile stages for API (runner) and frontend (nginx:alpine)
|
||||
- [x] Monorepo package exports fixed for production (dist/src paths)
|
||||
- [x] Production `.env` with env-driven CORS, auth URLs, cookie domain
|
||||
|
||||
### Git server + container registry
|
||||
|
||||
- [x] Forgejo running with built-in container registry
|
||||
- [x] SSH on port 2222, dev laptop `~/.ssh/config` configured
|
||||
- [x] Repository created, code pushed
|
||||
|
||||
### CI/CD
|
||||
|
||||
- [x] Forgejo Actions enabled
|
||||
- [x] Forgejo Runner container on VPS with Docker socket access
|
||||
- [x] `.forgejo/workflows/deploy.yml` — build, push, deploy via SSH on push to main
|
||||
- [x] Registry and SSH secrets configured in Forgejo
|
||||
|
||||
### Database
|
||||
|
||||
- [x] Initial seed via pg_dump from dev laptop
|
||||
- [x] Seeding script is idempotent (onConflictDoNothing) for future data additions
|
||||
- [x] Schema migrations via Drizzle (migrate first, deploy second)
|
||||
|
||||
### OAuth
|
||||
|
||||
- [x] Google and GitHub OAuth redirect URIs configured for production
|
||||
- [x] Cross-subdomain cookies via COOKIE_DOMAIN=.lilastudy.com
|
||||
|
||||
### Backups
|
||||
|
||||
- [x] Daily cron job (3 AM) with pg_dump, 7-day retention
|
||||
- [x] Dev laptop auto-syncs backups on login via rsync
|
||||
|
||||
### Documentation
|
||||
|
||||
- [x] `deployment.md` covering full infrastructure setup
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 — Multiplayer Lobby
|
||||
|
||||
**Goal:** Players can create and join rooms; the host sees all joined players in real time.
|
||||
**Done when:** Two browser tabs can join the same room and see each other's display names update live via WebSocket.
|
||||
|
||||
- [x] Write Drizzle schema: `lobbies`, `lobby_players`
|
||||
- [x] Write and run migration
|
||||
- [x] `POST /api/v1/lobbies` and `POST /api/v1/lobbies/:code/join` REST endpoints
|
||||
- [x] `LobbyService`: create lobby with Crockford Base32 code, join lobby, enforce max player limit
|
||||
- [x] WebSocket server: attach `ws` upgrade handler to Express HTTP server
|
||||
- [x] WS auth middleware: validate Better Auth session on upgrade
|
||||
- [x] WS message router: dispatch by `type` via Zod discriminated union
|
||||
- [x] `lobby:join` / `lobby:leave` handlers → broadcast `lobby:state`
|
||||
- [x] Lobby membership tracked in PostgreSQL (durable), game state in-memory (Valkey deferred)
|
||||
- [x] Define all WS event Zod schemas in `packages/shared`
|
||||
- [x] Frontend: `/multiplayer` — create lobby + join-by-code
|
||||
- [x] Frontend: `/multiplayer/lobby/:code` — player list, lobby code, "Start Game" (host only)
|
||||
- [x] Frontend: WS client class with typed message handlers
|
||||
|
||||
---
|
||||
|
||||
## Phase 5 — Multiplayer Game
|
||||
|
||||
**Goal:** Host starts a game; all players answer simultaneously in real time; a winner is declared.
|
||||
**Done when:** 2–4 players complete a 3-round game with correct live scores and a winner screen.
|
||||
|
||||
- [x] `MultiplayerGameService`: generate question sequence, enforce 15s server timer
|
||||
- [x] `lobby:start` WS handler → broadcast first `game:question`
|
||||
- [x] `game:answer` WS handler → collect per-player answers
|
||||
- [x] On all-answered or timeout → evaluate, broadcast `game:answer_result`
|
||||
- [x] After N rounds → broadcast `game:finished`, update DB (transactional)
|
||||
- [x] Frontend: `/multiplayer/game/:code` route
|
||||
- [x] Frontend: reuse `QuestionCard` + `OptionButton`; round results per player
|
||||
- [x] Frontend: `MultiplayerScoreScreen` — winner highlight, final scores, play again
|
||||
- [x] Unit tests for `LobbyService`, WS auth, WS router
|
||||
|
||||
---
|
||||
|
||||
## Phase 7 — Polish & Hardening
|
||||
|
||||
**Goal:** Production-ready for real users.
|
||||
|
||||
- [x] CI/CD pipeline (Forgejo Actions → SSH deploy)
|
||||
- [x] Database backups (cron → dev laptop sync)
|
||||
- [ ] Rate limiting on API endpoints
|
||||
- [ ] Graceful WS reconnect with exponential back-off
|
||||
- [ ] React error boundaries
|
||||
- [ ] `GET /users/me/stats` endpoint + profile page
|
||||
- [ ] Accessibility pass (keyboard nav, ARIA on quiz buttons)
|
||||
- [ ] Favicon, page titles, Open Graph meta
|
||||
- [ ] Offsite backup storage (Hetzner Object Storage)
|
||||
- [ ] Monitoring/logging (uptime, centralized logs)
|
||||
- [ ] Valkey for game session store (replace in-memory)
|
||||
|
||||
---
|
||||
|
||||
## Dependency Graph
|
||||
|
||||
```text
|
||||
Phase 0 (Foundation) ✅
|
||||
└── Phase 1 (Vocabulary Data + API) ✅
|
||||
└── Phase 2 (Singleplayer UI) ✅
|
||||
├── Phase 3 (Auth) ✅
|
||||
│ └── Phase 6 (Deployment + CI/CD) ✅
|
||||
└── Phase 4 (Multiplayer Lobby) ✅
|
||||
└── Phase 5 (Multiplayer Game) ✅
|
||||
└── Phase 7 (Hardening)
|
||||
```
|
||||
394
documentation/archive/spec.md
Normal file
394
documentation/archive/spec.md
Normal file
|
|
@ -0,0 +1,394 @@
|
|||
# lila — Project Specification
|
||||
|
||||
> **This document is the single source of truth for the project.**
|
||||
> It is written to be handed to any LLM as context. It contains the project vision, the current MVP scope, the tech stack, the architecture, and the roadmap.
|
||||
|
||||
---
|
||||
|
||||
## 1. Project Overview
|
||||
|
||||
A vocabulary trainer for English–Italian words. The quiz format is Duolingo-style: one word is shown as a prompt, and the user picks the correct translation from four choices (1 correct + 3 distractors of the same part-of-speech). The app supports both singleplayer and real-time multiplayer game modes.
|
||||
|
||||
**The core learning loop:**
|
||||
Show word → pick answer → see result → next word → final score
|
||||
|
||||
The vocabulary data comes from WordNet + the Open Multilingual Wordnet (OMW). A one-time Python script extracts English–Italian noun pairs and seeds the database. The data model is language-pair agnostic by design — adding a new language later requires no schema changes.
|
||||
|
||||
### Core Principles
|
||||
|
||||
- **Minimal but extendable**: working product fast, clean architecture for future growth
|
||||
- **Mobile-first**: touch-friendly Duolingo-like UX
|
||||
- **Type safety end-to-end**: TypeScript + Zod schemas shared between frontend and backend
|
||||
|
||||
---
|
||||
|
||||
## 2. Full Product Vision (Long-Term)
|
||||
|
||||
- Users log in via Google or GitHub (Better Auth)
|
||||
- Singleplayer mode: 10-round quiz, score screen
|
||||
- Multiplayer mode: create a room, share a code, 2–4 players answer simultaneously in real time, live scores, winner screen
|
||||
- 1000+ English–Italian nouns seeded from WordNet
|
||||
|
||||
This is the full vision. The current implementation already covers most of it; remaining items are captured in the roadmap and the Post-MVP ladder below.
|
||||
|
||||
---
|
||||
|
||||
## 3. MVP Scope
|
||||
|
||||
**Goal:** A working, presentable vocabulary trainer that can be shown to real people (singleplayer and multiplayer), with a production deployment.
|
||||
|
||||
### What is IN the MVP
|
||||
|
||||
- Vocabulary data in a PostgreSQL database (already seeded)
|
||||
- REST API that returns quiz terms with distractors
|
||||
- Singleplayer quiz UI: configurable rounds (3 or 10), answer feedback, score screen
|
||||
- Clean, mobile-friendly UI (Tailwind + shadcn/ui)
|
||||
- Global error handler with typed error classes
|
||||
- Unit + integration tests for the API
|
||||
- Authentication via Better Auth (Google + GitHub)
|
||||
- Multiplayer lobby + game over WebSockets
|
||||
- Production deployment (Docker Compose + Caddy + Hetzner) and CI/CD (Forgejo Actions)
|
||||
|
||||
### What is CUT from the MVP
|
||||
|
||||
| Feature | Why cut |
|
||||
| --------------------- | ---------- |
|
||||
| User stats / profiles | Needs auth |
|
||||
|
||||
These are not deleted from the plan — they are deferred. The architecture is already designed to support them. See Section 11 (Post-MVP Ladder).
|
||||
|
||||
---
|
||||
|
||||
## 4. Technology Stack
|
||||
|
||||
The monorepo structure and tooling are already set up. This is the full stack.
|
||||
|
||||
| Layer | Technology | Status |
|
||||
| ------------ | ------------------------------ | ------------------------------------------------------ |
|
||||
| Monorepo | pnpm workspaces | ✅ |
|
||||
| Frontend | React 18, Vite, TypeScript | ✅ |
|
||||
| Routing | TanStack Router | ✅ |
|
||||
| Server state | TanStack Query | ✅ |
|
||||
| Client state | Zustand | ✅ |
|
||||
| Styling | Tailwind CSS + shadcn/ui | ✅ |
|
||||
| Backend | Node.js, Express, TypeScript | ✅ |
|
||||
| Database | PostgreSQL + Drizzle ORM | ✅ |
|
||||
| Validation | Zod (shared schemas) | ✅ |
|
||||
| Testing | Vitest, supertest | ✅ |
|
||||
| Auth | Better Auth (Google + GitHub) | ✅ |
|
||||
| Deployment | Docker Compose, Caddy, Hetzner | ✅ |
|
||||
| CI/CD | Forgejo Actions | ✅ |
|
||||
| Realtime | WebSockets (`ws` library) | ✅ |
|
||||
| Cache | Valkey | ⚠️ optional (used locally; production/state hardening) |
|
||||
|
||||
---
|
||||
|
||||
## 5. Repository Structure
|
||||
|
||||
```text
|
||||
lila/
|
||||
├── .forgejo/
|
||||
│ └── workflows/
|
||||
│ └── deploy.yml — CI/CD pipeline (build, push, deploy)
|
||||
├── apps/
|
||||
│ ├── api/
|
||||
│ │ └── src/
|
||||
│ │ ├── app.ts — createApp() factory, CORS, auth handler, error middleware
|
||||
│ │ ├── server.ts — starts server on PORT
|
||||
│ │ ├── errors/
|
||||
│ │ │ └── AppError.ts — AppError, ValidationError, NotFoundError
|
||||
│ │ ├── lib/
|
||||
│ │ │ └── auth.ts — Better Auth config (Google + GitHub providers)
|
||||
│ │ ├── middleware/
|
||||
│ │ │ ├── authMiddleware.ts — session validation for protected routes
|
||||
│ │ │ └── errorHandler.ts — central error middleware
|
||||
│ │ ├── routes/
|
||||
│ │ │ ├── apiRouter.ts — mounts /health and /game routers
|
||||
│ │ │ ├── gameRouter.ts — POST /start, POST /answer
|
||||
│ │ │ └── healthRouter.ts
|
||||
│ │ ├── controllers/
|
||||
│ │ │ └── gameController.ts — validates input, calls service, sends response
|
||||
│ │ ├── services/
|
||||
│ │ │ ├── gameService.ts — builds quiz sessions, evaluates answers
|
||||
│ │ │ └── gameService.test.ts — unit tests (mocked DB)
|
||||
│ │ └── gameSessionStore/
|
||||
│ │ ├── GameSessionStore.ts — interface (async, Valkey-ready)
|
||||
│ │ ├── InMemoryGameSessionStore.ts
|
||||
│ │ └── index.ts
|
||||
│ └── web/
|
||||
│ ├── Dockerfile — multi-stage: dev + production (nginx:alpine)
|
||||
│ ├── nginx.conf — SPA fallback routing
|
||||
│ └── src/
|
||||
│ ├── routes/
|
||||
│ │ ├── index.tsx — landing page
|
||||
│ │ ├── play.tsx — the quiz
|
||||
│ │ ├── login.tsx — Google + GitHub login buttons
|
||||
│ │ ├── about.tsx
|
||||
│ │ └── __root.tsx
|
||||
│ ├── lib/
|
||||
│ │ └── auth-client.ts — Better Auth React client
|
||||
│ ├── components/
|
||||
│ │ └── game/
|
||||
│ │ ├── GameSetup.tsx — settings UI
|
||||
│ │ ├── QuestionCard.tsx — prompt + 4 options
|
||||
│ │ ├── OptionButton.tsx — idle / correct / wrong states
|
||||
│ │ └── ScoreScreen.tsx — final score + play again
|
||||
│ └── main.tsx
|
||||
├── packages/
|
||||
│ ├── shared/
|
||||
│ │ └── src/
|
||||
│ │ ├── constants.ts — SUPPORTED_POS, DIFFICULTY_LEVELS, etc.
|
||||
│ │ ├── schemas/game.ts — Zod schemas for all game types
|
||||
│ │ └── index.ts
|
||||
│ └── db/
|
||||
│ ├── drizzle/ — migration SQL files
|
||||
│ └── src/
|
||||
│ ├── db/schema.ts — Drizzle schema (terms, translations, auth tables)
|
||||
│ ├── models/termModel.ts — getGameTerms(), getDistractors()
|
||||
│ ├── seeding-datafiles.ts — seeds terms + translations from JSON
|
||||
│ ├── seeding-cefr-levels.ts — enriches translations with CEFR data
|
||||
│ ├── generating-deck.ts — builds curated decks
|
||||
│ └── index.ts
|
||||
├── scripts/ — Python extraction/comparison/merge scripts
|
||||
├── documentation/ — project docs
|
||||
├── docker-compose.yml — local dev stack
|
||||
├── Caddyfile — reverse proxy routing
|
||||
└── pnpm-workspace.yaml
|
||||
```
|
||||
|
||||
`packages/shared` is the contract between frontend and backend. All request/response shapes are defined there as Zod schemas — never duplicated.
|
||||
|
||||
---
|
||||
|
||||
## 6. Architecture
|
||||
|
||||
### The Layered Architecture
|
||||
|
||||
```text
|
||||
HTTP Request
|
||||
↓
|
||||
Router — maps URL + HTTP method to a controller
|
||||
↓
|
||||
Controller — handles HTTP only: validates input, calls service, sends response
|
||||
↓
|
||||
Service — business logic only: no HTTP, no direct DB access
|
||||
↓
|
||||
Model — database queries only: no business logic
|
||||
↓
|
||||
Database
|
||||
```
|
||||
|
||||
**The rule:** each layer only talks to the layer directly below it. A controller never touches the database. A service never reads `req.body`. A model never knows what a quiz is.
|
||||
|
||||
### Monorepo Package Responsibilities
|
||||
|
||||
| Package | Owns |
|
||||
| ----------------- | -------------------------------------------------------- |
|
||||
| `packages/shared` | Zod schemas, constants, derived TypeScript types |
|
||||
| `packages/db` | Drizzle schema, DB connection, all model/query functions |
|
||||
| `apps/api` | Router, controllers, services, error handling |
|
||||
| `apps/web` | React frontend, consumes types from shared |
|
||||
|
||||
**Key principle:** all database code lives in `packages/db`. `apps/api` never imports `drizzle-orm` for queries — it only calls functions exported from `packages/db`.
|
||||
|
||||
### Production Infrastructure
|
||||
|
||||
```text
|
||||
Internet → Caddy (HTTPS termination)
|
||||
├── lilastudy.com → web container (nginx, static files)
|
||||
├── api.lilastudy.com → api container (Express, port 3000)
|
||||
└── git.lilastudy.com → forgejo container (git + registry, port 3000)
|
||||
|
||||
SSH (port 2222) → forgejo container (git push/pull)
|
||||
```
|
||||
|
||||
All containers communicate over an internal Docker network. Only Caddy (80/443) and Forgejo SSH (2222) are exposed to the internet.
|
||||
|
||||
---
|
||||
|
||||
## 7. Data Model (Current State)
|
||||
|
||||
Words are modelled as language-neutral concepts (terms) separate from learning curricula (decks). Adding a new language pair requires no schema changes — only new rows in `translations`, `decks`.
|
||||
|
||||
**Core tables:** `terms`, `translations`, `term_glosses`, `decks`, `deck_terms`, `topics`, `term_topics`
|
||||
|
||||
**Auth tables (managed by Better Auth):** `user`, `session`, `account`, `verification`
|
||||
|
||||
Key columns on `terms`: `id` (uuid), `pos` (CHECK-constrained), `source`, `source_id` (unique pair for idempotent imports)
|
||||
|
||||
Key columns on `translations`: `id`, `term_id` (FK), `language_code` (CHECK-constrained), `text`, `cefr_level` (nullable varchar(2), CHECK A1–C2)
|
||||
|
||||
Deck model uses `source_language` + `validated_languages` array — one deck serves multiple target languages. Decks are frequency tiers (e.g. `en-core-1000`), not POS splits.
|
||||
|
||||
Full schema is in `packages/db/src/db/schema.ts`.
|
||||
|
||||
---
|
||||
|
||||
## 8. API
|
||||
|
||||
### Endpoints
|
||||
|
||||
```text
|
||||
POST /api/v1/game/start GameRequest → GameSession (requires auth)
|
||||
POST /api/v1/game/answer AnswerSubmission → AnswerResult (requires auth)
|
||||
GET /api/v1/health Health check (public)
|
||||
ALL /api/auth/* Better Auth handlers (public)
|
||||
```
|
||||
|
||||
### Schemas (packages/shared)
|
||||
|
||||
**GameRequest:** `{ source_language, target_language, pos, difficulty, rounds }`
|
||||
**GameSession:** `{ sessionId: uuid, questions: GameQuestion[] }`
|
||||
**GameQuestion:** `{ questionId: uuid, prompt: string, gloss: string | null, options: AnswerOption[4] }`
|
||||
**AnswerOption:** `{ optionId: number (0-3), text: string }`
|
||||
**AnswerSubmission:** `{ sessionId: uuid, questionId: uuid, selectedOptionId: number (0-3) }`
|
||||
**AnswerResult:** `{ questionId: uuid, isCorrect: boolean, correctOptionId: number (0-3), selectedOptionId: number (0-3) }`
|
||||
|
||||
### Error Handling
|
||||
|
||||
Typed error classes (`AppError` base, `ValidationError` 400, `NotFoundError` 404) with central error middleware. Controllers validate with `safeParse`, throw on failure, and call `next(error)` in the catch. The middleware maps `AppError` instances to HTTP status codes; unknown errors return 500.
|
||||
|
||||
### Key Design Rules
|
||||
|
||||
- Server-side answer evaluation: the correct answer is never sent to the frontend
|
||||
- `POST` not `GET` for game start (configuration in request body)
|
||||
- `safeParse` over `parse` (clean 400s, not raw Zod 500s)
|
||||
- Session state stored in `GameSessionStore` (in-memory now, Valkey later)
|
||||
|
||||
---
|
||||
|
||||
## 9. Game Mechanics
|
||||
|
||||
- **Format**: source-language word prompt + 4 target-language choices
|
||||
- **Distractors**: same POS, same difficulty, server-side, never the correct answer, never repeated within a session
|
||||
- **Session length**: 3 or 10 questions (configurable)
|
||||
- **Scoring**: +1 per correct answer (no speed bonus for MVP)
|
||||
- **Timer**: none in singleplayer MVP
|
||||
- **Auth required**: users must log in via Google or GitHub
|
||||
- **Submit-before-send**: user selects, then confirms (prevents misclicks)
|
||||
|
||||
---
|
||||
|
||||
## 10. Working Methodology
|
||||
|
||||
This project is a learning exercise. The goal is to understand the code, not just to ship it.
|
||||
|
||||
### How to use an LLM for help
|
||||
|
||||
1. Paste this document as context
|
||||
2. Describe what you're working on and what you're stuck on
|
||||
3. Ask for hints, not solutions
|
||||
|
||||
### Refactoring workflow
|
||||
|
||||
After completing a task: share the code, ask what to refactor and why. The LLM should explain the concept, not write the implementation.
|
||||
|
||||
---
|
||||
|
||||
## 11. Post-MVP Ladder
|
||||
|
||||
<<<<<<< HEAD
|
||||
| Phase | What it adds | Status |
|
||||
| ----------------- | ------------------------------------------------------------------------------- | ------ |
|
||||
| Auth | Better Auth (Google + GitHub), embedded in Express API, user rows in DB | ✅ |
|
||||
| Deployment | Docker Compose, Caddy, Forgejo, CI/CD, Hetzner VPS | ✅ |
|
||||
| Hardening (partial) | CI/CD pipeline, DB backups | ✅ |
|
||||
| User Stats | Games played, score history, profile page | ❌ |
|
||||
| Multiplayer Lobby | Room creation, join by code, WebSocket connection | ❌ |
|
||||
| Multiplayer Game | Simultaneous answers, server timer, live scores, winner screen | ❌ |
|
||||
| Hardening (rest) | Rate limiting, error boundaries, monitoring, accessibility | ❌ |
|
||||
=======
|
||||
| Phase | What it adds | Status |
|
||||
| ------------------- | ----------------------------------------------------------------------- | ------ |
|
||||
| Auth | Better Auth (Google + GitHub), embedded in Express API, user rows in DB | ✅ |
|
||||
| Deployment | Docker Compose, Caddy, Forgejo, CI/CD, Hetzner VPS | ✅ |
|
||||
| Hardening (partial) | CI/CD pipeline, DB backups | ✅ |
|
||||
| User Stats | Games played, score history, profile page | ❌ |
|
||||
| Multiplayer Lobby | Room creation, join by code, WebSocket connection | ✅ |
|
||||
| Multiplayer Game | Simultaneous answers, server timer, live scores, winner screen | ✅ |
|
||||
| Hardening (rest) | Rate limiting, error boundaries, monitoring, accessibility | ❌ |
|
||||
|
||||
> > > > > > > dev
|
||||
|
||||
### Future Data Model Extensions (deferred, additive)
|
||||
|
||||
- `noun_forms` — gender, singular, plural, articles per language
|
||||
- `verb_forms` — conjugation tables per language
|
||||
- `term_pronunciations` — IPA and audio URLs per language
|
||||
- `user_decks` — which decks a user is studying
|
||||
- `user_term_progress` — spaced repetition state per user/term/language
|
||||
- `quiz_answers` — history log for stats
|
||||
|
||||
All are new tables referencing existing `terms` rows via FK. No existing schema changes required.
|
||||
|
||||
### Multiplayer Architecture (current + deferred)
|
||||
|
||||
**Implemented now:**
|
||||
|
||||
- WebSocket protocol uses the `ws` library with a Zod discriminated union for message types (defined in `packages/shared`)
|
||||
- Room model uses human-readable codes (no matchmaking queue)
|
||||
- Lobby flow (create/join/leave) is real-time over WS, backed by PostgreSQL for durable membership/state
|
||||
- Multiplayer game flow is real-time: host starts, all players see the same question, answers are collected simultaneously, with a server-enforced 15s timer and live scoring
|
||||
- WebSocket connections are authenticated (Better Auth session validation on upgrade)
|
||||
|
||||
**Deferred / hardening:**
|
||||
|
||||
- Valkey-backed ephemeral state (room/game/session store) where in-memory state becomes a bottleneck
|
||||
- Graceful reconnect/resume flows and more robust failure handling (tracked in Phase 7)
|
||||
|
||||
### Infrastructure (current)
|
||||
|
||||
- `lilastudy.com` → React frontend (nginx serving static files)
|
||||
- `api.lilastudy.com` → Express API + Better Auth
|
||||
- `git.lilastudy.com` → Forgejo (git server + container registry)
|
||||
- Docker Compose with Caddy for automatic HTTPS via Let's Encrypt
|
||||
- CI/CD via Forgejo Actions (build on push to main, deploy via SSH)
|
||||
- Daily DB backups with cron, synced to dev laptop
|
||||
|
||||
See `deployment.md` for full infrastructure documentation.
|
||||
|
||||
---
|
||||
|
||||
## 12. Definition of Done (Current Baseline)
|
||||
|
||||
- [x] API returns quiz terms with correct distractors
|
||||
- [x] User can complete a quiz without errors
|
||||
- [x] Score screen shows final result and a play-again option
|
||||
- [x] App is usable on a mobile screen
|
||||
- [x] No hardcoded data — everything comes from the database
|
||||
- [x] Global error handler with typed error classes
|
||||
- [x] Unit + integration tests for API
|
||||
- [x] Auth works end-to-end (Google + GitHub via Better Auth)
|
||||
- [x] Multiplayer works end-to-end (lobby + real-time game over WebSockets)
|
||||
- [x] Production deployment is live behind HTTPS (Caddy) with CI/CD deploys via Forgejo Actions
|
||||
|
||||
---
|
||||
|
||||
## 13. Roadmap
|
||||
|
||||
See `roadmap.md` for the full roadmap with task-level checkboxes.
|
||||
|
||||
### Dependency Graph
|
||||
|
||||
```text
|
||||
Phase 0 (Foundation) ✅
|
||||
└── Phase 1 (Vocabulary Data + API) ✅
|
||||
└── Phase 2 (Singleplayer UI) ✅
|
||||
├── Phase 3 (Auth) ✅
|
||||
│ └── Phase 6 (Deployment + CI/CD) ✅
|
||||
└── Phase 4 (Multiplayer Lobby) ✅
|
||||
└── Phase 5 (Multiplayer Game) ✅
|
||||
└── Phase 7 (Hardening)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 14. Game Flow (Future)
|
||||
|
||||
Singleplayer: choose direction (en→it or it→en) → top-level category → part of speech → difficulty (A1–C2) → round count → game starts.
|
||||
|
||||
**Top-level categories (post-MVP):**
|
||||
|
||||
- **Grammar** — practice nouns, verb conjugations, etc.
|
||||
- **Media** — practice vocabulary from specific books, films, songs, etc.
|
||||
- **Thematic** — animals, kitchen, etc. (requires category metadata research)
|
||||
Loading…
Add table
Add a link
Reference in a new issue