Compare commits
3 commits
0dba68904e
...
1a50f73c74
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a50f73c74 | ||
|
|
66eddb9a2a | ||
|
|
9a3376cdcc |
11 changed files with 1369 additions and 2 deletions
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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
122
documentation/backlog.md
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
3
packages/db/drizzle/0009_rapid_cobalt_man.sql
Normal file
3
packages/db/drizzle/0009_rapid_cobalt_man.sql
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
CREATE TABLE "dummy" (
|
||||
"id" serial PRIMARY KEY NOT NULL
|
||||
);
|
||||
1203
packages/db/drizzle/meta/0009_snapshot.json
Normal file
1203
packages/db/drizzle/meta/0009_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"build": "rm -rf dist && tsc",
|
||||
"generate": "drizzle-kit generate",
|
||||
"migrate": "drizzle-kit migrate"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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() });
|
||||
|
|
|
|||
25
packages/db/src/migrate.ts
Normal file
25
packages/db/src/migrate.ts
Normal 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.");
|
||||
Loading…
Add table
Add a link
Reference in a new issue