From cbec696c59926766b0af4cbc494dab26f4eda857 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 9 Jul 2026 18:51:18 +0200 Subject: [PATCH] feat(db): provision Postgres and add score schema migration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wire in the txt_game_scores database provision and create the players/games tables via a provision-scoped migration so Child 2 can implement the server-side score persistence. - module.yml: requires postgres database (txt_game_scores) with env_map for host/port/user/password/name; empty env keys added so env-sync populates them after provisioning - migrations/package.json: ESM, pg + typescript devdeps - migrations/provision/postgres/000-init-scores.ts: creates players and games tables with idempotent IF NOT EXISTS guards and two supporting indexes (scoreboard sort, history lookup) Server code, Dockerfile, and docker-compose.yml are unchanged — server integration is deferred to the follow-on child task. Task: 3dab55e3-9792-47a0-a4a6-ba7757517314 --- migrations/package.json | 12 +++++ .../provision/postgres/000-init-scores.ts | 52 +++++++++++++++++++ module.yml | 16 ++++++ 3 files changed, 80 insertions(+) create mode 100644 migrations/package.json create mode 100644 migrations/provision/postgres/000-init-scores.ts diff --git a/migrations/package.json b/migrations/package.json new file mode 100644 index 0000000..77baf90 --- /dev/null +++ b/migrations/package.json @@ -0,0 +1,12 @@ +{ + "private": true, + "type": "module", + "dependencies": { + "pg": "*" + }, + "devDependencies": { + "@types/node": "*", + "@types/pg": "*", + "typescript": "*" + } +} diff --git a/migrations/provision/postgres/000-init-scores.ts b/migrations/provision/postgres/000-init-scores.ts new file mode 100644 index 0000000..2f4118c --- /dev/null +++ b/migrations/provision/postgres/000-init-scores.ts @@ -0,0 +1,52 @@ +import pg from "pg"; + +const client = new pg.Client({ + host: process.env.PROVISION_HOST, + port: parseInt(process.env.PROVISION_PORT ?? "5432"), + user: process.env.PROVISION_USER, + password: process.env.PROVISION_PASSWORD, + database: process.env.PROVISION_DATABASE, +}); + +await client.connect(); + +try { + await client.query(` + CREATE TABLE IF NOT EXISTS players ( + id TEXT PRIMARY KEY, + games_played INT NOT NULL DEFAULT 0, + games_won INT NOT NULL DEFAULT 0, + games_lost INT NOT NULL DEFAULT 0, + best_attempts INT, + current_streak INT NOT NULL DEFAULT 0, + best_streak INT NOT NULL DEFAULT 0, + last_played_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() + ) + `); + + await client.query(` + CREATE TABLE IF NOT EXISTS games ( + id BIGSERIAL PRIMARY KEY, + player_id TEXT NOT NULL REFERENCES players(id) ON DELETE CASCADE, + target INT NOT NULL, + attempts INT NOT NULL, + won BOOLEAN NOT NULL, + finished_at TIMESTAMPTZ NOT NULL DEFAULT now() + ) + `); + + await client.query(` + CREATE INDEX IF NOT EXISTS idx_players_scoreboard + ON players (best_attempts NULLS LAST, last_played_at) + `); + + await client.query(` + CREATE INDEX IF NOT EXISTS idx_games_player_history + ON games (player_id, finished_at DESC) + `); + + console.log("[txt-game migration-000] score schema created"); +} finally { + await client.end(); +} diff --git a/module.yml b/module.yml index b8731a4..4cc5bb5 100644 --- a/module.yml +++ b/module.yml @@ -11,7 +11,23 @@ docker: context: . dockerfile: Dockerfile +requires: + - provider: postgres + type: database + name: txt_game_scores + env_map: + TXT_GAME_DB_HOST: host + TXT_GAME_DB_PORT: port + TXT_GAME_DB_USER: user + TXT_GAME_DB_PASSWORD: password + TXT_GAME_DB_NAME: database + env: DOMAIN: from: node.domain default: localhost + TXT_GAME_DB_HOST: + TXT_GAME_DB_PORT: + TXT_GAME_DB_USER: + TXT_GAME_DB_PASSWORD: + TXT_GAME_DB_NAME: