Compare commits

...

3 commits

Author SHA1 Message Date
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
66eddb9a2a creating backlog with issues 2026-04-22 21:09:24 +02:00
lila
9a3376cdcc updating docs 2026-04-21 15:40:26 +02:00
11 changed files with 1369 additions and 2 deletions

View file

@ -39,6 +39,7 @@ COPY packages/db/package.json ./packages/db/
COPY --from=builder /app/apps/api/dist ./apps/api/dist
COPY --from=builder /app/packages/shared/dist ./packages/shared/dist
COPY --from=builder /app/packages/db/dist ./packages/db/dist
COPY --from=builder /app/packages/db/drizzle ./packages/db/drizzle
RUN pnpm install --frozen-lockfile --prod
EXPOSE 3000
CMD ["node", "apps/api/dist/src/server.js"]
CMD ["sh", "-c", "node packages/db/dist/src/migrate.js && node apps/api/dist/src/server.js"]

View file

@ -31,6 +31,7 @@ services:
api:
container_name: lila-api
user: "${UID}:${GID}"
build:
context: .
dockerfile: ./apps/api/Dockerfile
@ -59,6 +60,7 @@ services:
web:
container_name: lila-web
user: "${UID}:${GID}"
build:
context: .
dockerfile: ./apps/web/Dockerfile

122
documentation/backlog.md Normal file
View file

@ -0,0 +1,122 @@
# lila — backlog
Labels: `[feature]` `[infra]` `[security]` `[ux]` `[debt]`
---
## now
Things that are actively in progress or should be picked up immediately. Mostly operational risk and the remaining phase 7 hardening work.
- **Migrations in the deploy pipeline** `[infra]` `[debt]`
Run `drizzle migrate` as a step in the CI/CD pipeline before the API container is restarted. Deploying code before schema is applied causes crashes. See `deployment.md` — deploy order is currently documented but not enforced.
- **Rate limiting on API endpoints** `[security]`
At minimum: auth endpoints (brute force prevention) and game endpoints (spam prevention). Consider `express-rate-limit`.
- **404 and redirect handling** `[ux]`
Unknown routes return raw errors. Add a catch-all route on the frontend for client-side 404s. Consider a Caddy fallback for unrecognized subdomains.
- **React error boundaries** `[ux]`
Catch and display runtime errors gracefully instead of crashing the entire app.
- **Pin dependencies in package.json** `[debt]` `[infra]`
Unpinned deps in a CI/CD pipeline are a real risk. Pin all versions to exact values to prevent unexpected breakage on build.
- **Docker credential helper** `[debt]` `[infra]`
Credentials are stored unencrypted in `~/.docker/config.json`. Set up a credential helper. See https://docs.docker.com/go/credential-store/
- **Google OAuth publishing** `[infra]`
Only test users can currently log in via Google. Publish the OAuth consent screen so any Google user can sign in — requires branding verification in Google Cloud Console.
- **Hetzner domain migration check** `[infra]`
Verify whether the lilastudy.com domain needs to be migrated following a Hetzner DNS change. Check Hetzner dashboard for any pending migration notice.
- **Security headers with helmet** `[security]`
Add helmet middleware to set secure HTTP response headers. One-liner: app.use(helmet()). Covers headers like X-Content-Type-Options, X-Frame-Options, and Content-Security-Policy.
---
## next
Clearly planned work, not yet started. No hard ordering — sequence based on what unblocks real users first.
- **Guest / try-now flow** `[feature]`
Allow users to play a quiz without signing in so they can see what the app offers before creating an account. Make auth middleware optional on game routes, add a "Try without account" button on the landing/login page.
- **Favicon, page titles, Open Graph meta** `[ux]`
Add favicon, set proper per-route page titles, add Open Graph meta tags for link previews.
- **Accessibility pass** `[ux]`
Keyboard navigation for quiz buttons, ARIA labels on interactive elements, focus management during quiz flow.
- **Monitoring and logging** `[infra]`
Uptime monitoring and centralized logging on the VPS. Options: `chkrootkit`/`rkhunter` for security, `logwatch`/`monit` for daily summaries.
- **Offsite backup storage** `[infra]`
Database backups currently live on the same VPS. Add offsite copies to Hetzner Object Storage or an S3-compatible service to protect against VPS failure.
- **Valkey for game session store** `[infra]`
Add Valkey to the production Docker stack. Implement `ValkeyGameSessionStore` against the existing `GameSessionStore` interface. Required before multiplayer scales.
- **User stats endpoint + profile page** `[feature]`
`GET /users/me/stats` returning games played, score history, etc. Frontend profile page displaying the stats.
- **Admin dashboard** `[feature]`
User management, overview of words and languages, and per-term stats. Not urgent but has real operational value once real users are present.
- **Email + password login** `[feature]`
Traditional email/password auth as an alternative to social login. Configure via Better Auth.
- **Apple login** `[feature]`
Add Apple as a social login option via Better Auth. Requires Apple Developer account and Sign in with Apple configuration.
- **Graceful WS reconnect** `[infra]`
Handle WebSocket disconnections gracefully. Reconnect with exponential back-off. Restore game state on reconnection if a game is still in progress.
- **Configurable game settings in multiplayer lobby** `[feature]`
Game settings (mode, round count, timer duration, target score) are currently hardcoded. The host should be able to configure these when creating a lobby. Settings should be stored in the settings jsonb column on the lobbies table and passed through to the game service at start.
---
## later
Directionally right, timing is unclear. Revisit when the next/now work is done.
- **Game modes** `[feature]`
Five modes are designed in `game_modes.md` — TV Quiz Show, Race to the Top, Chain Link, Elimination Round, Cooperative Challenge. The lobby infrastructure is mode-agnostic; each mode adds game logic only. First mode to implement is TBD. This is effectively a new phase.
- **Single Player Extended** `[feature]`
Expanded singleplayer flow. Possible directions: longer sessions with increasing difficulty, streak bonuses, mixed POS/language rounds, progress tracking across sessions, timed challenge mode.
- **Users in a separate database** `[infra]`
Architectural separation of auth/user data from vocabulary and game data. No immediate benefit — revisit after hardening is complete and user growth justifies the complexity.
- **Modern env management** `[debt]`
Replace `.env` files with a more robust approach (e.g. `dotenvx`, `infisical`). Current setup works but is error-prone and not versioned.
- **Reorganize datafiles and wordlists** `[debt]`
The current layout of `data-sources/`, `scripts/datafiles/`, `scripts/data-sources/`, and `packages/db/src/data/` is confusing with overlapping content. Consolidate into a clear structure.
- **Resolve eslint peer dependency warning** `[debt]`
`eslint-plugin-react-hooks 7.0.1` expects `eslint ^3.0.0^9.0.0` but found `10.0.3`. Low impact but worth cleaning up when nearby.
- **husky + lint-staged** `[debt]`
Set up husky and lint-staged to run linting and formatting checks before every commit. Prevents CI failures from formatting or lint issues that slipped through locally.
- **OpenAPI documentation for REST endpoints** `[feature]`
Document the API surface using OpenAPI/Swagger. Covers all REST endpoints with request/response shapes. Useful groundwork for the admin dashboard and any future contributors.
---
## changelog
Shipped milestones, newest first.
- **04 - 2026 — Phase 6: Production deployment** — Hetzner VPS, Caddy HTTPS, Forgejo CI/CD, daily DB backups, cross-subdomain auth
- **04 - 2026 — Phase 5: Multiplayer game** — real-time simultaneous play, 15s server timer, live scoring, winner screen
- **04 - 2026 — Phase 4: Multiplayer lobby** — WebSocket server, lobby create/join, real-time player list
- **04 - 2026 — Phase 3: Auth** — Better Auth, Google + GitHub social login, session middleware, auth guard
- **04 - 2026 — Phase 2: Singleplayer UI** — full quiz loop in browser, game setup, question card, score screen
- **04 - 2026 — Phase 1: Vocabulary data + API** — WordNet/OMW data pipeline, CEFR enrichment, game session endpoints
- **04 - 2026 — Phase 0: Foundation** — pnpm monorepo, TypeScript, ESLint, Vitest, Drizzle, Docker Compose

