diff --git a/apps/api/Dockerfile b/apps/api/Dockerfile index a581eae..fa4b48c 100644 --- a/apps/api/Dockerfile +++ b/apps/api/Dockerfile @@ -39,7 +39,6 @@ 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 ["sh", "-c", "node packages/db/dist/src/migrate.js && node apps/api/dist/src/server.js"] +CMD ["node", "apps/api/dist/src/server.js"] diff --git a/docker-compose.yml b/docker-compose.yml index cae960a..b661975 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -31,7 +31,6 @@ services: api: container_name: lila-api - user: "${UID}:${GID}" build: context: . dockerfile: ./apps/api/Dockerfile @@ -60,7 +59,6 @@ services: web: container_name: lila-web - user: "${UID}:${GID}" build: context: . dockerfile: ./apps/web/Dockerfile diff --git a/documentation/data-pipeline.md b/documentation/PIPELINE.md similarity index 100% rename from documentation/data-pipeline.md rename to documentation/PIPELINE.md diff --git a/documentation/backlog.md b/documentation/backlog.md deleted file mode 100644 index 8515238..0000000 --- a/documentation/backlog.md +++ /dev/null @@ -1,122 +0,0 @@ -# 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 diff --git a/documentation/notes.md b/documentation/notes.md index 2da500d..50c13c1 100644 --- a/documentation/notes.md +++ b/documentation/notes.md @@ -2,7 +2,6 @@ ## 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 diff --git a/packages/db/drizzle/0009_rapid_cobalt_man.sql b/packages/db/drizzle/0009_rapid_cobalt_man.sql deleted file mode 100644 index e3dcc04..0000000 --- a/packages/db/drizzle/0009_rapid_cobalt_man.sql +++ /dev/null @@ -1,3 +0,0 @@ -CREATE TABLE "dummy" ( - "id" serial PRIMARY KEY NOT NULL -); diff --git a/packages/db/drizzle/meta/0009_snapshot.json b/packages/db/drizzle/meta/0009_snapshot.json deleted file mode 100644 index 8274112..0000000 --- a/packages/db/drizzle/meta/0009_snapshot.json +++ /dev/null @@ -1,1203 +0,0 @@ -{ - "id": "24f8a0f9-40eb-4ad7-b08a-00ab7c98ecd4", - "prevId": "3824b3fb-5334-4efe-aa71-b6dbfb682c15", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.account": { - "name": "account", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "account_id": { - "name": "account_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "provider_id": { - "name": "provider_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "access_token": { - "name": "access_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "refresh_token": { - "name": "refresh_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "id_token": { - "name": "id_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "access_token_expires_at": { - "name": "access_token_expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "refresh_token_expires_at": { - "name": "refresh_token_expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "password": { - "name": "password", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "account_userId_idx": { - "name": "account_userId_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "account_user_id_user_id_fk": { - "name": "account_user_id_user_id_fk", - "tableFrom": "account", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.deck_terms": { - "name": "deck_terms", - "schema": "", - "columns": { - "deck_id": { - "name": "deck_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "term_id": { - "name": "term_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "deck_terms_deck_id_decks_id_fk": { - "name": "deck_terms_deck_id_decks_id_fk", - "tableFrom": "deck_terms", - "tableTo": "decks", - "columnsFrom": [ - "deck_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "deck_terms_term_id_terms_id_fk": { - "name": "deck_terms_term_id_terms_id_fk", - "tableFrom": "deck_terms", - "tableTo": "terms", - "columnsFrom": [ - "term_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "deck_terms_deck_id_term_id_pk": { - "name": "deck_terms_deck_id_term_id_pk", - "columns": [ - "deck_id", - "term_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.decks": { - "name": "decks", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "source_language": { - "name": "source_language", - "type": "varchar(10)", - "primaryKey": false, - "notNull": true - }, - "validated_languages": { - "name": "validated_languages", - "type": "varchar(10)[]", - "primaryKey": false, - "notNull": true, - "default": "'{}'" - }, - "type": { - "name": "type", - "type": "varchar(20)", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "idx_decks_type": { - "name": "idx_decks_type", - "columns": [ - { - "expression": "type", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "source_language", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "unique_deck_name": { - "name": "unique_deck_name", - "nullsNotDistinct": false, - "columns": [ - "name", - "source_language" - ] - } - }, - "policies": {}, - "checkConstraints": { - "source_language_check": { - "name": "source_language_check", - "value": "\"decks\".\"source_language\" IN ('en', 'it', 'de', 'fr', 'es')" - }, - "validated_languages_check": { - "name": "validated_languages_check", - "value": "validated_languages <@ ARRAY['en', 'it', 'de', 'fr', 'es']::varchar[]" - }, - "validated_languages_excludes_source": { - "name": "validated_languages_excludes_source", - "value": "NOT (\"decks\".\"source_language\" = ANY(\"decks\".\"validated_languages\"))" - }, - "deck_type_check": { - "name": "deck_type_check", - "value": "\"decks\".\"type\" IN ('grammar', 'media')" - } - }, - "isRLSEnabled": false - }, - "public.dummy": { - "name": "dummy", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "serial", - "primaryKey": true, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.lobbies": { - "name": "lobbies", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "code": { - "name": "code", - "type": "varchar(10)", - "primaryKey": false, - "notNull": true - }, - "host_user_id": { - "name": "host_user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "varchar(20)", - "primaryKey": false, - "notNull": true, - "default": "'waiting'" - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "lobbies_host_user_id_user_id_fk": { - "name": "lobbies_host_user_id_user_id_fk", - "tableFrom": "lobbies", - "tableTo": "user", - "columnsFrom": [ - "host_user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "lobbies_code_unique": { - "name": "lobbies_code_unique", - "nullsNotDistinct": false, - "columns": [ - "code" - ] - } - }, - "policies": {}, - "checkConstraints": { - "lobby_status_check": { - "name": "lobby_status_check", - "value": "\"lobbies\".\"status\" IN ('waiting', 'in_progress', 'finished')" - } - }, - "isRLSEnabled": false - }, - "public.lobby_players": { - "name": "lobby_players", - "schema": "", - "columns": { - "lobby_id": { - "name": "lobby_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "score": { - "name": "score", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "joined_at": { - "name": "joined_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "lobby_players_lobby_id_lobbies_id_fk": { - "name": "lobby_players_lobby_id_lobbies_id_fk", - "tableFrom": "lobby_players", - "tableTo": "lobbies", - "columnsFrom": [ - "lobby_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "lobby_players_user_id_user_id_fk": { - "name": "lobby_players_user_id_user_id_fk", - "tableFrom": "lobby_players", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "lobby_players_lobby_id_user_id_pk": { - "name": "lobby_players_lobby_id_user_id_pk", - "columns": [ - "lobby_id", - "user_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.session": { - "name": "session", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "token": { - "name": "token", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "ip_address": { - "name": "ip_address", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_agent": { - "name": "user_agent", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "session_userId_idx": { - "name": "session_userId_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "session_user_id_user_id_fk": { - "name": "session_user_id_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "session_token_unique": { - "name": "session_token_unique", - "nullsNotDistinct": false, - "columns": [ - "token" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.term_examples": { - "name": "term_examples", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "term_id": { - "name": "term_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "language_code": { - "name": "language_code", - "type": "varchar(10)", - "primaryKey": false, - "notNull": true - }, - "text": { - "name": "text", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "idx_term_examples_term_id": { - "name": "idx_term_examples_term_id", - "columns": [ - { - "expression": "term_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "language_code", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "term_examples_term_id_terms_id_fk": { - "name": "term_examples_term_id_terms_id_fk", - "tableFrom": "term_examples", - "tableTo": "terms", - "columnsFrom": [ - "term_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "unique_term_example": { - "name": "unique_term_example", - "nullsNotDistinct": false, - "columns": [ - "term_id", - "language_code", - "text" - ] - } - }, - "policies": {}, - "checkConstraints": { - "language_code_check": { - "name": "language_code_check", - "value": "\"term_examples\".\"language_code\" IN ('en', 'it', 'de', 'fr', 'es')" - } - }, - "isRLSEnabled": false - }, - "public.term_glosses": { - "name": "term_glosses", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "term_id": { - "name": "term_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "language_code": { - "name": "language_code", - "type": "varchar(10)", - "primaryKey": false, - "notNull": true - }, - "text": { - "name": "text", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "term_glosses_term_id_terms_id_fk": { - "name": "term_glosses_term_id_terms_id_fk", - "tableFrom": "term_glosses", - "tableTo": "terms", - "columnsFrom": [ - "term_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "unique_term_gloss": { - "name": "unique_term_gloss", - "nullsNotDistinct": false, - "columns": [ - "term_id", - "language_code" - ] - } - }, - "policies": {}, - "checkConstraints": { - "language_code_check": { - "name": "language_code_check", - "value": "\"term_glosses\".\"language_code\" IN ('en', 'it', 'de', 'fr', 'es')" - } - }, - "isRLSEnabled": false - }, - "public.term_topics": { - "name": "term_topics", - "schema": "", - "columns": { - "term_id": { - "name": "term_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "topic_id": { - "name": "topic_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "term_topics_term_id_terms_id_fk": { - "name": "term_topics_term_id_terms_id_fk", - "tableFrom": "term_topics", - "tableTo": "terms", - "columnsFrom": [ - "term_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "term_topics_topic_id_topics_id_fk": { - "name": "term_topics_topic_id_topics_id_fk", - "tableFrom": "term_topics", - "tableTo": "topics", - "columnsFrom": [ - "topic_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "term_topics_term_id_topic_id_pk": { - "name": "term_topics_term_id_topic_id_pk", - "columns": [ - "term_id", - "topic_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.terms": { - "name": "terms", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "source": { - "name": "source", - "type": "varchar(50)", - "primaryKey": false, - "notNull": false - }, - "source_id": { - "name": "source_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "pos": { - "name": "pos", - "type": "varchar(20)", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "idx_terms_source_pos": { - "name": "idx_terms_source_pos", - "columns": [ - { - "expression": "source", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "pos", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "unique_source_id": { - "name": "unique_source_id", - "nullsNotDistinct": false, - "columns": [ - "source", - "source_id" - ] - } - }, - "policies": {}, - "checkConstraints": { - "pos_check": { - "name": "pos_check", - "value": "\"terms\".\"pos\" IN ('noun', 'verb', 'adjective', 'adverb')" - } - }, - "isRLSEnabled": false - }, - "public.topics": { - "name": "topics", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "slug": { - "name": "slug", - "type": "varchar(50)", - "primaryKey": false, - "notNull": true - }, - "label": { - "name": "label", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "topics_slug_unique": { - "name": "topics_slug_unique", - "nullsNotDistinct": false, - "columns": [ - "slug" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.translations": { - "name": "translations", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "term_id": { - "name": "term_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "language_code": { - "name": "language_code", - "type": "varchar(10)", - "primaryKey": false, - "notNull": true - }, - "text": { - "name": "text", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "cefr_level": { - "name": "cefr_level", - "type": "varchar(2)", - "primaryKey": false, - "notNull": false - }, - "difficulty": { - "name": "difficulty", - "type": "varchar(20)", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "idx_translations_lang": { - "name": "idx_translations_lang", - "columns": [ - { - "expression": "language_code", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "difficulty", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "cefr_level", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "term_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "translations_term_id_terms_id_fk": { - "name": "translations_term_id_terms_id_fk", - "tableFrom": "translations", - "tableTo": "terms", - "columnsFrom": [ - "term_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "unique_translations": { - "name": "unique_translations", - "nullsNotDistinct": false, - "columns": [ - "term_id", - "language_code", - "text" - ] - } - }, - "policies": {}, - "checkConstraints": { - "language_code_check": { - "name": "language_code_check", - "value": "\"translations\".\"language_code\" IN ('en', 'it', 'de', 'fr', 'es')" - }, - "cefr_check": { - "name": "cefr_check", - "value": "\"translations\".\"cefr_level\" IN ('A1', 'A2', 'B1', 'B2', 'C1', 'C2')" - }, - "difficulty_check": { - "name": "difficulty_check", - "value": "\"translations\".\"difficulty\" IN ('easy', 'intermediate', 'hard')" - } - }, - "isRLSEnabled": false - }, - "public.user": { - "name": "user", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "email_verified": { - "name": "email_verified", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "image": { - "name": "image", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "user_email_unique": { - "name": "user_email_unique", - "nullsNotDistinct": false, - "columns": [ - "email" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.verification": { - "name": "verification", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "identifier": { - "name": "identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "value": { - "name": "value", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "verification_identifier_idx": { - "name": "verification_identifier_idx", - "columns": [ - { - "expression": "identifier", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": {}, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file diff --git a/packages/db/drizzle/meta/_journal.json b/packages/db/drizzle/meta/_journal.json index 394f7e7..f86ac07 100644 --- a/packages/db/drizzle/meta/_journal.json +++ b/packages/db/drizzle/meta/_journal.json @@ -64,13 +64,6 @@ "when": 1776695279870, "tag": "0008_far_energizer", "breakpoints": true - }, - { - "idx": 9, - "version": "7", - "when": 1776928720684, - "tag": "0009_rapid_cobalt_man", - "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/db/package.json b/packages/db/package.json index 914e989..a717652 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "scripts": { - "build": "rm -rf dist && tsc", + "build": "tsc", "generate": "drizzle-kit generate", "migrate": "drizzle-kit migrate" }, diff --git a/packages/db/src/db/schema.ts b/packages/db/src/db/schema.ts index a79e64d..b2184b3 100644 --- a/packages/db/src/db/schema.ts +++ b/packages/db/src/db/schema.ts @@ -10,7 +10,6 @@ import { index, boolean, integer, - serial, } from "drizzle-orm/pg-core"; import { sql, relations } from "drizzle-orm"; @@ -331,5 +330,3 @@ 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() }); diff --git a/packages/db/src/migrate.ts b/packages/db/src/migrate.ts deleted file mode 100644 index 075cf2f..0000000 --- a/packages/db/src/migrate.ts +++ /dev/null @@ -1,25 +0,0 @@ -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.");