# 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

```tree
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:

```text
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

```text
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

```tree
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.