View file

@ -2,6 +2,7 @@
## 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

View file

@ -0,0 +1,3 @@
CREATE TABLE "dummy" (
"id" serial PRIMARY KEY NOT NULL
);

File diff suppressed because it is too large Load diff

View file

@ -64,6 +64,13 @@
"when": 1776695279870,
"tag": "0008_far_energizer",
"breakpoints": true
},
{
"idx": 9,
"version": "7",
"when": 1776928720684,
"tag": "0009_rapid_cobalt_man",
"breakpoints": true
}
]
}

View file

@ -4,7 +4,7 @@
"private": true,
"type": "module",
"scripts": {
"build": "tsc",
"build": "rm -rf dist && tsc",
"generate": "drizzle-kit generate",
"migrate": "drizzle-kit migrate"
},

View file

@ -10,6 +10,7 @@ import {
index,
boolean,
integer,
serial,
} from "drizzle-orm/pg-core";
import { sql, relations } from "drizzle-orm";
@ -330,3 +331,5 @@ export const lobbyPlayersRelations = relations(lobby_players, ({ one }) => ({
}),
user: one(user, { fields: [lobby_players.userId], references: [user.id] }),
}));
export const dummy = pgTable("dummy", { id: serial("id").primaryKey() });

View file

@ -0,0 +1,25 @@
import { config } from "dotenv";
import { drizzle } from "drizzle-orm/node-postgres";
import { migrate } from "drizzle-orm/node-postgres/migrator";
import { Pool } from "pg";
import { resolve, dirname } from "path";
import { fileURLToPath } from "url";
config({
path: resolve(dirname(fileURLToPath(import.meta.url)), "../../../.env"),
});
const pool = new Pool({ connectionString: process.env["DATABASE_URL"]! });
const db = drizzle(pool);
console.log("starting database migrations...");
await migrate(db, {
migrationsFolder: resolve(
dirname(fileURLToPath(import.meta.url)),
"../drizzle",
),
});
await pool.end();
console.log("database migrations complete.");