Compare commits
No commits in common. "d033a08d877132d625e27a987efff106a1ee6cb6" and "4f514a4e9999d5cd7d84616adefc3f913a237b1b" have entirely different histories.
d033a08d87
...
4f514a4e99
3 changed files with 18 additions and 195 deletions
169
README.md
169
README.md
|
|
@ -1,170 +1 @@
|
||||||
# lila
|
# lila
|
||||||
|
|
||||||
**Learn words. Beat friends.**
|
|
||||||
|
|
||||||
lila is a vocabulary trainer built around a Duolingo-style quiz loop: a word appears in one language, you pick the correct translation from four choices. It supports singleplayer and real-time multiplayer, and is designed to work across multiple language pairs without schema changes.
|
|
||||||
|
|
||||||
Live at [lilastudy.com](https://lilastudy.com).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Stack
|
|
||||||
|
|
||||||
| Layer | Technology |
|
|
||||||
|---|---|
|
|
||||||
| Monorepo | pnpm workspaces |
|
|
||||||
| Frontend | React 18, Vite, TypeScript |
|
|
||||||
| Routing | TanStack Router |
|
|
||||||
| Server state | TanStack Query |
|
|
||||||
| Styling | Tailwind CSS |
|
|
||||||
| Backend | Node.js, Express, TypeScript |
|
|
||||||
| Database | PostgreSQL + Drizzle ORM |
|
|
||||||
| Validation | Zod (shared schemas) |
|
|
||||||
| Auth | Better Auth (Google + GitHub) |
|
|
||||||
| Realtime | WebSockets (`ws` library) |
|
|
||||||
| Testing | Vitest, supertest |
|
|
||||||
| Deployment | Docker Compose, Caddy, Hetzner VPS |
|
|
||||||
| CI/CD | Forgejo Actions |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Repository Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
lila/
|
|
||||||
├── apps/
|
|
||||||
│ ├── api/ — Express backend
|
|
||||||
│ └── web/ — React frontend
|
|
||||||
├── packages/
|
|
||||||
│ ├── shared/ — Zod schemas and types shared between frontend and backend
|
|
||||||
│ └── db/ — Drizzle schema, migrations, models, seeding scripts
|
|
||||||
├── scripts/ — Python scripts for vocabulary data extraction
|
|
||||||
└── documentation/ — Project docs
|
|
||||||
```
|
|
||||||
|
|
||||||
`packages/shared` is the contract between frontend and backend. All request/response shapes are defined there as Zod schemas and never duplicated.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
Requests flow through a strict layered architecture:
|
|
||||||
|
|
||||||
```
|
|
||||||
HTTP Request → Router → Controller → Service → Model → Database
|
|
||||||
```
|
|
||||||
|
|
||||||
Each layer only talks to the layer directly below it. Controllers handle HTTP only. Services contain business logic only. Models contain database queries only. All database code lives in `packages/db` — the API never imports Drizzle directly for queries.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Data Model
|
|
||||||
|
|
||||||
Words are modelled as language-neutral concepts (`terms`) with per-language `translations`. Adding a new language requires no schema changes — only new rows. CEFR levels (A1–C2) are stored per translation for difficulty filtering.
|
|
||||||
|
|
||||||
Core tables: `terms`, `translations`, `term_glosses`, `decks`, `deck_terms`
|
|
||||||
Auth tables (managed by Better Auth): `user`, `session`, `account`, `verification`
|
|
||||||
|
|
||||||
Vocabulary data is sourced from WordNet and the Open Multilingual Wordnet (OMW).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
```
|
|
||||||
POST /api/v1/game/start — start a quiz session (auth required)
|
|
||||||
POST /api/v1/game/answer — submit an answer (auth required)
|
|
||||||
GET /api/v1/health — health check (public)
|
|
||||||
ALL /api/auth/* — Better Auth handlers (public)
|
|
||||||
```
|
|
||||||
|
|
||||||
The correct answer is never sent to the frontend — all evaluation happens server-side.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Multiplayer
|
|
||||||
|
|
||||||
Rooms are created via REST, then managed over WebSockets. Messages are typed via a Zod discriminated union. The host starts the game; all players answer simultaneously with a 15-second server-enforced timer. Room state is held in-memory (Valkey deferred).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Infrastructure
|
|
||||||
|
|
||||||
```
|
|
||||||
Internet → Caddy (HTTPS)
|
|
||||||
├── lilastudy.com → web (nginx, static files)
|
|
||||||
├── api.lilastudy.com → api (Express)
|
|
||||||
└── git.lilastudy.com → Forgejo (git + registry)
|
|
||||||
```
|
|
||||||
|
|
||||||
Deployed on a Hetzner VPS (Debian 13, ARM64). Images are built cross-compiled for ARM64 and pushed to the Forgejo container registry. CI/CD runs via Forgejo Actions on push to `main`. Daily database backups are synced to the dev laptop via rsync.
|
|
||||||
|
|
||||||
See `documentation/deployment.md` for the full infrastructure setup.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Local Development
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
- Node.js 20+
|
|
||||||
- pnpm 9+
|
|
||||||
- Docker + Docker Compose
|
|
||||||
|
|
||||||
### Setup
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Install dependencies
|
|
||||||
pnpm install
|
|
||||||
|
|
||||||
# Create your local env file (used by docker compose + the API)
|
|
||||||
cp .env.example .env
|
|
||||||
|
|
||||||
# Start local services (PostgreSQL, Valkey)
|
|
||||||
docker compose up -d
|
|
||||||
|
|
||||||
# Build shared packages
|
|
||||||
pnpm --filter @lila/shared build
|
|
||||||
pnpm --filter @lila/db build
|
|
||||||
|
|
||||||
# Run migrations and seed data
|
|
||||||
pnpm --filter @lila/db migrate
|
|
||||||
pnpm --filter @lila/db seed
|
|
||||||
|
|
||||||
# Start dev servers
|
|
||||||
pnpm dev
|
|
||||||
```
|
|
||||||
|
|
||||||
The API runs on `http://localhost:3000` and the frontend on `http://localhost:5173`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# All tests
|
|
||||||
pnpm test
|
|
||||||
|
|
||||||
# API only
|
|
||||||
pnpm --filter api test
|
|
||||||
|
|
||||||
# Frontend only
|
|
||||||
pnpm --filter web test
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Roadmap
|
|
||||||
|
|
||||||
| Phase | Description | Status |
|
|
||||||
|---|---|---|
|
|
||||||
| 0 | Foundation — monorepo, tooling, dev environment | ✅ |
|
|
||||||
| 1 | Vocabulary data pipeline + REST API | ✅ |
|
|
||||||
| 2 | Singleplayer quiz UI | ✅ |
|
|
||||||
| 3 | Auth (Google + GitHub) | ✅ |
|
|
||||||
| 4 | Multiplayer lobby (WebSockets) | ✅ |
|
|
||||||
| 5 | Multiplayer game (real-time, server timer) | ✅ |
|
|
||||||
| 6 | Production deployment + CI/CD | ✅ |
|
|
||||||
| 7 | Hardening (rate limiting, error boundaries, monitoring, accessibility) | 🔄 |
|
|
||||||
|
|
||||||
See `documentation/roadmap.md` for task-level detail.
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ Each phase produces a working increment. Nothing is built speculatively.
|
||||||
- [x] Configure Drizzle ORM + connection to local PostgreSQL
|
- [x] Configure Drizzle ORM + connection to local PostgreSQL
|
||||||
- [x] Write first migration (empty — validates the pipeline works)
|
- [x] Write first migration (empty — validates the pipeline works)
|
||||||
- [x] `docker-compose.yml` for local dev: `api`, `web`, `postgres`, `valkey`
|
- [x] `docker-compose.yml` for local dev: `api`, `web`, `postgres`, `valkey`
|
||||||
- [x] Root `.env.example` for local dev (`docker-compose.yml` + API)
|
- [x] `.env.example` files for `apps/api` and `apps/web`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
## 1. Project Overview
|
## 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.
|
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 long-term vision is a multiplayer competitive game, but the MVP is a polished singleplayer experience.
|
||||||
|
|
||||||
**The core learning loop:**
|
**The core learning loop:**
|
||||||
Show word → pick answer → see result → next word → final score
|
Show word → pick answer → see result → next word → final score
|
||||||
|
|
@ -29,13 +29,13 @@ The vocabulary data comes from WordNet + the Open Multilingual Wordnet (OMW). A
|
||||||
- Multiplayer mode: create a room, share a code, 2–4 players answer simultaneously in real time, live scores, winner 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
|
- 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.
|
This is the full vision. The MVP deliberately ignores most of it.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. MVP Scope
|
## 3. MVP Scope
|
||||||
|
|
||||||
**Goal:** A working, presentable vocabulary trainer that can be shown to real people (singleplayer and multiplayer), with a production deployment.
|
**Goal:** A working, presentable singleplayer quiz that can be shown to real people.
|
||||||
|
|
||||||
### What is IN the MVP
|
### What is IN the MVP
|
||||||
|
|
||||||
|
|
@ -45,14 +45,16 @@ This is the full vision. The current implementation already covers most of it; r
|
||||||
- Clean, mobile-friendly UI (Tailwind + shadcn/ui)
|
- Clean, mobile-friendly UI (Tailwind + shadcn/ui)
|
||||||
- Global error handler with typed error classes
|
- Global error handler with typed error classes
|
||||||
- Unit + integration tests for the API
|
- Unit + integration tests for the API
|
||||||
- Authentication via Better Auth (Google + GitHub)
|
- Local dev only (no deployment for MVP)
|
||||||
- Multiplayer lobby + game over WebSockets
|
|
||||||
- Production deployment (Docker Compose + Caddy + Hetzner) and CI/CD (Forgejo Actions)
|
|
||||||
|
|
||||||
### What is CUT from the MVP
|
### What is CUT from the MVP
|
||||||
|
|
||||||
| Feature | Why cut |
|
| Feature | Why cut |
|
||||||
| ------------------------------- | -------------------------------------- |
|
| ------------------------------- | -------------------------------------- |
|
||||||
|
| Authentication (Better Auth) | No user accounts needed for a demo |
|
||||||
|
| Multiplayer (WebSockets, rooms) | Core quiz works without it |
|
||||||
|
| Valkey / Redis cache | Only needed for multiplayer room state |
|
||||||
|
| Deployment to Hetzner | Ship to people locally first |
|
||||||
| User stats / profiles | Needs auth |
|
| 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).
|
These are not deleted from the plan — they are deferred. The architecture is already designed to support them. See Section 11 (Post-MVP Ladder).
|
||||||
|
|
@ -79,14 +81,14 @@ The monorepo structure and tooling are already set up. This is the full stack.
|
||||||
| Deployment | Docker Compose, Caddy, Hetzner | ✅ |
|
| Deployment | Docker Compose, Caddy, Hetzner | ✅ |
|
||||||
| CI/CD | Forgejo Actions | ✅ |
|
| CI/CD | Forgejo Actions | ✅ |
|
||||||
| Realtime | WebSockets (`ws` library) | ✅ |
|
| Realtime | WebSockets (`ws` library) | ✅ |
|
||||||
| Cache | Valkey | ⚠️ optional (used locally; production/state hardening) |
|
| Cache | Valkey | ❌ post-MVP |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. Repository Structure
|
## 5. Repository Structure
|
||||||
|
|
||||||
```text
|
```text
|
||||||
lila/
|
vocab-trainer/
|
||||||
├── .forgejo/
|
├── .forgejo/
|
||||||
│ └── workflows/
|
│ └── workflows/
|
||||||
│ └── deploy.yml — CI/CD pipeline (build, push, deploy)
|
│ └── deploy.yml — CI/CD pipeline (build, push, deploy)
|
||||||
|
|
@ -152,6 +154,7 @@ lila/
|
||||||
├── scripts/ — Python extraction/comparison/merge scripts
|
├── scripts/ — Python extraction/comparison/merge scripts
|
||||||
├── documentation/ — project docs
|
├── documentation/ — project docs
|
||||||
├── docker-compose.yml — local dev stack
|
├── docker-compose.yml — local dev stack
|
||||||
|
├── docker-compose.prod.yml — production config reference
|
||||||
├── Caddyfile — reverse proxy routing
|
├── Caddyfile — reverse proxy routing
|
||||||
└── pnpm-workspace.yaml
|
└── pnpm-workspace.yaml
|
||||||
```
|
```
|
||||||
|
|
@ -308,20 +311,12 @@ After completing a task: share the code, ask what to refactor and why. The LLM s
|
||||||
|
|
||||||
All are new tables referencing existing `terms` rows via FK. No existing schema changes required.
|
All are new tables referencing existing `terms` rows via FK. No existing schema changes required.
|
||||||
|
|
||||||
### Multiplayer Architecture (current + deferred)
|
### Multiplayer Architecture (deferred)
|
||||||
|
|
||||||
**Implemented now:**
|
- WebSocket protocol: `ws` library, Zod discriminated union for message types
|
||||||
|
- Room model: human-readable codes (e.g. `WOLF-42`), not matchmaking queue
|
||||||
- WebSocket protocol uses the `ws` library with a Zod discriminated union for message types (defined in `packages/shared`)
|
- Game mechanic: simultaneous answers, 15-second server timer, all players see same question
|
||||||
- Room model uses human-readable codes (no matchmaking queue)
|
- Valkey for ephemeral room state, PostgreSQL for durable records
|
||||||
- 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)
|
### Infrastructure (current)
|
||||||
|
|
||||||
|
|
@ -336,7 +331,7 @@ See `deployment.md` for full infrastructure documentation.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 12. Definition of Done (Current Baseline)
|
## 12. Definition of Done (MVP)
|
||||||
|
|
||||||
- [x] API returns quiz terms with correct distractors
|
- [x] API returns quiz terms with correct distractors
|
||||||
- [x] User can complete a quiz without errors
|
- [x] User can complete a quiz without errors
|
||||||
|
|
@ -345,9 +340,6 @@ See `deployment.md` for full infrastructure documentation.
|
||||||
- [x] No hardcoded data — everything comes from the database
|
- [x] No hardcoded data — everything comes from the database
|
||||||
- [x] Global error handler with typed error classes
|
- [x] Global error handler with typed error classes
|
||||||
- [x] Unit + integration tests for API
|
- [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
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue