From 0125af2086b65def6ac9444f1faae9f68ee823eb Mon Sep 17 00:00:00 2001 From: fireshaper Date: Fri, 9 Oct 2026 23:50:05 -0400 Subject: [PATCH] Step 14.11: retire the server Removes apps/server (Fastify, Drizzle, pg-boss, Better Auth), the development Postgres, the backup code routes, the web HTTP client and the CI database service. Deployment is static hosting with Caddy. A Playwright test checks that the production build loads only its own files. Co-Authored-By: Claude Opus 5.5 --- .env.example | 16 - .gitea/workflows/ci.yml | 15 - .gitignore | 1 + .prettierignore | 1 - DECISIONS.md | 7 + apps/server/drizzle.config.ts | 8 - apps/server/drizzle/0000_init.sql | 109 -- apps/server/drizzle/0001_auth.sql | 57 - .../drizzle/0002_cr001_faster_start.sql | 14 - .../drizzle/0003_cr002_location_boards.sql | 5 - apps/server/drizzle/0004_cr004_pilot_duty.sql | 2 - apps/server/drizzle/0005_cr007_goods.sql | 36 - .../drizzle/0006_cr009_ship_upgrades.sql | 11 - .../drizzle/0007_cr010_veteran_pilot.sql | 1 - .../drizzle/0008_cr011_backup_codes.sql | 11 - .../drizzle/0009_cr013_ship_from_deal.sql | 5 - apps/server/drizzle/meta/0000_snapshot.json | 842 ---------- apps/server/drizzle/meta/0001_snapshot.json | 1232 -------------- apps/server/drizzle/meta/0002_snapshot.json | 1319 --------------- apps/server/drizzle/meta/0003_snapshot.json | 1319 --------------- apps/server/drizzle/meta/0004_snapshot.json | 1332 --------------- apps/server/drizzle/meta/0005_snapshot.json | 1332 --------------- apps/server/drizzle/meta/0006_snapshot.json | 1403 ---------------- apps/server/drizzle/meta/0007_snapshot.json | 1410 ---------------- apps/server/drizzle/meta/0008_snapshot.json | 1487 ---------------- apps/server/drizzle/meta/0009_snapshot.json | 1494 ----------------- apps/server/drizzle/meta/_journal.json | 76 - apps/server/package.json | 31 - apps/server/src/app.ts | 75 - apps/server/src/auth.ts | 23 - apps/server/src/clock.ts | 30 - apps/server/src/config.ts | 45 - apps/server/src/db/auth-schema.ts | 96 -- apps/server/src/db/client.ts | 23 - apps/server/src/db/migrate-cli.ts | 15 - apps/server/src/db/migrate.ts | 15 - apps/server/src/db/schema.ts | 254 --- apps/server/src/game/backup.ts | 96 -- apps/server/src/game/boards.ts | 198 --- apps/server/src/game/company.ts | 24 - apps/server/src/game/deal.ts | 107 -- apps/server/src/game/dispatch.ts | 307 ---- apps/server/src/game/fleet.ts | 124 -- apps/server/src/game/goals.ts | 131 -- apps/server/src/game/loan.ts | 33 - apps/server/src/game/lock.ts | 22 - apps/server/src/game/mappers.ts | 101 -- apps/server/src/game/messages.ts | 34 - apps/server/src/game/names.ts | 117 -- apps/server/src/game/new-company.ts | 59 - apps/server/src/game/new-player.ts | 76 - apps/server/src/game/pilots.ts | 217 --- apps/server/src/game/player-state.ts | 134 -- apps/server/src/game/resolve.ts | 116 -- apps/server/src/game/summaries.ts | 56 - apps/server/src/game/upgrades.ts | 101 -- apps/server/src/http/errors.ts | 48 - apps/server/src/http/route.ts | 72 - apps/server/src/http/session.ts | 30 - apps/server/src/index.ts | 3 - apps/server/src/jobs/cleanup.ts | 34 - apps/server/src/jobs/definitions.ts | 42 - apps/server/src/jobs/queue.ts | 72 - apps/server/src/main.ts | 18 - apps/server/src/routes/boards.ts | 17 - apps/server/src/routes/context.ts | 16 - apps/server/src/routes/dev.ts | 127 -- apps/server/src/routes/dispatches.ts | 36 - apps/server/src/routes/fleet.ts | 59 - apps/server/src/routes/player.ts | 142 -- apps/server/src/routes/test-clock.ts | 20 - apps/server/src/routes/with-player.ts | 20 - apps/server/test/arrival-job.test.ts | 120 -- apps/server/test/backup.test.ts | 106 -- apps/server/test/board-keys.test.ts | 30 - apps/server/test/buy-ship.test.ts | 158 -- apps/server/test/cleanup.test.ts | 80 - apps/server/test/company-name.test.ts | 46 - apps/server/test/concurrency.test.ts | 78 - .../test/contract-dispatch-landing.test.ts | 69 - apps/server/test/contract-dispatch.test.ts | 249 --- apps/server/test/db.ts | 121 -- apps/server/test/deal.test.ts | 120 -- apps/server/test/dev-tools.test.ts | 156 -- apps/server/test/duty-rest.test.ts | 158 -- apps/server/test/game.ts | 222 --- apps/server/test/goals.test.ts | 139 -- apps/server/test/goods-backfill.test.ts | 96 -- apps/server/test/guest.test.ts | 89 - apps/server/test/harness.test.ts | 60 - apps/server/test/health.test.ts | 38 - apps/server/test/helpers.ts | 18 - apps/server/test/jobs.test.ts | 53 - apps/server/test/last-arrival.test.ts | 34 - apps/server/test/lease-bay.test.ts | 74 - apps/server/test/lighter-clipper.test.ts | 80 - apps/server/test/loan.test.ts | 132 -- apps/server/test/location-board.test.ts | 108 -- apps/server/test/migrations.test.ts | 59 - apps/server/test/new-company.test.ts | 77 - apps/server/test/pilots.test.ts | 178 -- apps/server/test/reposition.test.ts | 63 - apps/server/test/resolution.test.ts | 150 -- apps/server/test/skills.test.ts | 61 - apps/server/test/state.test.ts | 97 -- apps/server/test/stranded.test.ts | 97 -- apps/server/test/summaries.test.ts | 67 - apps/server/test/time.test.ts | 28 - apps/server/test/tutorial-board.test.ts | 115 -- apps/server/test/tutorial-dispatch.test.ts | 110 -- apps/server/test/tutorial-flow.ts | 31 - .../test/tutorial-v2-three-runs.test.ts | 38 - apps/server/test/tutorial-v2.test.ts | 80 - apps/server/test/upgrades.test.ts | 186 -- apps/server/test/validation.test.ts | 85 - apps/server/test/veteran.test.ts | 103 -- apps/server/tsconfig.json | 4 - apps/server/vitest.config.ts | 8 - apps/web/e2e/14-11-production.e2e.ts | 29 + apps/web/package.json | 2 +- apps/web/playwright.config.ts | 24 +- apps/web/src/api/client.ts | 91 +- apps/web/src/api/device.ts | 20 - apps/web/test/api-client.test.ts | 77 - apps/web/vite.config.ts | 9 +- deploy/.env.example | 22 +- deploy/Caddyfile | 20 +- deploy/README.md | 74 +- deploy/backup/backup.sh | 13 - deploy/backup/restore.sh | 15 - deploy/backup/schedule.sh | 13 - deploy/check.sh | 80 +- deploy/compose.yml | 57 +- deploy/server.Dockerfile | 32 - deploy/web.Dockerfile | 7 +- docker-compose.yml | 23 - docker/postgres/init/01-create-test-db.sql | 2 - eslint.config.js | 10 +- memory-bank/architecture.md | 443 +---- memory-bank/progress.md | 2 +- packages/engine/src/game/engine.ts | 7 +- packages/engine/src/index.ts | 8 +- packages/engine/test/catalog.test.ts | 5 +- packages/schema/src/errors.ts | 2 - packages/schema/src/requests.ts | 13 - packages/schema/src/routes.ts | 26 +- packages/schema/test/routes.test.ts | 9 +- pnpm-lock.yaml | 1460 +--------------- 148 files changed, 161 insertions(+), 23666 deletions(-) delete mode 100644 .env.example delete mode 100644 apps/server/drizzle.config.ts delete mode 100644 apps/server/drizzle/0000_init.sql delete mode 100644 apps/server/drizzle/0001_auth.sql delete mode 100644 apps/server/drizzle/0002_cr001_faster_start.sql delete mode 100644 apps/server/drizzle/0003_cr002_location_boards.sql delete mode 100644 apps/server/drizzle/0004_cr004_pilot_duty.sql delete mode 100644 apps/server/drizzle/0005_cr007_goods.sql delete mode 100644 apps/server/drizzle/0006_cr009_ship_upgrades.sql delete mode 100644 apps/server/drizzle/0007_cr010_veteran_pilot.sql delete mode 100644 apps/server/drizzle/0008_cr011_backup_codes.sql delete mode 100644 apps/server/drizzle/0009_cr013_ship_from_deal.sql delete mode 100644 apps/server/drizzle/meta/0000_snapshot.json delete mode 100644 apps/server/drizzle/meta/0001_snapshot.json delete mode 100644 apps/server/drizzle/meta/0002_snapshot.json delete mode 100644 apps/server/drizzle/meta/0003_snapshot.json delete mode 100644 apps/server/drizzle/meta/0004_snapshot.json delete mode 100644 apps/server/drizzle/meta/0005_snapshot.json delete mode 100644 apps/server/drizzle/meta/0006_snapshot.json delete mode 100644 apps/server/drizzle/meta/0007_snapshot.json delete mode 100644 apps/server/drizzle/meta/0008_snapshot.json delete mode 100644 apps/server/drizzle/meta/0009_snapshot.json delete mode 100644 apps/server/drizzle/meta/_journal.json delete mode 100644 apps/server/package.json delete mode 100644 apps/server/src/app.ts delete mode 100644 apps/server/src/auth.ts delete mode 100644 apps/server/src/clock.ts delete mode 100644 apps/server/src/config.ts delete mode 100644 apps/server/src/db/auth-schema.ts delete mode 100644 apps/server/src/db/client.ts delete mode 100644 apps/server/src/db/migrate-cli.ts delete mode 100644 apps/server/src/db/migrate.ts delete mode 100644 apps/server/src/db/schema.ts delete mode 100644 apps/server/src/game/backup.ts delete mode 100644 apps/server/src/game/boards.ts delete mode 100644 apps/server/src/game/company.ts delete mode 100644 apps/server/src/game/deal.ts delete mode 100644 apps/server/src/game/dispatch.ts delete mode 100644 apps/server/src/game/fleet.ts delete mode 100644 apps/server/src/game/goals.ts delete mode 100644 apps/server/src/game/loan.ts delete mode 100644 apps/server/src/game/lock.ts delete mode 100644 apps/server/src/game/mappers.ts delete mode 100644 apps/server/src/game/messages.ts delete mode 100644 apps/server/src/game/names.ts delete mode 100644 apps/server/src/game/new-company.ts delete mode 100644 apps/server/src/game/new-player.ts delete mode 100644 apps/server/src/game/pilots.ts delete mode 100644 apps/server/src/game/player-state.ts delete mode 100644 apps/server/src/game/resolve.ts delete mode 100644 apps/server/src/game/summaries.ts delete mode 100644 apps/server/src/game/upgrades.ts delete mode 100644 apps/server/src/http/errors.ts delete mode 100644 apps/server/src/http/route.ts delete mode 100644 apps/server/src/http/session.ts delete mode 100644 apps/server/src/index.ts delete mode 100644 apps/server/src/jobs/cleanup.ts delete mode 100644 apps/server/src/jobs/definitions.ts delete mode 100644 apps/server/src/jobs/queue.ts delete mode 100644 apps/server/src/main.ts delete mode 100644 apps/server/src/routes/boards.ts delete mode 100644 apps/server/src/routes/context.ts delete mode 100644 apps/server/src/routes/dev.ts delete mode 100644 apps/server/src/routes/dispatches.ts delete mode 100644 apps/server/src/routes/fleet.ts delete mode 100644 apps/server/src/routes/player.ts delete mode 100644 apps/server/src/routes/test-clock.ts delete mode 100644 apps/server/src/routes/with-player.ts delete mode 100644 apps/server/test/arrival-job.test.ts delete mode 100644 apps/server/test/backup.test.ts delete mode 100644 apps/server/test/board-keys.test.ts delete mode 100644 apps/server/test/buy-ship.test.ts delete mode 100644 apps/server/test/cleanup.test.ts delete mode 100644 apps/server/test/company-name.test.ts delete mode 100644 apps/server/test/concurrency.test.ts delete mode 100644 apps/server/test/contract-dispatch-landing.test.ts delete mode 100644 apps/server/test/contract-dispatch.test.ts delete mode 100644 apps/server/test/db.ts delete mode 100644 apps/server/test/deal.test.ts delete mode 100644 apps/server/test/dev-tools.test.ts delete mode 100644 apps/server/test/duty-rest.test.ts delete mode 100644 apps/server/test/game.ts delete mode 100644 apps/server/test/goals.test.ts delete mode 100644 apps/server/test/goods-backfill.test.ts delete mode 100644 apps/server/test/guest.test.ts delete mode 100644 apps/server/test/harness.test.ts delete mode 100644 apps/server/test/health.test.ts delete mode 100644 apps/server/test/helpers.ts delete mode 100644 apps/server/test/jobs.test.ts delete mode 100644 apps/server/test/last-arrival.test.ts delete mode 100644 apps/server/test/lease-bay.test.ts delete mode 100644 apps/server/test/lighter-clipper.test.ts delete mode 100644 apps/server/test/loan.test.ts delete mode 100644 apps/server/test/location-board.test.ts delete mode 100644 apps/server/test/migrations.test.ts delete mode 100644 apps/server/test/new-company.test.ts delete mode 100644 apps/server/test/pilots.test.ts delete mode 100644 apps/server/test/reposition.test.ts delete mode 100644 apps/server/test/resolution.test.ts delete mode 100644 apps/server/test/skills.test.ts delete mode 100644 apps/server/test/state.test.ts delete mode 100644 apps/server/test/stranded.test.ts delete mode 100644 apps/server/test/summaries.test.ts delete mode 100644 apps/server/test/time.test.ts delete mode 100644 apps/server/test/tutorial-board.test.ts delete mode 100644 apps/server/test/tutorial-dispatch.test.ts delete mode 100644 apps/server/test/tutorial-flow.ts delete mode 100644 apps/server/test/tutorial-v2-three-runs.test.ts delete mode 100644 apps/server/test/tutorial-v2.test.ts delete mode 100644 apps/server/test/upgrades.test.ts delete mode 100644 apps/server/test/validation.test.ts delete mode 100644 apps/server/test/veteran.test.ts delete mode 100644 apps/server/tsconfig.json delete mode 100644 apps/server/vitest.config.ts create mode 100644 apps/web/e2e/14-11-production.e2e.ts delete mode 100644 apps/web/src/api/device.ts delete mode 100644 apps/web/test/api-client.test.ts delete mode 100755 deploy/backup/backup.sh delete mode 100755 deploy/backup/restore.sh delete mode 100755 deploy/backup/schedule.sh delete mode 100644 deploy/server.Dockerfile delete mode 100644 docker-compose.yml delete mode 100644 docker/postgres/init/01-create-test-db.sql diff --git a/.env.example b/.env.example deleted file mode 100644 index 812419f..0000000 --- a/.env.example +++ /dev/null @@ -1,16 +0,0 @@ -# Copy to .env and adjust. Values below match docker-compose.yml. - -# Postgres: development and test databases (same server, see docker-compose.yml) -DATABASE_URL=postgres://deadhead:deadhead@localhost:5432/deadhead -TEST_DATABASE_URL=postgres://deadhead:deadhead@localhost:5432/deadhead_test - -# Auth (Better Auth). Secret must be at least 32 random characters; -# generate one with: openssl rand -base64 32 -BETTER_AUTH_SECRET=change-me-to-a-long-random-string -BETTER_AUTH_URL=http://localhost:3000 - -# HTTP port the API server listens on -PORT=3000 - -# Run background job workers (arrival resolution, cleanup) in the API process -WORKERS_ENABLED=true diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index d01cae6..3dcbd65 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -7,21 +7,6 @@ on: jobs: check: runs-on: ubuntu-latest - services: - # Test database for server integration tests. - postgres: - image: postgres:18 - env: - POSTGRES_USER: deadhead - POSTGRES_PASSWORD: deadhead - POSTGRES_DB: deadhead_test - options: >- - --health-cmd "pg_isready -U deadhead -d deadhead_test" - --health-interval 2s - --health-timeout 5s - --health-retries 30 - env: - TEST_DATABASE_URL: postgres://deadhead:deadhead@postgres:5432/deadhead_test steps: - uses: actions/checkout@v7 - uses: pnpm/action-setup@v6 diff --git a/.gitignore b/.gitignore index bc26312..65a0850 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ node_modules/ dist/ +dist-*/ coverage/ *.tsbuildinfo .env diff --git a/.prettierignore b/.prettierignore index 27708d1..51378bf 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,4 +1,3 @@ pnpm-lock.yaml memory-bank/ .pnpm-store/ -apps/server/drizzle/ diff --git a/DECISIONS.md b/DECISIONS.md index f330faf..72ee889 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -1416,3 +1416,10 @@ Implementation decisions not spelled out in `memory-bank/implementation-plan.md` - **"Share as file"** uses the Web Share API with a file where the device supports it (phones, including the Capacitor web view), otherwise a download. No Capacitor share plugin. - **Only save version 1 exists**, so the "older save migrates" test injects a migration through the engine's `migrate` option; `applyMigrations`'s stepping is tested in the schema package. - **Debug info reads storage through a small `GameContext`** that `GameRoot` provides; outside it (component tests) storage shows as "unknown". + +## Step 14.11 + +- **The route catalog stays as the command list**, with `method` and `path` kept: about 30 ported test files route by them, and renaming every command reference wouldn't change behavior. `auth` now reads as "needs a company". +- **The production-build check runs a real production build** (`dist-production`, built by `build:e2e` alongside the test build) rather than the test build with requests blocked: the test build has developer code the production one doesn't, so only the real build shows what players get. It records every request instead of blocking, so a stray request fails the test instead of being quietly aborted. +- **Deploying is static hosting with the same Caddy setup**, minus the API and backups: the domain, HTTPS and caching rules from Phase 13 still hold. Player saves live on devices; Export save is the backup. +- **The web Dockerfile was missing the engine package's manifest** since 14.7 (`pnpm install --filter @deadhead/web...` couldn't link it), found while rewriting `deploy/`; fixed here. diff --git a/apps/server/drizzle.config.ts b/apps/server/drizzle.config.ts deleted file mode 100644 index f88e759..0000000 --- a/apps/server/drizzle.config.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { defineConfig } from "drizzle-kit"; - -// `pnpm --filter @deadhead/server db:generate` writes SQL migrations to ./drizzle. -export default defineConfig({ - dialect: "postgresql", - schema: ["./src/db/schema.ts", "./src/db/auth-schema.ts"], - out: "./drizzle", -}); diff --git a/apps/server/drizzle/0000_init.sql b/apps/server/drizzle/0000_init.sql deleted file mode 100644 index 4f924c7..0000000 --- a/apps/server/drizzle/0000_init.sql +++ /dev/null @@ -1,109 +0,0 @@ -CREATE TYPE "public"."activity_status" AS ENUM('idle', 'in_flight');--> statement-breakpoint -CREATE TYPE "public"."board_kind" AS ENUM('bay', 'tutorial');--> statement-breakpoint -CREATE TYPE "public"."dispatch_status" AS ENUM('in_flight', 'resolved');--> statement-breakpoint -CREATE TABLE "accepted_contracts" ( - "player_id" text NOT NULL, - "contract_id" text NOT NULL, - "period_index" integer NOT NULL, - "dispatch_id" text NOT NULL, - "accepted_at" bigint NOT NULL, - CONSTRAINT "accepted_contracts_player_id_contract_id_pk" PRIMARY KEY("player_id","contract_id") -); ---> statement-breakpoint -CREATE TABLE "bays" ( - "id" text PRIMARY KEY NOT NULL, - "player_id" text NOT NULL, - "location_id" text NOT NULL, - "created_at" bigint NOT NULL -); ---> statement-breakpoint -CREATE TABLE "companies" ( - "id" text PRIMARY KEY NOT NULL, - "player_id" text NOT NULL, - "name" text NOT NULL, - "credits" bigint NOT NULL, - CONSTRAINT "companies_player_id_unique" UNIQUE("player_id") -); ---> statement-breakpoint -CREATE TABLE "contract_boards" ( - "id" text PRIMARY KEY NOT NULL, - "player_id" text NOT NULL, - "kind" "board_kind" NOT NULL, - "location_id" text NOT NULL, - "period_index" integer NOT NULL, - "contracts" jsonb NOT NULL, - "created_at" bigint NOT NULL -); ---> statement-breakpoint -CREATE TABLE "discovered_locations" ( - "player_id" text NOT NULL, - "location_id" text NOT NULL, - "discovered_at" bigint NOT NULL, - CONSTRAINT "discovered_locations_player_id_location_id_pk" PRIMARY KEY("player_id","location_id") -); ---> statement-breakpoint -CREATE TABLE "dispatches" ( - "id" text PRIMARY KEY NOT NULL, - "player_id" text NOT NULL, - "ship_id" text, - "pilot_id" text NOT NULL, - "tutorial" boolean NOT NULL, - "record" jsonb NOT NULL, - "arrival_at" bigint NOT NULL, - "status" "dispatch_status" DEFAULT 'in_flight' NOT NULL, - "resolution" jsonb, - "resolved_at" bigint, - "acknowledged_at" bigint -); ---> statement-breakpoint -CREATE TABLE "pilots" ( - "id" text PRIMARY KEY NOT NULL, - "player_id" text NOT NULL, - "name" text NOT NULL, - "is_avatar" boolean DEFAULT false NOT NULL, - "xp" integer DEFAULT 0 NOT NULL, - "skills" jsonb DEFAULT '{}'::jsonb NOT NULL, - "skill_points_spent" integer DEFAULT 0 NOT NULL, - "assigned_ship_id" text, - "location_id" text, - "status" "activity_status" DEFAULT 'idle' NOT NULL, - "created_at" bigint NOT NULL, - CONSTRAINT "pilots_assigned_ship_id_unique" UNIQUE("assigned_ship_id") -); ---> statement-breakpoint -CREATE TABLE "players" ( - "id" text PRIMARY KEY NOT NULL, - "world_seed" bigint NOT NULL, - "tutorial_active" boolean DEFAULT true NOT NULL, - "loaner_location_id" text, - "created_at" bigint NOT NULL -); ---> statement-breakpoint -CREATE TABLE "ships" ( - "id" text PRIMARY KEY NOT NULL, - "player_id" text NOT NULL, - "model_id" text NOT NULL, - "name" text NOT NULL, - "status" "activity_status" DEFAULT 'idle' NOT NULL, - "location_id" text, - "created_at" bigint NOT NULL -); ---> statement-breakpoint -ALTER TABLE "accepted_contracts" ADD CONSTRAINT "accepted_contracts_player_id_players_id_fk" FOREIGN KEY ("player_id") REFERENCES "public"."players"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "accepted_contracts" ADD CONSTRAINT "accepted_contracts_dispatch_id_dispatches_id_fk" FOREIGN KEY ("dispatch_id") REFERENCES "public"."dispatches"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "bays" ADD CONSTRAINT "bays_player_id_players_id_fk" FOREIGN KEY ("player_id") REFERENCES "public"."players"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "companies" ADD CONSTRAINT "companies_player_id_players_id_fk" FOREIGN KEY ("player_id") REFERENCES "public"."players"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "contract_boards" ADD CONSTRAINT "contract_boards_player_id_players_id_fk" FOREIGN KEY ("player_id") REFERENCES "public"."players"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "discovered_locations" ADD CONSTRAINT "discovered_locations_player_id_players_id_fk" FOREIGN KEY ("player_id") REFERENCES "public"."players"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "dispatches" ADD CONSTRAINT "dispatches_player_id_players_id_fk" FOREIGN KEY ("player_id") REFERENCES "public"."players"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "dispatches" ADD CONSTRAINT "dispatches_ship_id_ships_id_fk" FOREIGN KEY ("ship_id") REFERENCES "public"."ships"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "dispatches" ADD CONSTRAINT "dispatches_pilot_id_pilots_id_fk" FOREIGN KEY ("pilot_id") REFERENCES "public"."pilots"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "pilots" ADD CONSTRAINT "pilots_player_id_players_id_fk" FOREIGN KEY ("player_id") REFERENCES "public"."players"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "pilots" ADD CONSTRAINT "pilots_assigned_ship_id_ships_id_fk" FOREIGN KEY ("assigned_ship_id") REFERENCES "public"."ships"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "ships" ADD CONSTRAINT "ships_player_id_players_id_fk" FOREIGN KEY ("player_id") REFERENCES "public"."players"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -CREATE UNIQUE INDEX "bays_player_location_idx" ON "bays" USING btree ("player_id","location_id");--> statement-breakpoint -CREATE UNIQUE INDEX "contract_boards_unique_idx" ON "contract_boards" USING btree ("player_id","kind","location_id","period_index");--> statement-breakpoint -CREATE INDEX "dispatches_due_idx" ON "dispatches" USING btree ("status","arrival_at");--> statement-breakpoint -CREATE INDEX "dispatches_player_status_idx" ON "dispatches" USING btree ("player_id","status");--> statement-breakpoint -CREATE INDEX "pilots_player_idx" ON "pilots" USING btree ("player_id");--> statement-breakpoint -CREATE INDEX "ships_player_idx" ON "ships" USING btree ("player_id"); \ No newline at end of file diff --git a/apps/server/drizzle/0001_auth.sql b/apps/server/drizzle/0001_auth.sql deleted file mode 100644 index 38466fa..0000000 --- a/apps/server/drizzle/0001_auth.sql +++ /dev/null @@ -1,57 +0,0 @@ -CREATE TABLE "account" ( - "id" text PRIMARY KEY NOT NULL, - "account_id" text NOT NULL, - "provider_id" text NOT NULL, - "user_id" text NOT NULL, - "access_token" text, - "refresh_token" text, - "id_token" text, - "access_token_expires_at" timestamp, - "refresh_token_expires_at" timestamp, - "scope" text, - "password" text, - "created_at" timestamp DEFAULT now() NOT NULL, - "updated_at" timestamp NOT NULL -); ---> statement-breakpoint -CREATE TABLE "session" ( - "id" text PRIMARY KEY NOT NULL, - "expires_at" timestamp NOT NULL, - "token" text NOT NULL, - "created_at" timestamp DEFAULT now() NOT NULL, - "updated_at" timestamp NOT NULL, - "ip_address" text, - "user_agent" text, - "user_id" text NOT NULL, - CONSTRAINT "session_token_unique" UNIQUE("token") -); ---> statement-breakpoint -CREATE TABLE "user" ( - "id" text PRIMARY KEY NOT NULL, - "name" text NOT NULL, - "email" text NOT NULL, - "email_verified" boolean DEFAULT false NOT NULL, - "image" text, - "created_at" timestamp DEFAULT now() NOT NULL, - "updated_at" timestamp DEFAULT now() NOT NULL, - "is_anonymous" boolean DEFAULT false, - CONSTRAINT "user_email_unique" UNIQUE("email") -); ---> statement-breakpoint -CREATE TABLE "verification" ( - "id" text PRIMARY KEY NOT NULL, - "identifier" text NOT NULL, - "value" text NOT NULL, - "expires_at" timestamp NOT NULL, - "created_at" timestamp DEFAULT now() NOT NULL, - "updated_at" timestamp DEFAULT now() NOT NULL -); ---> statement-breakpoint -ALTER TABLE "players" ADD COLUMN "user_id" text NOT NULL;--> statement-breakpoint -ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -CREATE INDEX "account_userId_idx" ON "account" USING btree ("user_id");--> statement-breakpoint -CREATE INDEX "session_userId_idx" ON "session" USING btree ("user_id");--> statement-breakpoint -CREATE INDEX "verification_identifier_idx" ON "verification" USING btree ("identifier");--> statement-breakpoint -ALTER TABLE "players" ADD CONSTRAINT "players_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "players" ADD CONSTRAINT "players_user_id_unique" UNIQUE("user_id"); \ No newline at end of file diff --git a/apps/server/drizzle/0002_cr001_faster_start.sql b/apps/server/drizzle/0002_cr001_faster_start.sql deleted file mode 100644 index 3475d9a..0000000 --- a/apps/server/drizzle/0002_cr001_faster_start.sql +++ /dev/null @@ -1,14 +0,0 @@ -CREATE TABLE "claimed_goals" ( - "player_id" text NOT NULL, - "goal_id" text NOT NULL, - "claimed_at" bigint NOT NULL, - CONSTRAINT "claimed_goals_player_id_goal_id_pk" PRIMARY KEY("player_id","goal_id") -); ---> statement-breakpoint -DROP INDEX "contract_boards_unique_idx";--> statement-breakpoint -ALTER TABLE "companies" ADD COLUMN "loan_balance" bigint DEFAULT 0 NOT NULL;--> statement-breakpoint -ALTER TABLE "contract_boards" ADD COLUMN "tutorial_run" integer DEFAULT 0 NOT NULL;--> statement-breakpoint -ALTER TABLE "players" ADD COLUMN "tutorial_runs_completed" integer DEFAULT 0 NOT NULL;--> statement-breakpoint -ALTER TABLE "players" ADD COLUMN "deal_accepted" boolean DEFAULT false NOT NULL;--> statement-breakpoint -ALTER TABLE "claimed_goals" ADD CONSTRAINT "claimed_goals_player_id_players_id_fk" FOREIGN KEY ("player_id") REFERENCES "public"."players"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -CREATE UNIQUE INDEX "contract_boards_unique_idx" ON "contract_boards" USING btree ("player_id","kind","location_id","period_index","tutorial_run"); \ No newline at end of file diff --git a/apps/server/drizzle/0003_cr002_location_boards.sql b/apps/server/drizzle/0003_cr002_location_boards.sql deleted file mode 100644 index 2f46d32..0000000 --- a/apps/server/drizzle/0003_cr002_location_boards.sql +++ /dev/null @@ -1,5 +0,0 @@ -ALTER TABLE "contract_boards" ALTER COLUMN "kind" SET DATA TYPE text;--> statement-breakpoint -UPDATE "contract_boards" SET "kind" = 'location' WHERE "kind" = 'bay';--> statement-breakpoint -DROP TYPE "public"."board_kind";--> statement-breakpoint -CREATE TYPE "public"."board_kind" AS ENUM('location', 'tutorial');--> statement-breakpoint -ALTER TABLE "contract_boards" ALTER COLUMN "kind" SET DATA TYPE "public"."board_kind" USING "kind"::"public"."board_kind"; \ No newline at end of file diff --git a/apps/server/drizzle/0004_cr004_pilot_duty.sql b/apps/server/drizzle/0004_cr004_pilot_duty.sql deleted file mode 100644 index c5ad651..0000000 --- a/apps/server/drizzle/0004_cr004_pilot_duty.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE "pilots" ADD COLUMN "duty_hours" double precision DEFAULT 0 NOT NULL;--> statement-breakpoint -ALTER TABLE "pilots" ADD COLUMN "rest_ends_at" bigint; \ No newline at end of file diff --git a/apps/server/drizzle/0005_cr007_goods.sql b/apps/server/drizzle/0005_cr007_goods.sql deleted file mode 100644 index 3ffed7b..0000000 --- a/apps/server/drizzle/0005_cr007_goods.sql +++ /dev/null @@ -1,36 +0,0 @@ --- CR-007 (Step 10G.6): contracts carry a good, a shipper and Priority fields. --- Contracts live in JSONB (stored boards and dispatch snapshots), so no columns --- change. Backfill contracts stored before the change so existing saves keep --- working: a default good of the contract's class, the origin's first shipper, --- and no Priority. Rows that already have a good are left alone, so running --- this again changes nothing. -CREATE FUNCTION pg_temp.cr007_backfill(c jsonb) RETURNS jsonb AS $$ - SELECT CASE WHEN c ? 'goodId' THEN c ELSE c || jsonb_build_object( - 'goodId', CASE c->>'cargoTypeId' - WHEN 'heavy' THEN 'machine-parts' - WHEN 'high-value' THEN 'electronics' - WHEN 'dangerous' THEN 'industrial-chemicals' - ELSE 'consumer-goods' END, - 'shipper', CASE c->>'originId' - WHEN 'moon' THEN 'Shackleton Mining Co.' - WHEN 'mars' THEN 'Jezero Agricultural Co-op' - WHEN 'ceres-station' THEN 'Belt Ore Consolidated' - WHEN 'jupiter-cloud-platform' THEN 'Galilean Fuel Combine' - ELSE 'Pacific Orbital Logistics' END, - 'priority', false, - 'deliveryWindowGameHours', null - ) END -$$ LANGUAGE sql IMMUTABLE; ---> statement-breakpoint -UPDATE "contract_boards" -SET "contracts" = ( - SELECT COALESCE(jsonb_agg(pg_temp.cr007_backfill(t.c) ORDER BY t.ord), '[]'::jsonb) - FROM jsonb_array_elements("contracts") WITH ORDINALITY AS t(c, ord) -) -WHERE EXISTS ( - SELECT 1 FROM jsonb_array_elements("contracts") AS e(c) WHERE NOT (e.c ? 'goodId') -); ---> statement-breakpoint -UPDATE "dispatches" -SET "record" = jsonb_set("record", '{contract}', pg_temp.cr007_backfill("record"->'contract')) -WHERE jsonb_typeof("record"->'contract') = 'object' AND NOT ("record"->'contract' ? 'goodId'); diff --git a/apps/server/drizzle/0006_cr009_ship_upgrades.sql b/apps/server/drizzle/0006_cr009_ship_upgrades.sql deleted file mode 100644 index 064593e..0000000 --- a/apps/server/drizzle/0006_cr009_ship_upgrades.sql +++ /dev/null @@ -1,11 +0,0 @@ -CREATE TABLE "ship_upgrades" ( - "ship_id" text NOT NULL, - "upgrade_id" text NOT NULL, - "level" integer NOT NULL, - CONSTRAINT "ship_upgrades_ship_id_upgrade_id_pk" PRIMARY KEY("ship_id","upgrade_id") -); ---> statement-breakpoint -ALTER TABLE "ships" ADD COLUMN "in_shop_until" bigint;--> statement-breakpoint -ALTER TABLE "ships" ADD COLUMN "shop_upgrade_id" text;--> statement-breakpoint -ALTER TABLE "ships" ADD COLUMN "shop_level" integer;--> statement-breakpoint -ALTER TABLE "ship_upgrades" ADD CONSTRAINT "ship_upgrades_ship_id_ships_id_fk" FOREIGN KEY ("ship_id") REFERENCES "public"."ships"("id") ON DELETE cascade ON UPDATE no action; \ No newline at end of file diff --git a/apps/server/drizzle/0007_cr010_veteran_pilot.sql b/apps/server/drizzle/0007_cr010_veteran_pilot.sql deleted file mode 100644 index cad993e..0000000 --- a/apps/server/drizzle/0007_cr010_veteran_pilot.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE "pilots" ADD COLUMN "is_veteran" boolean DEFAULT false NOT NULL; \ No newline at end of file diff --git a/apps/server/drizzle/0008_cr011_backup_codes.sql b/apps/server/drizzle/0008_cr011_backup_codes.sql deleted file mode 100644 index 9e45c7c..0000000 --- a/apps/server/drizzle/0008_cr011_backup_codes.sql +++ /dev/null @@ -1,11 +0,0 @@ -CREATE TABLE "player_logins" ( - "user_id" text PRIMARY KEY NOT NULL, - "player_id" text NOT NULL, - "created_at" bigint NOT NULL -); ---> statement-breakpoint -ALTER TABLE "players" ADD COLUMN "backup_code_hash" text;--> statement-breakpoint -ALTER TABLE "players" ADD COLUMN "backup_code_created_at" bigint;--> statement-breakpoint -ALTER TABLE "player_logins" ADD CONSTRAINT "player_logins_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "player_logins" ADD CONSTRAINT "player_logins_player_id_players_id_fk" FOREIGN KEY ("player_id") REFERENCES "public"."players"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "players" ADD CONSTRAINT "players_backup_code_hash_unique" UNIQUE("backup_code_hash"); \ No newline at end of file diff --git a/apps/server/drizzle/0009_cr013_ship_from_deal.sql b/apps/server/drizzle/0009_cr013_ship_from_deal.sql deleted file mode 100644 index 62f30a5..0000000 --- a/apps/server/drizzle/0009_cr013_ship_from_deal.sql +++ /dev/null @@ -1,5 +0,0 @@ -ALTER TABLE "ships" ADD COLUMN "from_deal" boolean DEFAULT false NOT NULL;--> statement-breakpoint --- Backfill: each player's first ship came with the deal (CR-001), not from the Market. -UPDATE "ships" SET "from_deal" = true WHERE "id" IN ( - SELECT DISTINCT ON ("player_id") "id" FROM "ships" ORDER BY "player_id", "created_at", "id" -); diff --git a/apps/server/drizzle/meta/0000_snapshot.json b/apps/server/drizzle/meta/0000_snapshot.json deleted file mode 100644 index bd3b42c..0000000 --- a/apps/server/drizzle/meta/0000_snapshot.json +++ /dev/null @@ -1,842 +0,0 @@ -{ - "id": "1390d91d-5047-43d3-984f-06f82cb52ab5", - "prevId": "00000000-0000-0000-0000-000000000000", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.accepted_contracts": { - "name": "accepted_contracts", - "schema": "", - "columns": { - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "contract_id": { - "name": "contract_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "period_index": { - "name": "period_index", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "dispatch_id": { - "name": "dispatch_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "accepted_at": { - "name": "accepted_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "accepted_contracts_player_id_players_id_fk": { - "name": "accepted_contracts_player_id_players_id_fk", - "tableFrom": "accepted_contracts", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "accepted_contracts_dispatch_id_dispatches_id_fk": { - "name": "accepted_contracts_dispatch_id_dispatches_id_fk", - "tableFrom": "accepted_contracts", - "tableTo": "dispatches", - "columnsFrom": [ - "dispatch_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "accepted_contracts_player_id_contract_id_pk": { - "name": "accepted_contracts_player_id_contract_id_pk", - "columns": [ - "player_id", - "contract_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.bays": { - "name": "bays", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "bays_player_location_idx": { - "name": "bays_player_location_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "location_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "bays_player_id_players_id_fk": { - "name": "bays_player_id_players_id_fk", - "tableFrom": "bays", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.companies": { - "name": "companies", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "credits": { - "name": "credits", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "companies_player_id_players_id_fk": { - "name": "companies_player_id_players_id_fk", - "tableFrom": "companies", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "companies_player_id_unique": { - "name": "companies_player_id_unique", - "nullsNotDistinct": false, - "columns": [ - "player_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.contract_boards": { - "name": "contract_boards", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "kind": { - "name": "kind", - "type": "board_kind", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "period_index": { - "name": "period_index", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "contracts": { - "name": "contracts", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "contract_boards_unique_idx": { - "name": "contract_boards_unique_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "kind", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "location_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "period_index", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "contract_boards_player_id_players_id_fk": { - "name": "contract_boards_player_id_players_id_fk", - "tableFrom": "contract_boards", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.discovered_locations": { - "name": "discovered_locations", - "schema": "", - "columns": { - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "discovered_at": { - "name": "discovered_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "discovered_locations_player_id_players_id_fk": { - "name": "discovered_locations_player_id_players_id_fk", - "tableFrom": "discovered_locations", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "discovered_locations_player_id_location_id_pk": { - "name": "discovered_locations_player_id_location_id_pk", - "columns": [ - "player_id", - "location_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.dispatches": { - "name": "dispatches", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "ship_id": { - "name": "ship_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "pilot_id": { - "name": "pilot_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tutorial": { - "name": "tutorial", - "type": "boolean", - "primaryKey": false, - "notNull": true - }, - "record": { - "name": "record", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "arrival_at": { - "name": "arrival_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "dispatch_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'in_flight'" - }, - "resolution": { - "name": "resolution", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "resolved_at": { - "name": "resolved_at", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "acknowledged_at": { - "name": "acknowledged_at", - "type": "bigint", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "dispatches_due_idx": { - "name": "dispatches_due_idx", - "columns": [ - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "arrival_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "dispatches_player_status_idx": { - "name": "dispatches_player_status_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "dispatches_player_id_players_id_fk": { - "name": "dispatches_player_id_players_id_fk", - "tableFrom": "dispatches", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "dispatches_ship_id_ships_id_fk": { - "name": "dispatches_ship_id_ships_id_fk", - "tableFrom": "dispatches", - "tableTo": "ships", - "columnsFrom": [ - "ship_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "dispatches_pilot_id_pilots_id_fk": { - "name": "dispatches_pilot_id_pilots_id_fk", - "tableFrom": "dispatches", - "tableTo": "pilots", - "columnsFrom": [ - "pilot_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.pilots": { - "name": "pilots", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "is_avatar": { - "name": "is_avatar", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "xp": { - "name": "xp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "skills": { - "name": "skills", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'::jsonb" - }, - "skill_points_spent": { - "name": "skill_points_spent", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "assigned_ship_id": { - "name": "assigned_ship_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "activity_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'idle'" - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "pilots_player_idx": { - "name": "pilots_player_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "pilots_player_id_players_id_fk": { - "name": "pilots_player_id_players_id_fk", - "tableFrom": "pilots", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "pilots_assigned_ship_id_ships_id_fk": { - "name": "pilots_assigned_ship_id_ships_id_fk", - "tableFrom": "pilots", - "tableTo": "ships", - "columnsFrom": [ - "assigned_ship_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "pilots_assigned_ship_id_unique": { - "name": "pilots_assigned_ship_id_unique", - "nullsNotDistinct": false, - "columns": [ - "assigned_ship_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.players": { - "name": "players", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "world_seed": { - "name": "world_seed", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "tutorial_active": { - "name": "tutorial_active", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "loaner_location_id": { - "name": "loaner_location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.ships": { - "name": "ships", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "model_id": { - "name": "model_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "activity_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'idle'" - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "ships_player_idx": { - "name": "ships_player_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "ships_player_id_players_id_fk": { - "name": "ships_player_id_players_id_fk", - "tableFrom": "ships", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": { - "public.activity_status": { - "name": "activity_status", - "schema": "public", - "values": [ - "idle", - "in_flight" - ] - }, - "public.board_kind": { - "name": "board_kind", - "schema": "public", - "values": [ - "bay", - "tutorial" - ] - }, - "public.dispatch_status": { - "name": "dispatch_status", - "schema": "public", - "values": [ - "in_flight", - "resolved" - ] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file diff --git a/apps/server/drizzle/meta/0001_snapshot.json b/apps/server/drizzle/meta/0001_snapshot.json deleted file mode 100644 index 8be32fe..0000000 --- a/apps/server/drizzle/meta/0001_snapshot.json +++ /dev/null @@ -1,1232 +0,0 @@ -{ - "id": "b5c9da12-6f56-470b-8eef-0ef12cd9d2c2", - "prevId": "1390d91d-5047-43d3-984f-06f82cb52ab5", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.accepted_contracts": { - "name": "accepted_contracts", - "schema": "", - "columns": { - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "contract_id": { - "name": "contract_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "period_index": { - "name": "period_index", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "dispatch_id": { - "name": "dispatch_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "accepted_at": { - "name": "accepted_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "accepted_contracts_player_id_players_id_fk": { - "name": "accepted_contracts_player_id_players_id_fk", - "tableFrom": "accepted_contracts", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "accepted_contracts_dispatch_id_dispatches_id_fk": { - "name": "accepted_contracts_dispatch_id_dispatches_id_fk", - "tableFrom": "accepted_contracts", - "tableTo": "dispatches", - "columnsFrom": [ - "dispatch_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "accepted_contracts_player_id_contract_id_pk": { - "name": "accepted_contracts_player_id_contract_id_pk", - "columns": [ - "player_id", - "contract_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.bays": { - "name": "bays", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "bays_player_location_idx": { - "name": "bays_player_location_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "location_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "bays_player_id_players_id_fk": { - "name": "bays_player_id_players_id_fk", - "tableFrom": "bays", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.companies": { - "name": "companies", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "credits": { - "name": "credits", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "companies_player_id_players_id_fk": { - "name": "companies_player_id_players_id_fk", - "tableFrom": "companies", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "companies_player_id_unique": { - "name": "companies_player_id_unique", - "nullsNotDistinct": false, - "columns": [ - "player_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.contract_boards": { - "name": "contract_boards", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "kind": { - "name": "kind", - "type": "board_kind", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "period_index": { - "name": "period_index", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "contracts": { - "name": "contracts", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "contract_boards_unique_idx": { - "name": "contract_boards_unique_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "kind", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "location_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "period_index", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "contract_boards_player_id_players_id_fk": { - "name": "contract_boards_player_id_players_id_fk", - "tableFrom": "contract_boards", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.discovered_locations": { - "name": "discovered_locations", - "schema": "", - "columns": { - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "discovered_at": { - "name": "discovered_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "discovered_locations_player_id_players_id_fk": { - "name": "discovered_locations_player_id_players_id_fk", - "tableFrom": "discovered_locations", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "discovered_locations_player_id_location_id_pk": { - "name": "discovered_locations_player_id_location_id_pk", - "columns": [ - "player_id", - "location_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.dispatches": { - "name": "dispatches", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "ship_id": { - "name": "ship_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "pilot_id": { - "name": "pilot_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tutorial": { - "name": "tutorial", - "type": "boolean", - "primaryKey": false, - "notNull": true - }, - "record": { - "name": "record", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "arrival_at": { - "name": "arrival_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "dispatch_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'in_flight'" - }, - "resolution": { - "name": "resolution", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "resolved_at": { - "name": "resolved_at", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "acknowledged_at": { - "name": "acknowledged_at", - "type": "bigint", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "dispatches_due_idx": { - "name": "dispatches_due_idx", - "columns": [ - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "arrival_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "dispatches_player_status_idx": { - "name": "dispatches_player_status_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "dispatches_player_id_players_id_fk": { - "name": "dispatches_player_id_players_id_fk", - "tableFrom": "dispatches", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "dispatches_ship_id_ships_id_fk": { - "name": "dispatches_ship_id_ships_id_fk", - "tableFrom": "dispatches", - "tableTo": "ships", - "columnsFrom": [ - "ship_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "dispatches_pilot_id_pilots_id_fk": { - "name": "dispatches_pilot_id_pilots_id_fk", - "tableFrom": "dispatches", - "tableTo": "pilots", - "columnsFrom": [ - "pilot_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.pilots": { - "name": "pilots", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "is_avatar": { - "name": "is_avatar", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "xp": { - "name": "xp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "skills": { - "name": "skills", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'::jsonb" - }, - "skill_points_spent": { - "name": "skill_points_spent", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "assigned_ship_id": { - "name": "assigned_ship_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "activity_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'idle'" - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "pilots_player_idx": { - "name": "pilots_player_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "pilots_player_id_players_id_fk": { - "name": "pilots_player_id_players_id_fk", - "tableFrom": "pilots", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "pilots_assigned_ship_id_ships_id_fk": { - "name": "pilots_assigned_ship_id_ships_id_fk", - "tableFrom": "pilots", - "tableTo": "ships", - "columnsFrom": [ - "assigned_ship_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "pilots_assigned_ship_id_unique": { - "name": "pilots_assigned_ship_id_unique", - "nullsNotDistinct": false, - "columns": [ - "assigned_ship_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.players": { - "name": "players", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "world_seed": { - "name": "world_seed", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "tutorial_active": { - "name": "tutorial_active", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "loaner_location_id": { - "name": "loaner_location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "players_user_id_user_id_fk": { - "name": "players_user_id_user_id_fk", - "tableFrom": "players", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "players_user_id_unique": { - "name": "players_user_id_unique", - "nullsNotDistinct": false, - "columns": [ - "user_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.ships": { - "name": "ships", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "model_id": { - "name": "model_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "activity_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'idle'" - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "ships_player_idx": { - "name": "ships_player_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "ships_player_id_players_id_fk": { - "name": "ships_player_id_players_id_fk", - "tableFrom": "ships", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "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.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.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()" - }, - "is_anonymous": { - "name": "is_anonymous", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - } - }, - "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": { - "public.activity_status": { - "name": "activity_status", - "schema": "public", - "values": [ - "idle", - "in_flight" - ] - }, - "public.board_kind": { - "name": "board_kind", - "schema": "public", - "values": [ - "bay", - "tutorial" - ] - }, - "public.dispatch_status": { - "name": "dispatch_status", - "schema": "public", - "values": [ - "in_flight", - "resolved" - ] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file diff --git a/apps/server/drizzle/meta/0002_snapshot.json b/apps/server/drizzle/meta/0002_snapshot.json deleted file mode 100644 index 1c3d858..0000000 --- a/apps/server/drizzle/meta/0002_snapshot.json +++ /dev/null @@ -1,1319 +0,0 @@ -{ - "id": "ee1be5c3-4dc4-417d-9b1a-dac302bb4a2c", - "prevId": "b5c9da12-6f56-470b-8eef-0ef12cd9d2c2", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.accepted_contracts": { - "name": "accepted_contracts", - "schema": "", - "columns": { - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "contract_id": { - "name": "contract_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "period_index": { - "name": "period_index", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "dispatch_id": { - "name": "dispatch_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "accepted_at": { - "name": "accepted_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "accepted_contracts_player_id_players_id_fk": { - "name": "accepted_contracts_player_id_players_id_fk", - "tableFrom": "accepted_contracts", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "accepted_contracts_dispatch_id_dispatches_id_fk": { - "name": "accepted_contracts_dispatch_id_dispatches_id_fk", - "tableFrom": "accepted_contracts", - "tableTo": "dispatches", - "columnsFrom": [ - "dispatch_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "accepted_contracts_player_id_contract_id_pk": { - "name": "accepted_contracts_player_id_contract_id_pk", - "columns": [ - "player_id", - "contract_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.bays": { - "name": "bays", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "bays_player_location_idx": { - "name": "bays_player_location_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "location_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "bays_player_id_players_id_fk": { - "name": "bays_player_id_players_id_fk", - "tableFrom": "bays", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.claimed_goals": { - "name": "claimed_goals", - "schema": "", - "columns": { - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "goal_id": { - "name": "goal_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "claimed_at": { - "name": "claimed_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "claimed_goals_player_id_players_id_fk": { - "name": "claimed_goals_player_id_players_id_fk", - "tableFrom": "claimed_goals", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "claimed_goals_player_id_goal_id_pk": { - "name": "claimed_goals_player_id_goal_id_pk", - "columns": [ - "player_id", - "goal_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.companies": { - "name": "companies", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "credits": { - "name": "credits", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "loan_balance": { - "name": "loan_balance", - "type": "bigint", - "primaryKey": false, - "notNull": true, - "default": 0 - } - }, - "indexes": {}, - "foreignKeys": { - "companies_player_id_players_id_fk": { - "name": "companies_player_id_players_id_fk", - "tableFrom": "companies", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "companies_player_id_unique": { - "name": "companies_player_id_unique", - "nullsNotDistinct": false, - "columns": [ - "player_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.contract_boards": { - "name": "contract_boards", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "kind": { - "name": "kind", - "type": "board_kind", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "period_index": { - "name": "period_index", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "tutorial_run": { - "name": "tutorial_run", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "contracts": { - "name": "contracts", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "contract_boards_unique_idx": { - "name": "contract_boards_unique_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "kind", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "location_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "period_index", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "tutorial_run", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "contract_boards_player_id_players_id_fk": { - "name": "contract_boards_player_id_players_id_fk", - "tableFrom": "contract_boards", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.discovered_locations": { - "name": "discovered_locations", - "schema": "", - "columns": { - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "discovered_at": { - "name": "discovered_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "discovered_locations_player_id_players_id_fk": { - "name": "discovered_locations_player_id_players_id_fk", - "tableFrom": "discovered_locations", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "discovered_locations_player_id_location_id_pk": { - "name": "discovered_locations_player_id_location_id_pk", - "columns": [ - "player_id", - "location_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.dispatches": { - "name": "dispatches", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "ship_id": { - "name": "ship_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "pilot_id": { - "name": "pilot_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tutorial": { - "name": "tutorial", - "type": "boolean", - "primaryKey": false, - "notNull": true - }, - "record": { - "name": "record", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "arrival_at": { - "name": "arrival_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "dispatch_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'in_flight'" - }, - "resolution": { - "name": "resolution", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "resolved_at": { - "name": "resolved_at", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "acknowledged_at": { - "name": "acknowledged_at", - "type": "bigint", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "dispatches_due_idx": { - "name": "dispatches_due_idx", - "columns": [ - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "arrival_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "dispatches_player_status_idx": { - "name": "dispatches_player_status_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "dispatches_player_id_players_id_fk": { - "name": "dispatches_player_id_players_id_fk", - "tableFrom": "dispatches", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "dispatches_ship_id_ships_id_fk": { - "name": "dispatches_ship_id_ships_id_fk", - "tableFrom": "dispatches", - "tableTo": "ships", - "columnsFrom": [ - "ship_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "dispatches_pilot_id_pilots_id_fk": { - "name": "dispatches_pilot_id_pilots_id_fk", - "tableFrom": "dispatches", - "tableTo": "pilots", - "columnsFrom": [ - "pilot_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.pilots": { - "name": "pilots", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "is_avatar": { - "name": "is_avatar", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "xp": { - "name": "xp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "skills": { - "name": "skills", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'::jsonb" - }, - "skill_points_spent": { - "name": "skill_points_spent", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "assigned_ship_id": { - "name": "assigned_ship_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "activity_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'idle'" - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "pilots_player_idx": { - "name": "pilots_player_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "pilots_player_id_players_id_fk": { - "name": "pilots_player_id_players_id_fk", - "tableFrom": "pilots", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "pilots_assigned_ship_id_ships_id_fk": { - "name": "pilots_assigned_ship_id_ships_id_fk", - "tableFrom": "pilots", - "tableTo": "ships", - "columnsFrom": [ - "assigned_ship_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "pilots_assigned_ship_id_unique": { - "name": "pilots_assigned_ship_id_unique", - "nullsNotDistinct": false, - "columns": [ - "assigned_ship_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.players": { - "name": "players", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "world_seed": { - "name": "world_seed", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "tutorial_active": { - "name": "tutorial_active", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "loaner_location_id": { - "name": "loaner_location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tutorial_runs_completed": { - "name": "tutorial_runs_completed", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "deal_accepted": { - "name": "deal_accepted", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "players_user_id_user_id_fk": { - "name": "players_user_id_user_id_fk", - "tableFrom": "players", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "players_user_id_unique": { - "name": "players_user_id_unique", - "nullsNotDistinct": false, - "columns": [ - "user_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.ships": { - "name": "ships", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "model_id": { - "name": "model_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "activity_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'idle'" - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "ships_player_idx": { - "name": "ships_player_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "ships_player_id_players_id_fk": { - "name": "ships_player_id_players_id_fk", - "tableFrom": "ships", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "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.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.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()" - }, - "is_anonymous": { - "name": "is_anonymous", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - } - }, - "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": { - "public.activity_status": { - "name": "activity_status", - "schema": "public", - "values": [ - "idle", - "in_flight" - ] - }, - "public.board_kind": { - "name": "board_kind", - "schema": "public", - "values": [ - "bay", - "tutorial" - ] - }, - "public.dispatch_status": { - "name": "dispatch_status", - "schema": "public", - "values": [ - "in_flight", - "resolved" - ] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file diff --git a/apps/server/drizzle/meta/0003_snapshot.json b/apps/server/drizzle/meta/0003_snapshot.json deleted file mode 100644 index 20a121d..0000000 --- a/apps/server/drizzle/meta/0003_snapshot.json +++ /dev/null @@ -1,1319 +0,0 @@ -{ - "id": "85b1ffb1-0667-4aee-89aa-5d9daf642cbb", - "prevId": "ee1be5c3-4dc4-417d-9b1a-dac302bb4a2c", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.accepted_contracts": { - "name": "accepted_contracts", - "schema": "", - "columns": { - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "contract_id": { - "name": "contract_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "period_index": { - "name": "period_index", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "dispatch_id": { - "name": "dispatch_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "accepted_at": { - "name": "accepted_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "accepted_contracts_player_id_players_id_fk": { - "name": "accepted_contracts_player_id_players_id_fk", - "tableFrom": "accepted_contracts", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "accepted_contracts_dispatch_id_dispatches_id_fk": { - "name": "accepted_contracts_dispatch_id_dispatches_id_fk", - "tableFrom": "accepted_contracts", - "tableTo": "dispatches", - "columnsFrom": [ - "dispatch_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "accepted_contracts_player_id_contract_id_pk": { - "name": "accepted_contracts_player_id_contract_id_pk", - "columns": [ - "player_id", - "contract_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.bays": { - "name": "bays", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "bays_player_location_idx": { - "name": "bays_player_location_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "location_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "bays_player_id_players_id_fk": { - "name": "bays_player_id_players_id_fk", - "tableFrom": "bays", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.claimed_goals": { - "name": "claimed_goals", - "schema": "", - "columns": { - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "goal_id": { - "name": "goal_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "claimed_at": { - "name": "claimed_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "claimed_goals_player_id_players_id_fk": { - "name": "claimed_goals_player_id_players_id_fk", - "tableFrom": "claimed_goals", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "claimed_goals_player_id_goal_id_pk": { - "name": "claimed_goals_player_id_goal_id_pk", - "columns": [ - "player_id", - "goal_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.companies": { - "name": "companies", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "credits": { - "name": "credits", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "loan_balance": { - "name": "loan_balance", - "type": "bigint", - "primaryKey": false, - "notNull": true, - "default": 0 - } - }, - "indexes": {}, - "foreignKeys": { - "companies_player_id_players_id_fk": { - "name": "companies_player_id_players_id_fk", - "tableFrom": "companies", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "companies_player_id_unique": { - "name": "companies_player_id_unique", - "nullsNotDistinct": false, - "columns": [ - "player_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.contract_boards": { - "name": "contract_boards", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "kind": { - "name": "kind", - "type": "board_kind", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "period_index": { - "name": "period_index", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "tutorial_run": { - "name": "tutorial_run", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "contracts": { - "name": "contracts", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "contract_boards_unique_idx": { - "name": "contract_boards_unique_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "kind", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "location_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "period_index", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "tutorial_run", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "contract_boards_player_id_players_id_fk": { - "name": "contract_boards_player_id_players_id_fk", - "tableFrom": "contract_boards", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.discovered_locations": { - "name": "discovered_locations", - "schema": "", - "columns": { - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "discovered_at": { - "name": "discovered_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "discovered_locations_player_id_players_id_fk": { - "name": "discovered_locations_player_id_players_id_fk", - "tableFrom": "discovered_locations", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "discovered_locations_player_id_location_id_pk": { - "name": "discovered_locations_player_id_location_id_pk", - "columns": [ - "player_id", - "location_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.dispatches": { - "name": "dispatches", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "ship_id": { - "name": "ship_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "pilot_id": { - "name": "pilot_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tutorial": { - "name": "tutorial", - "type": "boolean", - "primaryKey": false, - "notNull": true - }, - "record": { - "name": "record", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "arrival_at": { - "name": "arrival_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "dispatch_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'in_flight'" - }, - "resolution": { - "name": "resolution", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "resolved_at": { - "name": "resolved_at", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "acknowledged_at": { - "name": "acknowledged_at", - "type": "bigint", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "dispatches_due_idx": { - "name": "dispatches_due_idx", - "columns": [ - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "arrival_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "dispatches_player_status_idx": { - "name": "dispatches_player_status_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "dispatches_player_id_players_id_fk": { - "name": "dispatches_player_id_players_id_fk", - "tableFrom": "dispatches", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "dispatches_ship_id_ships_id_fk": { - "name": "dispatches_ship_id_ships_id_fk", - "tableFrom": "dispatches", - "tableTo": "ships", - "columnsFrom": [ - "ship_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "dispatches_pilot_id_pilots_id_fk": { - "name": "dispatches_pilot_id_pilots_id_fk", - "tableFrom": "dispatches", - "tableTo": "pilots", - "columnsFrom": [ - "pilot_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.pilots": { - "name": "pilots", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "is_avatar": { - "name": "is_avatar", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "xp": { - "name": "xp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "skills": { - "name": "skills", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'::jsonb" - }, - "skill_points_spent": { - "name": "skill_points_spent", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "assigned_ship_id": { - "name": "assigned_ship_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "activity_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'idle'" - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "pilots_player_idx": { - "name": "pilots_player_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "pilots_player_id_players_id_fk": { - "name": "pilots_player_id_players_id_fk", - "tableFrom": "pilots", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "pilots_assigned_ship_id_ships_id_fk": { - "name": "pilots_assigned_ship_id_ships_id_fk", - "tableFrom": "pilots", - "tableTo": "ships", - "columnsFrom": [ - "assigned_ship_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "pilots_assigned_ship_id_unique": { - "name": "pilots_assigned_ship_id_unique", - "nullsNotDistinct": false, - "columns": [ - "assigned_ship_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.players": { - "name": "players", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "world_seed": { - "name": "world_seed", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "tutorial_active": { - "name": "tutorial_active", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "loaner_location_id": { - "name": "loaner_location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tutorial_runs_completed": { - "name": "tutorial_runs_completed", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "deal_accepted": { - "name": "deal_accepted", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "players_user_id_user_id_fk": { - "name": "players_user_id_user_id_fk", - "tableFrom": "players", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "players_user_id_unique": { - "name": "players_user_id_unique", - "nullsNotDistinct": false, - "columns": [ - "user_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.ships": { - "name": "ships", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "model_id": { - "name": "model_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "activity_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'idle'" - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "ships_player_idx": { - "name": "ships_player_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "ships_player_id_players_id_fk": { - "name": "ships_player_id_players_id_fk", - "tableFrom": "ships", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "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.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.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()" - }, - "is_anonymous": { - "name": "is_anonymous", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - } - }, - "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": { - "public.activity_status": { - "name": "activity_status", - "schema": "public", - "values": [ - "idle", - "in_flight" - ] - }, - "public.board_kind": { - "name": "board_kind", - "schema": "public", - "values": [ - "location", - "tutorial" - ] - }, - "public.dispatch_status": { - "name": "dispatch_status", - "schema": "public", - "values": [ - "in_flight", - "resolved" - ] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file diff --git a/apps/server/drizzle/meta/0004_snapshot.json b/apps/server/drizzle/meta/0004_snapshot.json deleted file mode 100644 index 89e33d3..0000000 --- a/apps/server/drizzle/meta/0004_snapshot.json +++ /dev/null @@ -1,1332 +0,0 @@ -{ - "id": "e8a22e4a-d8ea-4ccf-a64b-5e4fd9fdea92", - "prevId": "85b1ffb1-0667-4aee-89aa-5d9daf642cbb", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.accepted_contracts": { - "name": "accepted_contracts", - "schema": "", - "columns": { - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "contract_id": { - "name": "contract_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "period_index": { - "name": "period_index", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "dispatch_id": { - "name": "dispatch_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "accepted_at": { - "name": "accepted_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "accepted_contracts_player_id_players_id_fk": { - "name": "accepted_contracts_player_id_players_id_fk", - "tableFrom": "accepted_contracts", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "accepted_contracts_dispatch_id_dispatches_id_fk": { - "name": "accepted_contracts_dispatch_id_dispatches_id_fk", - "tableFrom": "accepted_contracts", - "tableTo": "dispatches", - "columnsFrom": [ - "dispatch_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "accepted_contracts_player_id_contract_id_pk": { - "name": "accepted_contracts_player_id_contract_id_pk", - "columns": [ - "player_id", - "contract_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.bays": { - "name": "bays", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "bays_player_location_idx": { - "name": "bays_player_location_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "location_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "bays_player_id_players_id_fk": { - "name": "bays_player_id_players_id_fk", - "tableFrom": "bays", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.claimed_goals": { - "name": "claimed_goals", - "schema": "", - "columns": { - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "goal_id": { - "name": "goal_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "claimed_at": { - "name": "claimed_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "claimed_goals_player_id_players_id_fk": { - "name": "claimed_goals_player_id_players_id_fk", - "tableFrom": "claimed_goals", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "claimed_goals_player_id_goal_id_pk": { - "name": "claimed_goals_player_id_goal_id_pk", - "columns": [ - "player_id", - "goal_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.companies": { - "name": "companies", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "credits": { - "name": "credits", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "loan_balance": { - "name": "loan_balance", - "type": "bigint", - "primaryKey": false, - "notNull": true, - "default": 0 - } - }, - "indexes": {}, - "foreignKeys": { - "companies_player_id_players_id_fk": { - "name": "companies_player_id_players_id_fk", - "tableFrom": "companies", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "companies_player_id_unique": { - "name": "companies_player_id_unique", - "nullsNotDistinct": false, - "columns": [ - "player_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.contract_boards": { - "name": "contract_boards", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "kind": { - "name": "kind", - "type": "board_kind", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "period_index": { - "name": "period_index", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "tutorial_run": { - "name": "tutorial_run", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "contracts": { - "name": "contracts", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "contract_boards_unique_idx": { - "name": "contract_boards_unique_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "kind", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "location_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "period_index", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "tutorial_run", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "contract_boards_player_id_players_id_fk": { - "name": "contract_boards_player_id_players_id_fk", - "tableFrom": "contract_boards", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.discovered_locations": { - "name": "discovered_locations", - "schema": "", - "columns": { - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "discovered_at": { - "name": "discovered_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "discovered_locations_player_id_players_id_fk": { - "name": "discovered_locations_player_id_players_id_fk", - "tableFrom": "discovered_locations", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "discovered_locations_player_id_location_id_pk": { - "name": "discovered_locations_player_id_location_id_pk", - "columns": [ - "player_id", - "location_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.dispatches": { - "name": "dispatches", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "ship_id": { - "name": "ship_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "pilot_id": { - "name": "pilot_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tutorial": { - "name": "tutorial", - "type": "boolean", - "primaryKey": false, - "notNull": true - }, - "record": { - "name": "record", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "arrival_at": { - "name": "arrival_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "dispatch_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'in_flight'" - }, - "resolution": { - "name": "resolution", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "resolved_at": { - "name": "resolved_at", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "acknowledged_at": { - "name": "acknowledged_at", - "type": "bigint", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "dispatches_due_idx": { - "name": "dispatches_due_idx", - "columns": [ - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "arrival_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "dispatches_player_status_idx": { - "name": "dispatches_player_status_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "dispatches_player_id_players_id_fk": { - "name": "dispatches_player_id_players_id_fk", - "tableFrom": "dispatches", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "dispatches_ship_id_ships_id_fk": { - "name": "dispatches_ship_id_ships_id_fk", - "tableFrom": "dispatches", - "tableTo": "ships", - "columnsFrom": [ - "ship_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "dispatches_pilot_id_pilots_id_fk": { - "name": "dispatches_pilot_id_pilots_id_fk", - "tableFrom": "dispatches", - "tableTo": "pilots", - "columnsFrom": [ - "pilot_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.pilots": { - "name": "pilots", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "is_avatar": { - "name": "is_avatar", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "xp": { - "name": "xp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "skills": { - "name": "skills", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'::jsonb" - }, - "skill_points_spent": { - "name": "skill_points_spent", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "assigned_ship_id": { - "name": "assigned_ship_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "activity_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'idle'" - }, - "duty_hours": { - "name": "duty_hours", - "type": "double precision", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "rest_ends_at": { - "name": "rest_ends_at", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "pilots_player_idx": { - "name": "pilots_player_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "pilots_player_id_players_id_fk": { - "name": "pilots_player_id_players_id_fk", - "tableFrom": "pilots", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "pilots_assigned_ship_id_ships_id_fk": { - "name": "pilots_assigned_ship_id_ships_id_fk", - "tableFrom": "pilots", - "tableTo": "ships", - "columnsFrom": [ - "assigned_ship_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "pilots_assigned_ship_id_unique": { - "name": "pilots_assigned_ship_id_unique", - "nullsNotDistinct": false, - "columns": [ - "assigned_ship_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.players": { - "name": "players", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "world_seed": { - "name": "world_seed", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "tutorial_active": { - "name": "tutorial_active", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "loaner_location_id": { - "name": "loaner_location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tutorial_runs_completed": { - "name": "tutorial_runs_completed", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "deal_accepted": { - "name": "deal_accepted", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "players_user_id_user_id_fk": { - "name": "players_user_id_user_id_fk", - "tableFrom": "players", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "players_user_id_unique": { - "name": "players_user_id_unique", - "nullsNotDistinct": false, - "columns": [ - "user_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.ships": { - "name": "ships", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "model_id": { - "name": "model_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "activity_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'idle'" - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "ships_player_idx": { - "name": "ships_player_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "ships_player_id_players_id_fk": { - "name": "ships_player_id_players_id_fk", - "tableFrom": "ships", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "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.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.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()" - }, - "is_anonymous": { - "name": "is_anonymous", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - } - }, - "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": { - "public.activity_status": { - "name": "activity_status", - "schema": "public", - "values": [ - "idle", - "in_flight" - ] - }, - "public.board_kind": { - "name": "board_kind", - "schema": "public", - "values": [ - "location", - "tutorial" - ] - }, - "public.dispatch_status": { - "name": "dispatch_status", - "schema": "public", - "values": [ - "in_flight", - "resolved" - ] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file diff --git a/apps/server/drizzle/meta/0005_snapshot.json b/apps/server/drizzle/meta/0005_snapshot.json deleted file mode 100644 index eff356e..0000000 --- a/apps/server/drizzle/meta/0005_snapshot.json +++ /dev/null @@ -1,1332 +0,0 @@ -{ - "id": "412112de-94d1-4792-9691-27058a4e089b", - "prevId": "e8a22e4a-d8ea-4ccf-a64b-5e4fd9fdea92", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.accepted_contracts": { - "name": "accepted_contracts", - "schema": "", - "columns": { - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "contract_id": { - "name": "contract_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "period_index": { - "name": "period_index", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "dispatch_id": { - "name": "dispatch_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "accepted_at": { - "name": "accepted_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "accepted_contracts_player_id_players_id_fk": { - "name": "accepted_contracts_player_id_players_id_fk", - "tableFrom": "accepted_contracts", - "columnsFrom": [ - "player_id" - ], - "tableTo": "players", - "columnsTo": [ - "id" - ], - "onUpdate": "no action", - "onDelete": "cascade" - }, - "accepted_contracts_dispatch_id_dispatches_id_fk": { - "name": "accepted_contracts_dispatch_id_dispatches_id_fk", - "tableFrom": "accepted_contracts", - "columnsFrom": [ - "dispatch_id" - ], - "tableTo": "dispatches", - "columnsTo": [ - "id" - ], - "onUpdate": "no action", - "onDelete": "cascade" - } - }, - "compositePrimaryKeys": { - "accepted_contracts_player_id_contract_id_pk": { - "name": "accepted_contracts_player_id_contract_id_pk", - "columns": [ - "player_id", - "contract_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.bays": { - "name": "bays", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "bays_player_location_idx": { - "name": "bays_player_location_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "location_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "with": {}, - "method": "btree", - "concurrently": false - } - }, - "foreignKeys": { - "bays_player_id_players_id_fk": { - "name": "bays_player_id_players_id_fk", - "tableFrom": "bays", - "columnsFrom": [ - "player_id" - ], - "tableTo": "players", - "columnsTo": [ - "id" - ], - "onUpdate": "no action", - "onDelete": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.claimed_goals": { - "name": "claimed_goals", - "schema": "", - "columns": { - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "goal_id": { - "name": "goal_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "claimed_at": { - "name": "claimed_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "claimed_goals_player_id_players_id_fk": { - "name": "claimed_goals_player_id_players_id_fk", - "tableFrom": "claimed_goals", - "columnsFrom": [ - "player_id" - ], - "tableTo": "players", - "columnsTo": [ - "id" - ], - "onUpdate": "no action", - "onDelete": "cascade" - } - }, - "compositePrimaryKeys": { - "claimed_goals_player_id_goal_id_pk": { - "name": "claimed_goals_player_id_goal_id_pk", - "columns": [ - "player_id", - "goal_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.companies": { - "name": "companies", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "credits": { - "name": "credits", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "loan_balance": { - "name": "loan_balance", - "type": "bigint", - "primaryKey": false, - "notNull": true, - "default": 0 - } - }, - "indexes": {}, - "foreignKeys": { - "companies_player_id_players_id_fk": { - "name": "companies_player_id_players_id_fk", - "tableFrom": "companies", - "columnsFrom": [ - "player_id" - ], - "tableTo": "players", - "columnsTo": [ - "id" - ], - "onUpdate": "no action", - "onDelete": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "companies_player_id_unique": { - "name": "companies_player_id_unique", - "columns": [ - "player_id" - ], - "nullsNotDistinct": false - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.contract_boards": { - "name": "contract_boards", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "kind": { - "name": "kind", - "type": "board_kind", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "period_index": { - "name": "period_index", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "tutorial_run": { - "name": "tutorial_run", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "contracts": { - "name": "contracts", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "contract_boards_unique_idx": { - "name": "contract_boards_unique_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "kind", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "location_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "period_index", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "tutorial_run", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "with": {}, - "method": "btree", - "concurrently": false - } - }, - "foreignKeys": { - "contract_boards_player_id_players_id_fk": { - "name": "contract_boards_player_id_players_id_fk", - "tableFrom": "contract_boards", - "columnsFrom": [ - "player_id" - ], - "tableTo": "players", - "columnsTo": [ - "id" - ], - "onUpdate": "no action", - "onDelete": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.discovered_locations": { - "name": "discovered_locations", - "schema": "", - "columns": { - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "discovered_at": { - "name": "discovered_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "discovered_locations_player_id_players_id_fk": { - "name": "discovered_locations_player_id_players_id_fk", - "tableFrom": "discovered_locations", - "columnsFrom": [ - "player_id" - ], - "tableTo": "players", - "columnsTo": [ - "id" - ], - "onUpdate": "no action", - "onDelete": "cascade" - } - }, - "compositePrimaryKeys": { - "discovered_locations_player_id_location_id_pk": { - "name": "discovered_locations_player_id_location_id_pk", - "columns": [ - "player_id", - "location_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.dispatches": { - "name": "dispatches", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "ship_id": { - "name": "ship_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "pilot_id": { - "name": "pilot_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tutorial": { - "name": "tutorial", - "type": "boolean", - "primaryKey": false, - "notNull": true - }, - "record": { - "name": "record", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "arrival_at": { - "name": "arrival_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "dispatch_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'in_flight'" - }, - "resolution": { - "name": "resolution", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "resolved_at": { - "name": "resolved_at", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "acknowledged_at": { - "name": "acknowledged_at", - "type": "bigint", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "dispatches_due_idx": { - "name": "dispatches_due_idx", - "columns": [ - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "arrival_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "with": {}, - "method": "btree", - "concurrently": false - }, - "dispatches_player_status_idx": { - "name": "dispatches_player_status_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "with": {}, - "method": "btree", - "concurrently": false - } - }, - "foreignKeys": { - "dispatches_player_id_players_id_fk": { - "name": "dispatches_player_id_players_id_fk", - "tableFrom": "dispatches", - "columnsFrom": [ - "player_id" - ], - "tableTo": "players", - "columnsTo": [ - "id" - ], - "onUpdate": "no action", - "onDelete": "cascade" - }, - "dispatches_ship_id_ships_id_fk": { - "name": "dispatches_ship_id_ships_id_fk", - "tableFrom": "dispatches", - "columnsFrom": [ - "ship_id" - ], - "tableTo": "ships", - "columnsTo": [ - "id" - ], - "onUpdate": "no action", - "onDelete": "set null" - }, - "dispatches_pilot_id_pilots_id_fk": { - "name": "dispatches_pilot_id_pilots_id_fk", - "tableFrom": "dispatches", - "columnsFrom": [ - "pilot_id" - ], - "tableTo": "pilots", - "columnsTo": [ - "id" - ], - "onUpdate": "no action", - "onDelete": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.pilots": { - "name": "pilots", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "is_avatar": { - "name": "is_avatar", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "xp": { - "name": "xp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "skills": { - "name": "skills", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'::jsonb" - }, - "skill_points_spent": { - "name": "skill_points_spent", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "assigned_ship_id": { - "name": "assigned_ship_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "activity_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'idle'" - }, - "duty_hours": { - "name": "duty_hours", - "type": "double precision", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "rest_ends_at": { - "name": "rest_ends_at", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "pilots_player_idx": { - "name": "pilots_player_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "with": {}, - "method": "btree", - "concurrently": false - } - }, - "foreignKeys": { - "pilots_player_id_players_id_fk": { - "name": "pilots_player_id_players_id_fk", - "tableFrom": "pilots", - "columnsFrom": [ - "player_id" - ], - "tableTo": "players", - "columnsTo": [ - "id" - ], - "onUpdate": "no action", - "onDelete": "cascade" - }, - "pilots_assigned_ship_id_ships_id_fk": { - "name": "pilots_assigned_ship_id_ships_id_fk", - "tableFrom": "pilots", - "columnsFrom": [ - "assigned_ship_id" - ], - "tableTo": "ships", - "columnsTo": [ - "id" - ], - "onUpdate": "no action", - "onDelete": "set null" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "pilots_assigned_ship_id_unique": { - "name": "pilots_assigned_ship_id_unique", - "columns": [ - "assigned_ship_id" - ], - "nullsNotDistinct": false - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.players": { - "name": "players", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "world_seed": { - "name": "world_seed", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "tutorial_active": { - "name": "tutorial_active", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "loaner_location_id": { - "name": "loaner_location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tutorial_runs_completed": { - "name": "tutorial_runs_completed", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "deal_accepted": { - "name": "deal_accepted", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "players_user_id_user_id_fk": { - "name": "players_user_id_user_id_fk", - "tableFrom": "players", - "columnsFrom": [ - "user_id" - ], - "tableTo": "user", - "columnsTo": [ - "id" - ], - "onUpdate": "no action", - "onDelete": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "players_user_id_unique": { - "name": "players_user_id_unique", - "columns": [ - "user_id" - ], - "nullsNotDistinct": false - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.ships": { - "name": "ships", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "model_id": { - "name": "model_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "activity_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'idle'" - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "ships_player_idx": { - "name": "ships_player_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "with": {}, - "method": "btree", - "concurrently": false - } - }, - "foreignKeys": { - "ships_player_id_players_id_fk": { - "name": "ships_player_id_players_id_fk", - "tableFrom": "ships", - "columnsFrom": [ - "player_id" - ], - "tableTo": "players", - "columnsTo": [ - "id" - ], - "onUpdate": "no action", - "onDelete": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "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, - "with": {}, - "method": "btree", - "concurrently": false - } - }, - "foreignKeys": { - "account_user_id_user_id_fk": { - "name": "account_user_id_user_id_fk", - "tableFrom": "account", - "columnsFrom": [ - "user_id" - ], - "tableTo": "user", - "columnsTo": [ - "id" - ], - "onUpdate": "no action", - "onDelete": "cascade" - } - }, - "compositePrimaryKeys": {}, - "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, - "with": {}, - "method": "btree", - "concurrently": false - } - }, - "foreignKeys": { - "session_user_id_user_id_fk": { - "name": "session_user_id_user_id_fk", - "tableFrom": "session", - "columnsFrom": [ - "user_id" - ], - "tableTo": "user", - "columnsTo": [ - "id" - ], - "onUpdate": "no action", - "onDelete": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "session_token_unique": { - "name": "session_token_unique", - "columns": [ - "token" - ], - "nullsNotDistinct": false - } - }, - "policies": {}, - "checkConstraints": {}, - "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()" - }, - "is_anonymous": { - "name": "is_anonymous", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "user_email_unique": { - "name": "user_email_unique", - "columns": [ - "email" - ], - "nullsNotDistinct": false - } - }, - "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, - "with": {}, - "method": "btree", - "concurrently": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": { - "public.activity_status": { - "name": "activity_status", - "schema": "public", - "values": [ - "idle", - "in_flight" - ] - }, - "public.board_kind": { - "name": "board_kind", - "schema": "public", - "values": [ - "location", - "tutorial" - ] - }, - "public.dispatch_status": { - "name": "dispatch_status", - "schema": "public", - "values": [ - "in_flight", - "resolved" - ] - } - }, - "schemas": {}, - "views": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file diff --git a/apps/server/drizzle/meta/0006_snapshot.json b/apps/server/drizzle/meta/0006_snapshot.json deleted file mode 100644 index 05daa8a..0000000 --- a/apps/server/drizzle/meta/0006_snapshot.json +++ /dev/null @@ -1,1403 +0,0 @@ -{ - "id": "21666449-7a7a-42de-8e7c-bb0deec8658e", - "prevId": "412112de-94d1-4792-9691-27058a4e089b", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.accepted_contracts": { - "name": "accepted_contracts", - "schema": "", - "columns": { - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "contract_id": { - "name": "contract_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "period_index": { - "name": "period_index", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "dispatch_id": { - "name": "dispatch_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "accepted_at": { - "name": "accepted_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "accepted_contracts_player_id_players_id_fk": { - "name": "accepted_contracts_player_id_players_id_fk", - "tableFrom": "accepted_contracts", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "accepted_contracts_dispatch_id_dispatches_id_fk": { - "name": "accepted_contracts_dispatch_id_dispatches_id_fk", - "tableFrom": "accepted_contracts", - "tableTo": "dispatches", - "columnsFrom": [ - "dispatch_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "accepted_contracts_player_id_contract_id_pk": { - "name": "accepted_contracts_player_id_contract_id_pk", - "columns": [ - "player_id", - "contract_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.bays": { - "name": "bays", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "bays_player_location_idx": { - "name": "bays_player_location_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "location_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "bays_player_id_players_id_fk": { - "name": "bays_player_id_players_id_fk", - "tableFrom": "bays", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.claimed_goals": { - "name": "claimed_goals", - "schema": "", - "columns": { - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "goal_id": { - "name": "goal_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "claimed_at": { - "name": "claimed_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "claimed_goals_player_id_players_id_fk": { - "name": "claimed_goals_player_id_players_id_fk", - "tableFrom": "claimed_goals", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "claimed_goals_player_id_goal_id_pk": { - "name": "claimed_goals_player_id_goal_id_pk", - "columns": [ - "player_id", - "goal_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.companies": { - "name": "companies", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "credits": { - "name": "credits", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "loan_balance": { - "name": "loan_balance", - "type": "bigint", - "primaryKey": false, - "notNull": true, - "default": 0 - } - }, - "indexes": {}, - "foreignKeys": { - "companies_player_id_players_id_fk": { - "name": "companies_player_id_players_id_fk", - "tableFrom": "companies", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "companies_player_id_unique": { - "name": "companies_player_id_unique", - "nullsNotDistinct": false, - "columns": [ - "player_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.contract_boards": { - "name": "contract_boards", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "kind": { - "name": "kind", - "type": "board_kind", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "period_index": { - "name": "period_index", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "tutorial_run": { - "name": "tutorial_run", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "contracts": { - "name": "contracts", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "contract_boards_unique_idx": { - "name": "contract_boards_unique_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "kind", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "location_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "period_index", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "tutorial_run", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "contract_boards_player_id_players_id_fk": { - "name": "contract_boards_player_id_players_id_fk", - "tableFrom": "contract_boards", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.discovered_locations": { - "name": "discovered_locations", - "schema": "", - "columns": { - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "discovered_at": { - "name": "discovered_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "discovered_locations_player_id_players_id_fk": { - "name": "discovered_locations_player_id_players_id_fk", - "tableFrom": "discovered_locations", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "discovered_locations_player_id_location_id_pk": { - "name": "discovered_locations_player_id_location_id_pk", - "columns": [ - "player_id", - "location_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.dispatches": { - "name": "dispatches", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "ship_id": { - "name": "ship_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "pilot_id": { - "name": "pilot_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tutorial": { - "name": "tutorial", - "type": "boolean", - "primaryKey": false, - "notNull": true - }, - "record": { - "name": "record", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "arrival_at": { - "name": "arrival_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "dispatch_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'in_flight'" - }, - "resolution": { - "name": "resolution", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "resolved_at": { - "name": "resolved_at", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "acknowledged_at": { - "name": "acknowledged_at", - "type": "bigint", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "dispatches_due_idx": { - "name": "dispatches_due_idx", - "columns": [ - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "arrival_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "dispatches_player_status_idx": { - "name": "dispatches_player_status_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "dispatches_player_id_players_id_fk": { - "name": "dispatches_player_id_players_id_fk", - "tableFrom": "dispatches", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "dispatches_ship_id_ships_id_fk": { - "name": "dispatches_ship_id_ships_id_fk", - "tableFrom": "dispatches", - "tableTo": "ships", - "columnsFrom": [ - "ship_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "dispatches_pilot_id_pilots_id_fk": { - "name": "dispatches_pilot_id_pilots_id_fk", - "tableFrom": "dispatches", - "tableTo": "pilots", - "columnsFrom": [ - "pilot_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.pilots": { - "name": "pilots", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "is_avatar": { - "name": "is_avatar", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "xp": { - "name": "xp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "skills": { - "name": "skills", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'::jsonb" - }, - "skill_points_spent": { - "name": "skill_points_spent", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "assigned_ship_id": { - "name": "assigned_ship_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "activity_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'idle'" - }, - "duty_hours": { - "name": "duty_hours", - "type": "double precision", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "rest_ends_at": { - "name": "rest_ends_at", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "pilots_player_idx": { - "name": "pilots_player_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "pilots_player_id_players_id_fk": { - "name": "pilots_player_id_players_id_fk", - "tableFrom": "pilots", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "pilots_assigned_ship_id_ships_id_fk": { - "name": "pilots_assigned_ship_id_ships_id_fk", - "tableFrom": "pilots", - "tableTo": "ships", - "columnsFrom": [ - "assigned_ship_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "pilots_assigned_ship_id_unique": { - "name": "pilots_assigned_ship_id_unique", - "nullsNotDistinct": false, - "columns": [ - "assigned_ship_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.players": { - "name": "players", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "world_seed": { - "name": "world_seed", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "tutorial_active": { - "name": "tutorial_active", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "loaner_location_id": { - "name": "loaner_location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tutorial_runs_completed": { - "name": "tutorial_runs_completed", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "deal_accepted": { - "name": "deal_accepted", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "players_user_id_user_id_fk": { - "name": "players_user_id_user_id_fk", - "tableFrom": "players", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "players_user_id_unique": { - "name": "players_user_id_unique", - "nullsNotDistinct": false, - "columns": [ - "user_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.ship_upgrades": { - "name": "ship_upgrades", - "schema": "", - "columns": { - "ship_id": { - "name": "ship_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "upgrade_id": { - "name": "upgrade_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "level": { - "name": "level", - "type": "integer", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "ship_upgrades_ship_id_ships_id_fk": { - "name": "ship_upgrades_ship_id_ships_id_fk", - "tableFrom": "ship_upgrades", - "tableTo": "ships", - "columnsFrom": [ - "ship_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "ship_upgrades_ship_id_upgrade_id_pk": { - "name": "ship_upgrades_ship_id_upgrade_id_pk", - "columns": [ - "ship_id", - "upgrade_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.ships": { - "name": "ships", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "model_id": { - "name": "model_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "activity_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'idle'" - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "in_shop_until": { - "name": "in_shop_until", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "shop_upgrade_id": { - "name": "shop_upgrade_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "shop_level": { - "name": "shop_level", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "ships_player_idx": { - "name": "ships_player_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "ships_player_id_players_id_fk": { - "name": "ships_player_id_players_id_fk", - "tableFrom": "ships", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "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.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.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()" - }, - "is_anonymous": { - "name": "is_anonymous", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - } - }, - "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": { - "public.activity_status": { - "name": "activity_status", - "schema": "public", - "values": [ - "idle", - "in_flight" - ] - }, - "public.board_kind": { - "name": "board_kind", - "schema": "public", - "values": [ - "location", - "tutorial" - ] - }, - "public.dispatch_status": { - "name": "dispatch_status", - "schema": "public", - "values": [ - "in_flight", - "resolved" - ] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file diff --git a/apps/server/drizzle/meta/0007_snapshot.json b/apps/server/drizzle/meta/0007_snapshot.json deleted file mode 100644 index 00e121e..0000000 --- a/apps/server/drizzle/meta/0007_snapshot.json +++ /dev/null @@ -1,1410 +0,0 @@ -{ - "id": "4d1e7e7c-f079-46db-8c00-bfe58ba49f73", - "prevId": "21666449-7a7a-42de-8e7c-bb0deec8658e", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.accepted_contracts": { - "name": "accepted_contracts", - "schema": "", - "columns": { - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "contract_id": { - "name": "contract_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "period_index": { - "name": "period_index", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "dispatch_id": { - "name": "dispatch_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "accepted_at": { - "name": "accepted_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "accepted_contracts_player_id_players_id_fk": { - "name": "accepted_contracts_player_id_players_id_fk", - "tableFrom": "accepted_contracts", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "accepted_contracts_dispatch_id_dispatches_id_fk": { - "name": "accepted_contracts_dispatch_id_dispatches_id_fk", - "tableFrom": "accepted_contracts", - "tableTo": "dispatches", - "columnsFrom": [ - "dispatch_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "accepted_contracts_player_id_contract_id_pk": { - "name": "accepted_contracts_player_id_contract_id_pk", - "columns": [ - "player_id", - "contract_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.bays": { - "name": "bays", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "bays_player_location_idx": { - "name": "bays_player_location_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "location_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "bays_player_id_players_id_fk": { - "name": "bays_player_id_players_id_fk", - "tableFrom": "bays", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.claimed_goals": { - "name": "claimed_goals", - "schema": "", - "columns": { - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "goal_id": { - "name": "goal_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "claimed_at": { - "name": "claimed_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "claimed_goals_player_id_players_id_fk": { - "name": "claimed_goals_player_id_players_id_fk", - "tableFrom": "claimed_goals", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "claimed_goals_player_id_goal_id_pk": { - "name": "claimed_goals_player_id_goal_id_pk", - "columns": [ - "player_id", - "goal_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.companies": { - "name": "companies", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "credits": { - "name": "credits", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "loan_balance": { - "name": "loan_balance", - "type": "bigint", - "primaryKey": false, - "notNull": true, - "default": 0 - } - }, - "indexes": {}, - "foreignKeys": { - "companies_player_id_players_id_fk": { - "name": "companies_player_id_players_id_fk", - "tableFrom": "companies", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "companies_player_id_unique": { - "name": "companies_player_id_unique", - "nullsNotDistinct": false, - "columns": [ - "player_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.contract_boards": { - "name": "contract_boards", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "kind": { - "name": "kind", - "type": "board_kind", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "period_index": { - "name": "period_index", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "tutorial_run": { - "name": "tutorial_run", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "contracts": { - "name": "contracts", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "contract_boards_unique_idx": { - "name": "contract_boards_unique_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "kind", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "location_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "period_index", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "tutorial_run", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "contract_boards_player_id_players_id_fk": { - "name": "contract_boards_player_id_players_id_fk", - "tableFrom": "contract_boards", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.discovered_locations": { - "name": "discovered_locations", - "schema": "", - "columns": { - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "discovered_at": { - "name": "discovered_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "discovered_locations_player_id_players_id_fk": { - "name": "discovered_locations_player_id_players_id_fk", - "tableFrom": "discovered_locations", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "discovered_locations_player_id_location_id_pk": { - "name": "discovered_locations_player_id_location_id_pk", - "columns": [ - "player_id", - "location_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.dispatches": { - "name": "dispatches", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "ship_id": { - "name": "ship_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "pilot_id": { - "name": "pilot_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tutorial": { - "name": "tutorial", - "type": "boolean", - "primaryKey": false, - "notNull": true - }, - "record": { - "name": "record", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "arrival_at": { - "name": "arrival_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "dispatch_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'in_flight'" - }, - "resolution": { - "name": "resolution", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "resolved_at": { - "name": "resolved_at", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "acknowledged_at": { - "name": "acknowledged_at", - "type": "bigint", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "dispatches_due_idx": { - "name": "dispatches_due_idx", - "columns": [ - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "arrival_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "dispatches_player_status_idx": { - "name": "dispatches_player_status_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "dispatches_player_id_players_id_fk": { - "name": "dispatches_player_id_players_id_fk", - "tableFrom": "dispatches", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "dispatches_ship_id_ships_id_fk": { - "name": "dispatches_ship_id_ships_id_fk", - "tableFrom": "dispatches", - "tableTo": "ships", - "columnsFrom": [ - "ship_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "dispatches_pilot_id_pilots_id_fk": { - "name": "dispatches_pilot_id_pilots_id_fk", - "tableFrom": "dispatches", - "tableTo": "pilots", - "columnsFrom": [ - "pilot_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.pilots": { - "name": "pilots", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "is_avatar": { - "name": "is_avatar", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "is_veteran": { - "name": "is_veteran", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "xp": { - "name": "xp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "skills": { - "name": "skills", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'::jsonb" - }, - "skill_points_spent": { - "name": "skill_points_spent", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "assigned_ship_id": { - "name": "assigned_ship_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "activity_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'idle'" - }, - "duty_hours": { - "name": "duty_hours", - "type": "double precision", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "rest_ends_at": { - "name": "rest_ends_at", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "pilots_player_idx": { - "name": "pilots_player_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "pilots_player_id_players_id_fk": { - "name": "pilots_player_id_players_id_fk", - "tableFrom": "pilots", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "pilots_assigned_ship_id_ships_id_fk": { - "name": "pilots_assigned_ship_id_ships_id_fk", - "tableFrom": "pilots", - "tableTo": "ships", - "columnsFrom": [ - "assigned_ship_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "pilots_assigned_ship_id_unique": { - "name": "pilots_assigned_ship_id_unique", - "nullsNotDistinct": false, - "columns": [ - "assigned_ship_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.players": { - "name": "players", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "world_seed": { - "name": "world_seed", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "tutorial_active": { - "name": "tutorial_active", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "loaner_location_id": { - "name": "loaner_location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tutorial_runs_completed": { - "name": "tutorial_runs_completed", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "deal_accepted": { - "name": "deal_accepted", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "players_user_id_user_id_fk": { - "name": "players_user_id_user_id_fk", - "tableFrom": "players", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "players_user_id_unique": { - "name": "players_user_id_unique", - "nullsNotDistinct": false, - "columns": [ - "user_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.ship_upgrades": { - "name": "ship_upgrades", - "schema": "", - "columns": { - "ship_id": { - "name": "ship_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "upgrade_id": { - "name": "upgrade_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "level": { - "name": "level", - "type": "integer", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "ship_upgrades_ship_id_ships_id_fk": { - "name": "ship_upgrades_ship_id_ships_id_fk", - "tableFrom": "ship_upgrades", - "tableTo": "ships", - "columnsFrom": [ - "ship_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "ship_upgrades_ship_id_upgrade_id_pk": { - "name": "ship_upgrades_ship_id_upgrade_id_pk", - "columns": [ - "ship_id", - "upgrade_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.ships": { - "name": "ships", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "model_id": { - "name": "model_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "activity_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'idle'" - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "in_shop_until": { - "name": "in_shop_until", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "shop_upgrade_id": { - "name": "shop_upgrade_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "shop_level": { - "name": "shop_level", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "ships_player_idx": { - "name": "ships_player_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "ships_player_id_players_id_fk": { - "name": "ships_player_id_players_id_fk", - "tableFrom": "ships", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "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.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.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()" - }, - "is_anonymous": { - "name": "is_anonymous", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - } - }, - "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": { - "public.activity_status": { - "name": "activity_status", - "schema": "public", - "values": [ - "idle", - "in_flight" - ] - }, - "public.board_kind": { - "name": "board_kind", - "schema": "public", - "values": [ - "location", - "tutorial" - ] - }, - "public.dispatch_status": { - "name": "dispatch_status", - "schema": "public", - "values": [ - "in_flight", - "resolved" - ] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file diff --git a/apps/server/drizzle/meta/0008_snapshot.json b/apps/server/drizzle/meta/0008_snapshot.json deleted file mode 100644 index 0f136d0..0000000 --- a/apps/server/drizzle/meta/0008_snapshot.json +++ /dev/null @@ -1,1487 +0,0 @@ -{ - "id": "9c84497a-e6c5-4313-a339-71cc45364eb4", - "prevId": "4d1e7e7c-f079-46db-8c00-bfe58ba49f73", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.accepted_contracts": { - "name": "accepted_contracts", - "schema": "", - "columns": { - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "contract_id": { - "name": "contract_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "period_index": { - "name": "period_index", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "dispatch_id": { - "name": "dispatch_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "accepted_at": { - "name": "accepted_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "accepted_contracts_player_id_players_id_fk": { - "name": "accepted_contracts_player_id_players_id_fk", - "tableFrom": "accepted_contracts", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "accepted_contracts_dispatch_id_dispatches_id_fk": { - "name": "accepted_contracts_dispatch_id_dispatches_id_fk", - "tableFrom": "accepted_contracts", - "tableTo": "dispatches", - "columnsFrom": [ - "dispatch_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "accepted_contracts_player_id_contract_id_pk": { - "name": "accepted_contracts_player_id_contract_id_pk", - "columns": [ - "player_id", - "contract_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.bays": { - "name": "bays", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "bays_player_location_idx": { - "name": "bays_player_location_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "location_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "bays_player_id_players_id_fk": { - "name": "bays_player_id_players_id_fk", - "tableFrom": "bays", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.claimed_goals": { - "name": "claimed_goals", - "schema": "", - "columns": { - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "goal_id": { - "name": "goal_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "claimed_at": { - "name": "claimed_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "claimed_goals_player_id_players_id_fk": { - "name": "claimed_goals_player_id_players_id_fk", - "tableFrom": "claimed_goals", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "claimed_goals_player_id_goal_id_pk": { - "name": "claimed_goals_player_id_goal_id_pk", - "columns": [ - "player_id", - "goal_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.companies": { - "name": "companies", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "credits": { - "name": "credits", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "loan_balance": { - "name": "loan_balance", - "type": "bigint", - "primaryKey": false, - "notNull": true, - "default": 0 - } - }, - "indexes": {}, - "foreignKeys": { - "companies_player_id_players_id_fk": { - "name": "companies_player_id_players_id_fk", - "tableFrom": "companies", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "companies_player_id_unique": { - "name": "companies_player_id_unique", - "nullsNotDistinct": false, - "columns": [ - "player_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.contract_boards": { - "name": "contract_boards", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "kind": { - "name": "kind", - "type": "board_kind", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "period_index": { - "name": "period_index", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "tutorial_run": { - "name": "tutorial_run", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "contracts": { - "name": "contracts", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "contract_boards_unique_idx": { - "name": "contract_boards_unique_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "kind", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "location_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "period_index", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "tutorial_run", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "contract_boards_player_id_players_id_fk": { - "name": "contract_boards_player_id_players_id_fk", - "tableFrom": "contract_boards", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.discovered_locations": { - "name": "discovered_locations", - "schema": "", - "columns": { - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "discovered_at": { - "name": "discovered_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "discovered_locations_player_id_players_id_fk": { - "name": "discovered_locations_player_id_players_id_fk", - "tableFrom": "discovered_locations", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "discovered_locations_player_id_location_id_pk": { - "name": "discovered_locations_player_id_location_id_pk", - "columns": [ - "player_id", - "location_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.dispatches": { - "name": "dispatches", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "ship_id": { - "name": "ship_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "pilot_id": { - "name": "pilot_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tutorial": { - "name": "tutorial", - "type": "boolean", - "primaryKey": false, - "notNull": true - }, - "record": { - "name": "record", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "arrival_at": { - "name": "arrival_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "dispatch_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'in_flight'" - }, - "resolution": { - "name": "resolution", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "resolved_at": { - "name": "resolved_at", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "acknowledged_at": { - "name": "acknowledged_at", - "type": "bigint", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "dispatches_due_idx": { - "name": "dispatches_due_idx", - "columns": [ - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "arrival_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "dispatches_player_status_idx": { - "name": "dispatches_player_status_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "dispatches_player_id_players_id_fk": { - "name": "dispatches_player_id_players_id_fk", - "tableFrom": "dispatches", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "dispatches_ship_id_ships_id_fk": { - "name": "dispatches_ship_id_ships_id_fk", - "tableFrom": "dispatches", - "tableTo": "ships", - "columnsFrom": [ - "ship_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "dispatches_pilot_id_pilots_id_fk": { - "name": "dispatches_pilot_id_pilots_id_fk", - "tableFrom": "dispatches", - "tableTo": "pilots", - "columnsFrom": [ - "pilot_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.pilots": { - "name": "pilots", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "is_avatar": { - "name": "is_avatar", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "is_veteran": { - "name": "is_veteran", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "xp": { - "name": "xp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "skills": { - "name": "skills", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'::jsonb" - }, - "skill_points_spent": { - "name": "skill_points_spent", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "assigned_ship_id": { - "name": "assigned_ship_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "activity_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'idle'" - }, - "duty_hours": { - "name": "duty_hours", - "type": "double precision", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "rest_ends_at": { - "name": "rest_ends_at", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "pilots_player_idx": { - "name": "pilots_player_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "pilots_player_id_players_id_fk": { - "name": "pilots_player_id_players_id_fk", - "tableFrom": "pilots", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "pilots_assigned_ship_id_ships_id_fk": { - "name": "pilots_assigned_ship_id_ships_id_fk", - "tableFrom": "pilots", - "tableTo": "ships", - "columnsFrom": [ - "assigned_ship_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "pilots_assigned_ship_id_unique": { - "name": "pilots_assigned_ship_id_unique", - "nullsNotDistinct": false, - "columns": [ - "assigned_ship_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.player_logins": { - "name": "player_logins", - "schema": "", - "columns": { - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "player_logins_user_id_user_id_fk": { - "name": "player_logins_user_id_user_id_fk", - "tableFrom": "player_logins", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "player_logins_player_id_players_id_fk": { - "name": "player_logins_player_id_players_id_fk", - "tableFrom": "player_logins", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.players": { - "name": "players", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "world_seed": { - "name": "world_seed", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "tutorial_active": { - "name": "tutorial_active", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "loaner_location_id": { - "name": "loaner_location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tutorial_runs_completed": { - "name": "tutorial_runs_completed", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "deal_accepted": { - "name": "deal_accepted", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "backup_code_hash": { - "name": "backup_code_hash", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "backup_code_created_at": { - "name": "backup_code_created_at", - "type": "bigint", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "players_user_id_user_id_fk": { - "name": "players_user_id_user_id_fk", - "tableFrom": "players", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "players_user_id_unique": { - "name": "players_user_id_unique", - "nullsNotDistinct": false, - "columns": [ - "user_id" - ] - }, - "players_backup_code_hash_unique": { - "name": "players_backup_code_hash_unique", - "nullsNotDistinct": false, - "columns": [ - "backup_code_hash" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.ship_upgrades": { - "name": "ship_upgrades", - "schema": "", - "columns": { - "ship_id": { - "name": "ship_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "upgrade_id": { - "name": "upgrade_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "level": { - "name": "level", - "type": "integer", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "ship_upgrades_ship_id_ships_id_fk": { - "name": "ship_upgrades_ship_id_ships_id_fk", - "tableFrom": "ship_upgrades", - "tableTo": "ships", - "columnsFrom": [ - "ship_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "ship_upgrades_ship_id_upgrade_id_pk": { - "name": "ship_upgrades_ship_id_upgrade_id_pk", - "columns": [ - "ship_id", - "upgrade_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.ships": { - "name": "ships", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "model_id": { - "name": "model_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "activity_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'idle'" - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "in_shop_until": { - "name": "in_shop_until", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "shop_upgrade_id": { - "name": "shop_upgrade_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "shop_level": { - "name": "shop_level", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "ships_player_idx": { - "name": "ships_player_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "ships_player_id_players_id_fk": { - "name": "ships_player_id_players_id_fk", - "tableFrom": "ships", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "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.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.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()" - }, - "is_anonymous": { - "name": "is_anonymous", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - } - }, - "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": { - "public.activity_status": { - "name": "activity_status", - "schema": "public", - "values": [ - "idle", - "in_flight" - ] - }, - "public.board_kind": { - "name": "board_kind", - "schema": "public", - "values": [ - "location", - "tutorial" - ] - }, - "public.dispatch_status": { - "name": "dispatch_status", - "schema": "public", - "values": [ - "in_flight", - "resolved" - ] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file diff --git a/apps/server/drizzle/meta/0009_snapshot.json b/apps/server/drizzle/meta/0009_snapshot.json deleted file mode 100644 index 6448ed6..0000000 --- a/apps/server/drizzle/meta/0009_snapshot.json +++ /dev/null @@ -1,1494 +0,0 @@ -{ - "id": "8344e66f-27e5-466e-aad0-4878dd4fb197", - "prevId": "9c84497a-e6c5-4313-a339-71cc45364eb4", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.accepted_contracts": { - "name": "accepted_contracts", - "schema": "", - "columns": { - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "contract_id": { - "name": "contract_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "period_index": { - "name": "period_index", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "dispatch_id": { - "name": "dispatch_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "accepted_at": { - "name": "accepted_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "accepted_contracts_player_id_players_id_fk": { - "name": "accepted_contracts_player_id_players_id_fk", - "tableFrom": "accepted_contracts", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "accepted_contracts_dispatch_id_dispatches_id_fk": { - "name": "accepted_contracts_dispatch_id_dispatches_id_fk", - "tableFrom": "accepted_contracts", - "tableTo": "dispatches", - "columnsFrom": [ - "dispatch_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "accepted_contracts_player_id_contract_id_pk": { - "name": "accepted_contracts_player_id_contract_id_pk", - "columns": [ - "player_id", - "contract_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.bays": { - "name": "bays", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "bays_player_location_idx": { - "name": "bays_player_location_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "location_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "bays_player_id_players_id_fk": { - "name": "bays_player_id_players_id_fk", - "tableFrom": "bays", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.claimed_goals": { - "name": "claimed_goals", - "schema": "", - "columns": { - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "goal_id": { - "name": "goal_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "claimed_at": { - "name": "claimed_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "claimed_goals_player_id_players_id_fk": { - "name": "claimed_goals_player_id_players_id_fk", - "tableFrom": "claimed_goals", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "claimed_goals_player_id_goal_id_pk": { - "name": "claimed_goals_player_id_goal_id_pk", - "columns": [ - "player_id", - "goal_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.companies": { - "name": "companies", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "credits": { - "name": "credits", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "loan_balance": { - "name": "loan_balance", - "type": "bigint", - "primaryKey": false, - "notNull": true, - "default": 0 - } - }, - "indexes": {}, - "foreignKeys": { - "companies_player_id_players_id_fk": { - "name": "companies_player_id_players_id_fk", - "tableFrom": "companies", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "companies_player_id_unique": { - "name": "companies_player_id_unique", - "nullsNotDistinct": false, - "columns": [ - "player_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.contract_boards": { - "name": "contract_boards", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "kind": { - "name": "kind", - "type": "board_kind", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "period_index": { - "name": "period_index", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "tutorial_run": { - "name": "tutorial_run", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "contracts": { - "name": "contracts", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "contract_boards_unique_idx": { - "name": "contract_boards_unique_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "kind", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "location_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "period_index", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "tutorial_run", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "contract_boards_player_id_players_id_fk": { - "name": "contract_boards_player_id_players_id_fk", - "tableFrom": "contract_boards", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.discovered_locations": { - "name": "discovered_locations", - "schema": "", - "columns": { - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "discovered_at": { - "name": "discovered_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "discovered_locations_player_id_players_id_fk": { - "name": "discovered_locations_player_id_players_id_fk", - "tableFrom": "discovered_locations", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "discovered_locations_player_id_location_id_pk": { - "name": "discovered_locations_player_id_location_id_pk", - "columns": [ - "player_id", - "location_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.dispatches": { - "name": "dispatches", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "ship_id": { - "name": "ship_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "pilot_id": { - "name": "pilot_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tutorial": { - "name": "tutorial", - "type": "boolean", - "primaryKey": false, - "notNull": true - }, - "record": { - "name": "record", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "arrival_at": { - "name": "arrival_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "dispatch_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'in_flight'" - }, - "resolution": { - "name": "resolution", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "resolved_at": { - "name": "resolved_at", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "acknowledged_at": { - "name": "acknowledged_at", - "type": "bigint", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "dispatches_due_idx": { - "name": "dispatches_due_idx", - "columns": [ - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "arrival_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "dispatches_player_status_idx": { - "name": "dispatches_player_status_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "dispatches_player_id_players_id_fk": { - "name": "dispatches_player_id_players_id_fk", - "tableFrom": "dispatches", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "dispatches_ship_id_ships_id_fk": { - "name": "dispatches_ship_id_ships_id_fk", - "tableFrom": "dispatches", - "tableTo": "ships", - "columnsFrom": [ - "ship_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - }, - "dispatches_pilot_id_pilots_id_fk": { - "name": "dispatches_pilot_id_pilots_id_fk", - "tableFrom": "dispatches", - "tableTo": "pilots", - "columnsFrom": [ - "pilot_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.pilots": { - "name": "pilots", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "is_avatar": { - "name": "is_avatar", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "is_veteran": { - "name": "is_veteran", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "xp": { - "name": "xp", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "skills": { - "name": "skills", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'::jsonb" - }, - "skill_points_spent": { - "name": "skill_points_spent", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "assigned_ship_id": { - "name": "assigned_ship_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "activity_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'idle'" - }, - "duty_hours": { - "name": "duty_hours", - "type": "double precision", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "rest_ends_at": { - "name": "rest_ends_at", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "pilots_player_idx": { - "name": "pilots_player_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "pilots_player_id_players_id_fk": { - "name": "pilots_player_id_players_id_fk", - "tableFrom": "pilots", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "pilots_assigned_ship_id_ships_id_fk": { - "name": "pilots_assigned_ship_id_ships_id_fk", - "tableFrom": "pilots", - "tableTo": "ships", - "columnsFrom": [ - "assigned_ship_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "pilots_assigned_ship_id_unique": { - "name": "pilots_assigned_ship_id_unique", - "nullsNotDistinct": false, - "columns": [ - "assigned_ship_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.player_logins": { - "name": "player_logins", - "schema": "", - "columns": { - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "player_logins_user_id_user_id_fk": { - "name": "player_logins_user_id_user_id_fk", - "tableFrom": "player_logins", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "player_logins_player_id_players_id_fk": { - "name": "player_logins_player_id_players_id_fk", - "tableFrom": "player_logins", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.players": { - "name": "players", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "world_seed": { - "name": "world_seed", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "tutorial_active": { - "name": "tutorial_active", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "loaner_location_id": { - "name": "loaner_location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tutorial_runs_completed": { - "name": "tutorial_runs_completed", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "deal_accepted": { - "name": "deal_accepted", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "backup_code_hash": { - "name": "backup_code_hash", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "backup_code_created_at": { - "name": "backup_code_created_at", - "type": "bigint", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "players_user_id_user_id_fk": { - "name": "players_user_id_user_id_fk", - "tableFrom": "players", - "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "players_user_id_unique": { - "name": "players_user_id_unique", - "nullsNotDistinct": false, - "columns": [ - "user_id" - ] - }, - "players_backup_code_hash_unique": { - "name": "players_backup_code_hash_unique", - "nullsNotDistinct": false, - "columns": [ - "backup_code_hash" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.ship_upgrades": { - "name": "ship_upgrades", - "schema": "", - "columns": { - "ship_id": { - "name": "ship_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "upgrade_id": { - "name": "upgrade_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "level": { - "name": "level", - "type": "integer", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "ship_upgrades_ship_id_ships_id_fk": { - "name": "ship_upgrades_ship_id_ships_id_fk", - "tableFrom": "ship_upgrades", - "tableTo": "ships", - "columnsFrom": [ - "ship_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "ship_upgrades_ship_id_upgrade_id_pk": { - "name": "ship_upgrades_ship_id_upgrade_id_pk", - "columns": [ - "ship_id", - "upgrade_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.ships": { - "name": "ships", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "player_id": { - "name": "player_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "model_id": { - "name": "model_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "activity_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'idle'" - }, - "location_id": { - "name": "location_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "in_shop_until": { - "name": "in_shop_until", - "type": "bigint", - "primaryKey": false, - "notNull": false - }, - "shop_upgrade_id": { - "name": "shop_upgrade_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "shop_level": { - "name": "shop_level", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "from_deal": { - "name": "from_deal", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "created_at": { - "name": "created_at", - "type": "bigint", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "ships_player_idx": { - "name": "ships_player_idx", - "columns": [ - { - "expression": "player_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "ships_player_id_players_id_fk": { - "name": "ships_player_id_players_id_fk", - "tableFrom": "ships", - "tableTo": "players", - "columnsFrom": [ - "player_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "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.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.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()" - }, - "is_anonymous": { - "name": "is_anonymous", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - } - }, - "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": { - "public.activity_status": { - "name": "activity_status", - "schema": "public", - "values": [ - "idle", - "in_flight" - ] - }, - "public.board_kind": { - "name": "board_kind", - "schema": "public", - "values": [ - "location", - "tutorial" - ] - }, - "public.dispatch_status": { - "name": "dispatch_status", - "schema": "public", - "values": [ - "in_flight", - "resolved" - ] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file diff --git a/apps/server/drizzle/meta/_journal.json b/apps/server/drizzle/meta/_journal.json deleted file mode 100644 index 1e770c7..0000000 --- a/apps/server/drizzle/meta/_journal.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "version": "7", - "dialect": "postgresql", - "entries": [ - { - "idx": 0, - "version": "7", - "when": 1790991444530, - "tag": "0000_init", - "breakpoints": true - }, - { - "idx": 1, - "version": "7", - "when": 1790991751364, - "tag": "0001_auth", - "breakpoints": true - }, - { - "idx": 2, - "version": "7", - "when": 1791039630016, - "tag": "0002_cr001_faster_start", - "breakpoints": true - }, - { - "idx": 3, - "version": "7", - "when": 1791053200611, - "tag": "0003_cr002_location_boards", - "breakpoints": true - }, - { - "idx": 4, - "version": "7", - "when": 1791078648879, - "tag": "0004_cr004_pilot_duty", - "breakpoints": true - }, - { - "idx": 5, - "version": "7", - "when": 1791148094765, - "tag": "0005_cr007_goods", - "breakpoints": true - }, - { - "idx": 6, - "version": "7", - "when": 1791155978006, - "tag": "0006_cr009_ship_upgrades", - "breakpoints": true - }, - { - "idx": 7, - "version": "7", - "when": 1791164145228, - "tag": "0007_cr010_veteran_pilot", - "breakpoints": true - }, - { - "idx": 8, - "version": "7", - "when": 1791171500580, - "tag": "0008_cr011_backup_codes", - "breakpoints": true - }, - { - "idx": 9, - "version": "7", - "when": 1791214535055, - "tag": "0009_cr013_ship_from_deal", - "breakpoints": true - } - ] -} \ No newline at end of file diff --git a/apps/server/package.json b/apps/server/package.json deleted file mode 100644 index a923aeb..0000000 --- a/apps/server/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "@deadhead/server", - "version": "0.0.0", - "private": true, - "type": "module", - "main": "./src/index.ts", - "types": "./src/index.ts", - "scripts": { - "dev": "tsx watch src/main.ts", - "start": "tsx src/main.ts", - "typecheck": "tsc --noEmit", - "db:generate": "drizzle-kit generate", - "db:migrate": "tsx src/db/migrate-cli.ts" - }, - "dependencies": { - "@deadhead/content": "workspace:*", - "@deadhead/schema": "workspace:*", - "@deadhead/sim": "workspace:*", - "better-auth": "^1.7.7", - "drizzle-orm": "^0.45.3", - "fastify": "^5.12.5", - "pg": "^8.23.1", - "pg-boss": "^12.36.0", - "tsx": "^4.23.15", - "zod": "^4.6.5" - }, - "devDependencies": { - "@types/pg": "^8.23.1", - "drizzle-kit": "^0.31.11" - } -} diff --git a/apps/server/src/app.ts b/apps/server/src/app.ts deleted file mode 100644 index 49b5d76..0000000 --- a/apps/server/src/app.ts +++ /dev/null @@ -1,75 +0,0 @@ -import Fastify, { type FastifyInstance } from "fastify"; -import { createAuth } from "./auth"; -import { type Clock, createOffsetClock, systemClock } from "./clock"; -import type { ServerConfig } from "./config"; -import { createDatabase, type Database } from "./db/client"; -import { registerErrorHandler } from "./http/errors"; -import { requirePlayerId } from "./http/session"; -import { appJobs } from "./jobs/definitions"; -import { createPgBossQueue, type JobQueue } from "./jobs/queue"; -import { registerBoardRoutes } from "./routes/boards"; -import type { AppContext } from "./routes/context"; -import { registerDispatchRoutes } from "./routes/dispatches"; -import { registerFleetRoutes } from "./routes/fleet"; -import { registerPlayerRoutes } from "./routes/player"; -import { registerDevRoutes } from "./routes/dev"; -import { registerTestClockRoute } from "./routes/test-clock"; - -export interface AppDeps { - clock?: Clock; - /** An existing database (tests). Otherwise the app opens one and closes it on shutdown. */ - database?: Database; - /** Background job queue. Defaults to pg-boss on the app's database. */ - jobs?: JobQueue; -} - -/** Builds the Fastify app without listening, so tests can start it on any port. */ -export function buildApp(config: ServerConfig, deps: AppDeps = {}): FastifyInstance { - const database = deps.database ?? createDatabase(config.databaseUrl); - const { db } = database; - const auth = createAuth(db, { secret: config.authSecret, baseUrl: config.authUrl }); - const clock = createOffsetClock(deps.clock ?? systemClock); - const jobs = - deps.jobs ?? - createPgBossQueue(config.databaseUrl, { - workers: config.workersEnabled, - jobs: appJobs({ db, clock: clock.now }), - }); - const ctx: AppContext = { - db, - auth, - clock: clock.now, - // pg-boss timers run on the real clock, not the (possibly offset) game clock. - toBaseTime: clock.toBase, - jobs, - options: { - requirePlayer: (request) => requirePlayerId(request, auth, db), - validateResponses: config.env !== "production", - }, - }; - - const app = Fastify({ - // Structured JSON logs (pino); silenced in tests unless LOG_LEVEL says otherwise. - logger: { level: config.logLevel }, - }); - // The queue starts with the app and stops before the database closes. - app.addHook("onReady", () => jobs.start()); - app.addHook("onClose", async () => { - await jobs.stop(); - if (!deps.database) await database.close(); - }); - registerErrorHandler(app); - - // Under /api so it is reachable through the same-domain proxy (decision 0.5.20). - app.get("/api/health", async () => ({ status: "ok" })); - registerPlayerRoutes(app, ctx); - registerBoardRoutes(app, ctx); - registerDispatchRoutes(app, ctx); - registerFleetRoutes(app, ctx); - if (config.env !== "production") { - registerTestClockRoute(app, clock); - registerDevRoutes(app, ctx, clock); - } - - return app; -} diff --git a/apps/server/src/auth.ts b/apps/server/src/auth.ts deleted file mode 100644 index bf4a2ba..0000000 --- a/apps/server/src/auth.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { betterAuth } from "better-auth"; -import { drizzleAdapter } from "better-auth/adapters/drizzle"; -import { anonymous } from "better-auth/plugins/anonymous"; -import type { Db } from "./db/client"; -import * as authSchema from "./db/auth-schema"; - -export interface AuthSettings { - secret: string; - baseUrl: string; -} - -/** Better Auth with guest (anonymous) sign-in, storing users and sessions in Postgres. */ -export function createAuth(db: Db, settings: AuthSettings) { - return betterAuth({ - database: drizzleAdapter(db, { provider: "pg", schema: authSchema }), - secret: settings.secret, - baseURL: settings.baseUrl, - basePath: "/api/auth", - plugins: [anonymous()], - }); -} - -export type Auth = ReturnType; diff --git a/apps/server/src/clock.ts b/apps/server/src/clock.ts deleted file mode 100644 index 1293e95..0000000 --- a/apps/server/src/clock.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { RealTime } from "@deadhead/sim"; - -/** The server's only source of "now" (decision 0.5.18), so tests can control time. */ -export type Clock = () => RealTime; - -export const systemClock: Clock = () => Date.now(); - -/** - * A clock that runs ahead of (or behind) its base clock by an adjustable offset. - * The test-only clock-offset endpoint moves it; production never does (Step 7.2). - */ -export interface OffsetClock { - now: Clock; - offsetMs(): number; - setOffset(ms: number): void; - /** Converts a time on this clock to the base clock, e.g. for pg-boss timers. */ - toBase(time: RealTime): RealTime; -} - -export function createOffsetClock(base: Clock): OffsetClock { - let offset = 0; - return { - now: () => base() + offset, - offsetMs: () => offset, - setOffset: (ms) => { - offset = ms; - }, - toBase: (time) => time - offset, - }; -} diff --git a/apps/server/src/config.ts b/apps/server/src/config.ts deleted file mode 100644 index 9778ba5..0000000 --- a/apps/server/src/config.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { z } from "zod"; - -/** Server settings from environment variables (see `.env.example`). */ -const envSchema = z.object({ - NODE_ENV: z.enum(["development", "test", "production"]).default("development"), - PORT: z.coerce.number().int().min(0).max(65535).default(3000), - LOG_LEVEL: z.enum(["fatal", "error", "warn", "info", "debug", "trace", "silent"]).default("info"), - DATABASE_URL: z.url(), - TEST_DATABASE_URL: z.url().optional(), - BETTER_AUTH_SECRET: z.string().min(32), - BETTER_AUTH_URL: z.url(), - /** Run background job workers in this process (Step 7.1). */ - WORKERS_ENABLED: z.enum(["true", "false"]).default("true"), -}); - -export interface ServerConfig { - env: "development" | "test" | "production"; - port: number; - logLevel: z.infer["LOG_LEVEL"]; - databaseUrl: string; - testDatabaseUrl: string | undefined; - authSecret: string; - authUrl: string; - workersEnabled: boolean; -} - -/** Validates the environment, throwing one error that lists every problem. */ -export function loadConfig(env: Record): ServerConfig { - const result = envSchema.safeParse(env); - if (!result.success) { - const problems = result.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`); - throw new Error(`Invalid server environment:\n${problems.join("\n")}`); - } - const e = result.data; - return { - env: e.NODE_ENV, - port: e.PORT, - logLevel: e.LOG_LEVEL, - databaseUrl: e.DATABASE_URL, - testDatabaseUrl: e.TEST_DATABASE_URL, - authSecret: e.BETTER_AUTH_SECRET, - authUrl: e.BETTER_AUTH_URL, - workersEnabled: e.WORKERS_ENABLED === "true", - }; -} diff --git a/apps/server/src/db/auth-schema.ts b/apps/server/src/db/auth-schema.ts deleted file mode 100644 index 71a0204..0000000 --- a/apps/server/src/db/auth-schema.ts +++ /dev/null @@ -1,96 +0,0 @@ -// Better Auth tables, generated by `@better-auth/cli generate` (anonymous plugin). -// Regenerate rather than hand-edit when Better Auth or its plugins change. -import { relations } from "drizzle-orm"; -import { pgTable, text, timestamp, boolean, index } from "drizzle-orm/pg-core"; - -export const user = pgTable("user", { - id: text("id").primaryKey(), - name: text("name").notNull(), - email: text("email").notNull().unique(), - emailVerified: boolean("email_verified").default(false).notNull(), - image: text("image"), - createdAt: timestamp("created_at").defaultNow().notNull(), - updatedAt: timestamp("updated_at") - .defaultNow() - .$onUpdate(() => /* @__PURE__ */ new Date()) - .notNull(), - isAnonymous: boolean("is_anonymous").default(false), -}); - -export const session = pgTable( - "session", - { - id: text("id").primaryKey(), - expiresAt: timestamp("expires_at").notNull(), - token: text("token").notNull().unique(), - createdAt: timestamp("created_at").defaultNow().notNull(), - updatedAt: timestamp("updated_at") - .$onUpdate(() => /* @__PURE__ */ new Date()) - .notNull(), - ipAddress: text("ip_address"), - userAgent: text("user_agent"), - userId: text("user_id") - .notNull() - .references(() => user.id, { onDelete: "cascade" }), - }, - (table) => [index("session_userId_idx").on(table.userId)], -); - -export const account = pgTable( - "account", - { - id: text("id").primaryKey(), - accountId: text("account_id").notNull(), - providerId: text("provider_id").notNull(), - userId: text("user_id") - .notNull() - .references(() => user.id, { onDelete: "cascade" }), - accessToken: text("access_token"), - refreshToken: text("refresh_token"), - idToken: text("id_token"), - accessTokenExpiresAt: timestamp("access_token_expires_at"), - refreshTokenExpiresAt: timestamp("refresh_token_expires_at"), - scope: text("scope"), - password: text("password"), - createdAt: timestamp("created_at").defaultNow().notNull(), - updatedAt: timestamp("updated_at") - .$onUpdate(() => /* @__PURE__ */ new Date()) - .notNull(), - }, - (table) => [index("account_userId_idx").on(table.userId)], -); - -export const verification = pgTable( - "verification", - { - id: text("id").primaryKey(), - identifier: text("identifier").notNull(), - value: text("value").notNull(), - expiresAt: timestamp("expires_at").notNull(), - createdAt: timestamp("created_at").defaultNow().notNull(), - updatedAt: timestamp("updated_at") - .defaultNow() - .$onUpdate(() => /* @__PURE__ */ new Date()) - .notNull(), - }, - (table) => [index("verification_identifier_idx").on(table.identifier)], -); - -export const userRelations = relations(user, ({ many }) => ({ - sessions: many(session), - accounts: many(account), -})); - -export const sessionRelations = relations(session, ({ one }) => ({ - user: one(user, { - fields: [session.userId], - references: [user.id], - }), -})); - -export const accountRelations = relations(account, ({ one }) => ({ - user: one(user, { - fields: [account.userId], - references: [user.id], - }), -})); diff --git a/apps/server/src/db/client.ts b/apps/server/src/db/client.ts deleted file mode 100644 index 56b5520..0000000 --- a/apps/server/src/db/client.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { PgDatabase } from "drizzle-orm/pg-core"; -import { drizzle, type NodePgDatabase, type NodePgQueryResultHKT } from "drizzle-orm/node-postgres"; -import pg from "pg"; -import * as authSchema from "./auth-schema"; -import * as gameSchema from "./schema"; - -const schema = { ...gameSchema, ...authSchema }; -type Schema = typeof schema; - -export type Db = NodePgDatabase; -/** The database or an open transaction: anything that can run queries. */ -export type Queryable = PgDatabase; - -export interface Database { - db: Db; - pool: pg.Pool; - close(): Promise; -} - -export function createDatabase(url: string): Database { - const pool = new pg.Pool({ connectionString: url }); - return { db: drizzle(pool, { schema }), pool, close: () => pool.end() }; -} diff --git a/apps/server/src/db/migrate-cli.ts b/apps/server/src/db/migrate-cli.ts deleted file mode 100644 index c283073..0000000 --- a/apps/server/src/db/migrate-cli.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { existsSync } from "node:fs"; -import { fileURLToPath } from "node:url"; -import { runMigrations } from "./migrate"; - -// `pnpm --filter @deadhead/server db:migrate` migrates DATABASE_URL. -const envFile = fileURLToPath(new URL("../../../../.env", import.meta.url)); -if (existsSync(envFile)) process.loadEnvFile(envFile); - -const url = process.env.DATABASE_URL; -if (!url) { - console.error("DATABASE_URL is not set"); - process.exit(1); -} -await runMigrations(url); -console.log("Migrations applied"); diff --git a/apps/server/src/db/migrate.ts b/apps/server/src/db/migrate.ts deleted file mode 100644 index e228d51..0000000 --- a/apps/server/src/db/migrate.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { fileURLToPath } from "node:url"; -import { migrate } from "drizzle-orm/node-postgres/migrator"; -import { createDatabase } from "./client"; - -export const MIGRATIONS_FOLDER = fileURLToPath(new URL("../../drizzle", import.meta.url)); - -/** Applies pending migrations. Already-applied ones are skipped, so it is safe to rerun. */ -export async function runMigrations(url: string): Promise { - const database = createDatabase(url); - try { - await migrate(database.db, { migrationsFolder: MIGRATIONS_FOLDER }); - } finally { - await database.close(); - } -} diff --git a/apps/server/src/db/schema.ts b/apps/server/src/db/schema.ts deleted file mode 100644 index f6c59d8..0000000 --- a/apps/server/src/db/schema.ts +++ /dev/null @@ -1,254 +0,0 @@ -import type { Contract, DispatchRecord, Resolution } from "@deadhead/sim"; -import { - bigint, - boolean, - doublePrecision, - index, - integer, - jsonb, - pgEnum, - pgTable, - primaryKey, - text, - uniqueIndex, -} from "drizzle-orm/pg-core"; -import { user } from "./auth-schema"; - -// All times are Unix milliseconds (bigint), matching sim and the API. -const time = (name: string) => bigint(name, { mode: "number" }); - -export const activityStatus = pgEnum("activity_status", ["idle", "in_flight"]); -export const dispatchStatus = pgEnum("dispatch_status", ["in_flight", "resolved"]); -export const boardKind = pgEnum("board_kind", ["location", "tutorial"]); - -export const players = pgTable("players", { - id: text("id").primaryKey(), - /** The Better Auth user (a guest account in the base game). */ - userId: text("user_id") - .notNull() - .unique() - .references(() => user.id, { onDelete: "cascade" }), - /** 32-bit unsigned seed for boards, dispatches and names (decision 0.5.10). */ - worldSeed: bigint("world_seed", { mode: "number" }).notNull(), - tutorialActive: boolean("tutorial_active").notNull().default(true), - /** Where the tutorial loaner is (decision 0.5.26); null once the tutorial ends. */ - loanerLocationId: text("loaner_location_id"), - /** Tutorial runs resolved so far (CR-001). */ - tutorialRunsCompleted: integer("tutorial_runs_completed").notNull().default(0), - /** The one-time deal has been accepted (decision 0.5.30). */ - dealAccepted: boolean("deal_accepted").notNull().default(false), - createdAt: time("created_at").notNull(), - /** SHA-256 of the player's backup code (decision 0.5.72); null until one is made. */ - backupCodeHash: text("backup_code_hash").unique(), - backupCodeCreatedAt: time("backup_code_created_at"), -}); - -export const companies = pgTable("companies", { - id: text("id").primaryKey(), - playerId: text("player_id") - .notNull() - .unique() - .references(() => players.id, { onDelete: "cascade" }), - name: text("name").notNull(), - /** Row-locked by every endpoint that changes money or fleet state (Step 6.14). */ - credits: bigint("credits", { mode: "number" }).notNull(), - /** Owed to the lender (decision 0.5.32). */ - loanBalance: bigint("loan_balance", { mode: "number" }).notNull().default(0), -}); - -export const ships = pgTable( - "ships", - { - id: text("id").primaryKey(), - playerId: text("player_id") - .notNull() - .references(() => players.id, { onDelete: "cascade" }), - modelId: text("model_id").notNull(), - name: text("name").notNull(), - status: activityStatus("status").notNull().default("idle"), - /** Null while in flight. */ - locationId: text("location_id"), - /** In the upgrade shop until this time (decision 0.5.66); null or past: not. */ - inShopUntil: time("in_shop_until"), - /** - * The latest shop job (upgrade and level), kept after it finishes until the - * player has seen it ("out of the shop"); null when none or acknowledged. - */ - shopUpgradeId: text("shop_upgrade_id"), - shopLevel: integer("shop_level"), - /** Handed over in the deal rather than bought from the Market (CR-013 goal 3). */ - fromDeal: boolean("from_deal").notNull().default(false), - createdAt: time("created_at").notNull(), - }, - (t) => [index("ships_player_idx").on(t.playerId)], -); - -/** Installed upgrade levels per ship (CR-009). A missing row is level 0. */ -export const shipUpgrades = pgTable( - "ship_upgrades", - { - shipId: text("ship_id") - .notNull() - .references(() => ships.id, { onDelete: "cascade" }), - upgradeId: text("upgrade_id").notNull(), - level: integer("level").notNull(), - }, - (t) => [primaryKey({ columns: [t.shipId, t.upgradeId] })], -); - -export const pilots = pgTable( - "pilots", - { - id: text("id").primaryKey(), - playerId: text("player_id") - .notNull() - .references(() => players.id, { onDelete: "cascade" }), - name: text("name").notNull(), - isAvatar: boolean("is_avatar").notNull().default(false), - /** The veteran who came with the deal (decision 0.5.70); otherwise a normal hired pilot. */ - isVeteran: boolean("is_veteran").notNull().default(false), - xp: integer("xp").notNull().default(0), - skills: jsonb("skills").$type>().notNull().default({}), - skillPointsSpent: integer("skill_points_spent").notNull().default(0), - /** One pilot per ship (decision 0.5.2). */ - assignedShipId: text("assigned_ship_id") - .unique() - .references(() => ships.id, { onDelete: "set null" }), - /** Only unassigned pilots store a location (decision 0.5.27). */ - locationId: text("location_id"), - status: activityStatus("status").notNull().default("idle"), - /** Game hours flown since the last rest (decision 0.5.42). */ - dutyHours: doublePrecision("duty_hours").notNull().default(0), - /** Resting until this time; null (or past) when not resting (decision 0.5.45). */ - restEndsAt: time("rest_ends_at"), - createdAt: time("created_at").notNull(), - }, - (t) => [index("pilots_player_idx").on(t.playerId)], -); - -export const bays = pgTable( - "bays", - { - id: text("id").primaryKey(), - playerId: text("player_id") - .notNull() - .references(() => players.id, { onDelete: "cascade" }), - locationId: text("location_id").notNull(), - createdAt: time("created_at").notNull(), - }, - (t) => [uniqueIndex("bays_player_location_idx").on(t.playerId, t.locationId)], -); - -export const discoveredLocations = pgTable( - "discovered_locations", - { - playerId: text("player_id") - .notNull() - .references(() => players.id, { onDelete: "cascade" }), - locationId: text("location_id").notNull(), - discoveredAt: time("discovered_at").notNull(), - }, - (t) => [primaryKey({ columns: [t.playerId, t.locationId] })], -); - -/** - * One stored board per bay per period, and one tutorial board per period per - * loaner location (decisions 0.5.5 and 0.5.21). `locationId` is the bay's or - * the loaner's location. - */ -export const contractBoards = pgTable( - "contract_boards", - { - id: text("id").primaryKey(), - playerId: text("player_id") - .notNull() - .references(() => players.id, { onDelete: "cascade" }), - kind: boardKind("kind").notNull(), - locationId: text("location_id").notNull(), - periodIndex: integer("period_index").notNull(), - /** Tutorial boards only: runs completed when generated (0 for bay boards). */ - tutorialRun: integer("tutorial_run").notNull().default(0), - contracts: jsonb("contracts").$type().notNull(), - createdAt: time("created_at").notNull(), - }, - (t) => [ - uniqueIndex("contract_boards_unique_idx").on( - t.playerId, - t.kind, - t.locationId, - t.periodIndex, - t.tutorialRun, - ), - ], -); - -export const dispatches = pgTable( - "dispatches", - { - id: text("id").primaryKey(), - playerId: text("player_id") - .notNull() - .references(() => players.id, { onDelete: "cascade" }), - /** Null for a tutorial run (the loaner is not a ship row). */ - shipId: text("ship_id").references(() => ships.id, { onDelete: "set null" }), - pilotId: text("pilot_id") - .notNull() - .references(() => pilots.id, { onDelete: "cascade" }), - tutorial: boolean("tutorial").notNull(), - /** The full sim record, including a snapshot of the contract. */ - record: jsonb("record").$type().notNull(), - arrivalAt: time("arrival_at").notNull(), - status: dispatchStatus("status").notNull().default("in_flight"), - /** Applied effects, kept for the "while you were away" summary. */ - resolution: jsonb("resolution").$type(), - resolvedAt: time("resolved_at"), - acknowledgedAt: time("acknowledged_at"), - }, - (t) => [ - index("dispatches_due_idx").on(t.status, t.arrivalAt), - index("dispatches_player_status_idx").on(t.playerId, t.status), - ], -); - -export const acceptedContracts = pgTable( - "accepted_contracts", - { - playerId: text("player_id") - .notNull() - .references(() => players.id, { onDelete: "cascade" }), - contractId: text("contract_id").notNull(), - periodIndex: integer("period_index").notNull(), - dispatchId: text("dispatch_id") - .notNull() - .references(() => dispatches.id, { onDelete: "cascade" }), - acceptedAt: time("accepted_at").notNull(), - }, - (t) => [primaryKey({ columns: [t.playerId, t.contractId] })], -); - -/** Onboarding goals whose reward has been claimed (decision 0.5.34). */ -export const claimedGoals = pgTable( - "claimed_goals", - { - playerId: text("player_id") - .notNull() - .references(() => players.id, { onDelete: "cascade" }), - goalId: text("goal_id").notNull(), - claimedAt: time("claimed_at").notNull(), - }, - (t) => [primaryKey({ columns: [t.playerId, t.goalId] })], -); - -/** - * Extra sign-ins for a player (decisions 0.5.72, 0.5.73): each device that - * restores with a backup code gets its own guest user, linked here. - */ -export const playerLogins = pgTable("player_logins", { - userId: text("user_id") - .primaryKey() - .references(() => user.id, { onDelete: "cascade" }), - playerId: text("player_id") - .notNull() - .references(() => players.id, { onDelete: "cascade" }), - createdAt: time("created_at").notNull(), -}); diff --git a/apps/server/src/game/backup.ts b/apps/server/src/game/backup.ts deleted file mode 100644 index 64945fe..0000000 --- a/apps/server/src/game/backup.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { createHash, randomInt } from "node:crypto"; -import type { RealTime } from "@deadhead/sim"; -import { eq } from "drizzle-orm"; -import type { Queryable } from "../db/client"; -import { players } from "../db/schema"; - -/** - * Backup codes (decision 0.5.72): 20 characters from 31 that can't be mistaken - * for each other (no 0/O, 1/I/L), shown as four groups of five: about 99 bits. - * Only a SHA-256 of the normalized code is stored; the code itself is random - * enough that a slow hash would add nothing. - */ -export const BACKUP_ALPHABET = "23456789ABCDEFGHJKMNPQRSTUVWXYZ"; -const GROUPS = 4; -const GROUP_LENGTH = 5; - -export function generateBackupCode(): string { - const groups = Array.from({ length: GROUPS }, () => - Array.from( - { length: GROUP_LENGTH }, - () => BACKUP_ALPHABET[randomInt(BACKUP_ALPHABET.length)], - ).join(""), - ); - return groups.join("-"); -} - -/** The code without spaces or dashes, in capitals: how players type it doesn't matter. */ -export function normalizeBackupCode(code: string): string { - return code.toUpperCase().replace(/[\s-]/g, ""); -} - -export function hashBackupCode(code: string): string { - return createHash("sha256").update(normalizeBackupCode(code)).digest("hex"); -} - -/** Makes a new code for the player, replacing any old one; returns it once. */ -export async function createBackupCode( - tx: Queryable, - playerId: string, - now: RealTime, -): Promise<{ code: string; createdAt: RealTime }> { - const code = generateBackupCode(); - await tx - .update(players) - .set({ backupCodeHash: hashBackupCode(code), backupCodeCreatedAt: now }) - .where(eq(players.id, playerId)); - return { code, createdAt: now }; -} - -export async function backupCodeStatus(tx: Queryable, playerId: string) { - const [row] = await tx - .select({ hash: players.backupCodeHash, createdAt: players.backupCodeCreatedAt }) - .from(players) - .where(eq(players.id, playerId)); - return { hasCode: Boolean(row?.hash), createdAt: row?.hash ? (row.createdAt ?? null) : null }; -} - -/** The player a backup code belongs to, or null. */ -export async function playerForBackupCode(tx: Queryable, code: string): Promise { - if (normalizeBackupCode(code).length === 0) return null; - const [row] = await tx - .select({ id: players.id }) - .from(players) - .where(eq(players.backupCodeHash, hashBackupCode(code))); - return row?.id ?? null; -} - -/** - * Failed restore attempts per key (an IP, a device), in memory: at most - * `limit` failures per `windowMs` for any key (decision 0.5.72). - */ -export class RestoreLimiter { - private readonly failures = new Map(); - - constructor( - readonly limit = 5, - readonly windowMs = 15 * 60_000, - ) {} - - /** Whether any of the keys has used up its attempts. */ - blocked(keys: readonly string[], now: RealTime): boolean { - return keys.some((key) => { - const entry = this.failures.get(key); - return entry !== undefined && entry.resetAt > now && entry.count >= this.limit; - }); - } - - fail(keys: readonly string[], now: RealTime): void { - for (const key of keys) { - const entry = this.failures.get(key); - if (!entry || entry.resetAt <= now) - this.failures.set(key, { count: 1, resetAt: now + this.windowMs }); - else entry.count++; - } - } -} diff --git a/apps/server/src/game/boards.ts b/apps/server/src/game/boards.ts deleted file mode 100644 index 0006edb..0000000 --- a/apps/server/src/game/boards.ts +++ /dev/null @@ -1,198 +0,0 @@ -import { randomUUID } from "node:crypto"; -import { content } from "@deadhead/content"; -import { - boardPeriodIndex, - boardPeriodMs, - generateContractBoard, - generateTutorialBoard, - type Contract, - type FleetMember, - type RealTime, -} from "@deadhead/sim"; -import { and, asc, eq } from "drizzle-orm"; -import type { Queryable } from "../db/client"; -import { - acceptedContracts, - contractBoards, - discoveredLocations, - pilots, - players, - ships, -} from "../db/schema"; -import { gameError } from "../http/errors"; -import { loadUpgradeLevels } from "./upgrades"; - -export interface BoardView { - contracts: Contract[]; - periodEndsAt: RealTime; -} - -/** - * Returns the stored board for (player, kind, location, period), generating and - * storing it on the first request (decisions 0.5.5 and 0.5.21). Concurrent - * first requests store one board: the insert ignores conflicts and re-reads. - */ -export async function getOrCreateBoard( - tx: Queryable, - key: { - playerId: string; - kind: "location" | "tutorial"; - locationId: string; - periodIndex: number; - /** Tutorial boards: runs completed (CR-001). Location boards: 0. */ - tutorialRun?: number; - }, - generate: () => Contract[] | Promise, - now: RealTime, -): Promise { - const where = and( - eq(contractBoards.playerId, key.playerId), - eq(contractBoards.kind, key.kind), - eq(contractBoards.locationId, key.locationId), - eq(contractBoards.periodIndex, key.periodIndex), - eq(contractBoards.tutorialRun, key.tutorialRun ?? 0), - ); - const [existing] = await tx.select().from(contractBoards).where(where); - if (existing) return existing.contracts; - - await tx - .insert(contractBoards) - .values({ - id: randomUUID(), - ...key, - tutorialRun: key.tutorialRun ?? 0, - contracts: await generate(), - createdAt: now, - }) - .onConflictDoNothing(); - const [stored] = await tx.select().from(contractBoards).where(where); - return stored!.contracts; -} - -export async function acceptedContractIds(tx: Queryable, playerId: string): Promise> { - const rows = await tx - .select({ contractId: acceptedContracts.contractId }) - .from(acceptedContracts) - .where(eq(acceptedContracts.playerId, playerId)); - return new Set(rows.map((r) => r.contractId)); -} - -/** Board contracts minus the ones this player already accepted. */ -export async function boardView( - tx: Queryable, - playerId: string, - contracts: Contract[], - periodIndex: number, -): Promise { - const accepted = await acceptedContractIds(tx, playerId); - return { - contracts: contracts.filter((c) => !accepted.has(c.id)), - periodEndsAt: (periodIndex + 1) * boardPeriodMs(content.config), - }; -} - -/** The tutorial board for the current run and period at the loaner's location (Steps 6.3, 10A.7). */ -export async function tutorialBoard( - tx: Queryable, - playerId: string, - now: RealTime, -): Promise { - const [player] = await tx.select().from(players).where(eq(players.id, playerId)); - if (!player?.tutorialActive || !player.loanerLocationId) { - throw gameError("not-in-tutorial", "The tutorial is over."); - } - const loanerLocationId = player.loanerLocationId as "earth" | "moon"; - const periodIndex = boardPeriodIndex(now, content.config); - const runsCompleted = player.tutorialRunsCompleted; - // One board per run (and per period), so every run gets three fresh contracts. - const contracts = await getOrCreateBoard( - tx, - { - playerId, - kind: "tutorial", - locationId: loanerLocationId, - periodIndex, - tutorialRun: runsCompleted, - }, - () => generateTutorialBoard({ loanerLocationId, runsCompleted, periodIndex }, content), - now, - ); - return boardView(tx, playerId, contracts, periodIndex); -} - -/** - * The contract board (freight market) of any discovered location for the - * current period (Step 6.9, CR-002): no bay needed. Generated on the first - * request in the period from the locations discovered and the fleet owned at - * that moment (CR-006), then stored, so later changes in the same period do - * not change it. - */ -export async function locationBoard( - tx: Queryable, - playerId: string, - locationId: string, - now: RealTime, -): Promise { - const origin = content.location(locationId); - if (!origin) throw gameError("not-found", `No location "${locationId}".`); - const [known] = await tx - .select({ locationId: discoveredLocations.locationId }) - .from(discoveredLocations) - .where( - and( - eq(discoveredLocations.playerId, playerId), - eq(discoveredLocations.locationId, locationId), - ), - ); - if (!known) { - throw gameError("location-undiscovered", `Deliver to ${origin.name} to open its market.`); - } - - const [player] = await tx.select().from(players).where(eq(players.id, playerId)); - const periodIndex = boardPeriodIndex(now, content.config); - const contracts = await getOrCreateBoard( - tx, - { playerId, kind: "location", locationId, periodIndex }, - async () => { - const discovered = await tx - .select({ locationId: discoveredLocations.locationId }) - .from(discoveredLocations) - .where(eq(discoveredLocations.playerId, playerId)); - return generateContractBoard( - { - location: origin, - discoveredIds: new Set(discovered.map((d) => d.locationId)), - periodIndex, - worldSeed: player!.worldSeed, - fleet: await playerFleet(tx, playerId), - }, - content, - ); - }, - now, - ); - return boardView(tx, playerId, contracts, periodIndex); -} - -/** - * The player's ships with their pilots' skills (none without a pilot), in a - * stable order so the same fleet always gives the same board (decision 0.5.52). - */ -export async function playerFleet(tx: Queryable, playerId: string): Promise { - const rows = await tx - .select({ id: ships.id, modelId: ships.modelId, skills: pilots.skills }) - .from(ships) - .leftJoin(pilots, eq(pilots.assignedShipId, ships.id)) - .where(eq(ships.playerId, playerId)) - .orderBy(asc(ships.createdAt), asc(ships.id)); - // Upgrades count: an expanded cargo bay gets bigger fit jobs (CR-009). - const upgrades = await loadUpgradeLevels( - tx, - rows.map((r) => r.id), - ); - return rows.map((r) => ({ - modelId: r.modelId, - skills: r.skills ?? {}, - upgrades: upgrades.get(r.id) ?? {}, - })); -} diff --git a/apps/server/src/game/company.ts b/apps/server/src/game/company.ts deleted file mode 100644 index 3da176d..0000000 --- a/apps/server/src/game/company.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { Company } from "@deadhead/schema"; -import { eq } from "drizzle-orm"; -import type { Queryable } from "../db/client"; -import { companies } from "../db/schema"; - -/** Renames the player's company (decision 0.5.75); the name arrives validated and trimmed. */ -export async function renameCompany( - tx: Queryable, - playerId: string, - name: string, -): Promise { - const [updated] = await tx - .update(companies) - .set({ name }) - .where(eq(companies.playerId, playerId)) - .returning(); - if (!updated) throw new Error(`Player ${playerId} has no company`); - return { - id: updated.id, - name: updated.name, - credits: updated.credits, - loanBalance: updated.loanBalance, - }; -} diff --git a/apps/server/src/game/deal.ts b/apps/server/src/game/deal.ts deleted file mode 100644 index 2590f55..0000000 --- a/apps/server/src/game/deal.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { randomUUID } from "node:crypto"; -import { content } from "@deadhead/content"; -import type { RealTime } from "@deadhead/sim"; -import { and, count, eq } from "drizzle-orm"; -import type { Queryable } from "../db/client"; -import { bays, companies, dispatches, pilots, players, ships } from "../db/schema"; -import { gameError } from "../http/errors"; -import { shipName } from "./names"; - -/** What the deal hands over (decisions 0.5.30, 0.5.82), from content. */ -const { avatarShipModelId, veteranShipModelId, bayLocationId } = content.config.deal; -export const DEAL_SHIP_MODEL_ID = avatarShipModelId; -export const DEAL_BAY_LOCATION_ID = bayLocationId; - -/** - * The deal can be accepted now (decision 0.5.30): tutorial on, enough runs - * completed, the loaner back at Earth, and no tutorial run in flight. - */ -export function isDealAvailable( - player: typeof players.$inferSelect, - tutorialRunInFlight: boolean, -): boolean { - return ( - player.tutorialActive && - !player.dealAccepted && - player.tutorialRunsCompleted >= content.config.tutorialRunsBeforeDeal && - player.loanerLocationId === "earth" && - !tutorialRunInFlight - ); -} - -/** - * Accepts the deal (Steps 10A.8, 10M.3): the loaner goes back to the company; - * the player gets the old Courier at Earth crewed by the avatar, the Clipper - * crewed by the veteran, the Earth bay, and the loan; the tutorial ends. Run inside the player lock, so two simultaneous - * accepts produce one deal. - */ -export async function acceptDeal(tx: Queryable, playerId: string, now: RealTime): Promise { - const [player] = await tx.select().from(players).where(eq(players.id, playerId)); - if (!player) throw new Error(`Player ${playerId} not found`); - const [running] = await tx - .select({ id: dispatches.id }) - .from(dispatches) - .where( - and( - eq(dispatches.playerId, playerId), - eq(dispatches.tutorial, true), - eq(dispatches.status, "in_flight"), - ), - ); - if (!isDealAvailable(player, running !== undefined)) { - throw gameError("deal-not-available", "There is no deal on the table right now."); - } - - const [owned] = await tx.select({ n: count() }).from(ships).where(eq(ships.playerId, playerId)); - const shipId = randomUUID(); - const clipperId = randomUUID(); - await tx.insert(ships).values( - [ - { id: shipId, modelId: avatarShipModelId }, - { id: clipperId, modelId: veteranShipModelId }, - ].map((ship, i) => ({ - ...ship, - playerId, - name: shipName(player.worldSeed, owned!.n + i), - locationId: bayLocationId, - fromDeal: true, - createdAt: now, - })), - ); - await tx.insert(bays).values({ - id: randomUUID(), - playerId, - locationId: DEAL_BAY_LOCATION_ID, - createdAt: now, - }); - await tx - .update(pilots) - .set({ assignedShipId: shipId, locationId: null, status: "idle" }) - .where(and(eq(pilots.playerId, playerId), eq(pilots.isAvatar, true))); - // Halvorsen's veteran comes with the business (decisions 0.5.70, 0.5.82), - // crewing his Clipper, at their level with their skills bought and the rest unspent. - const veteran = content.veteranPilot; - const xpForLevel = - veteran.level === 0 ? 0 : content.config.pilotLevelXpThresholds[veteran.level - 1]!; - await tx.insert(pilots).values({ - id: randomUUID(), - playerId, - name: veteran.name, - isVeteran: true, - xp: xpForLevel, - skills: veteran.skills, - skillPointsSpent: Object.values(veteran.skills).reduce((a, b) => a + b, 0), - assignedShipId: clipperId, - // Assigned pilots are where their ship is (as for the avatar). - locationId: null, - createdAt: now, - }); - await tx - .update(companies) - .set({ loanBalance: content.dealLoanAmount }) - .where(eq(companies.playerId, playerId)); - await tx - .update(players) - .set({ tutorialActive: false, loanerLocationId: null, dealAccepted: true }) - .where(eq(players.id, playerId)); -} diff --git a/apps/server/src/game/dispatch.ts b/apps/server/src/game/dispatch.ts deleted file mode 100644 index 6bcd727..0000000 --- a/apps/server/src/game/dispatch.ts +++ /dev/null @@ -1,307 +0,0 @@ -import { randomUUID } from "node:crypto"; -import { content } from "@deadhead/content"; -import type { Dispatch } from "@deadhead/schema"; -import { - createDispatchRecord, - createRepositionRecord, - loanerModel, - validateDispatch, - validateReposition, - type Contract, - type DispatchRecord, - type PilotState, - type RealTime, - type ShipState, - type UpgradeLevels, -} from "@deadhead/sim"; -import { and, eq, sql } from "drizzle-orm"; -import type { Queryable } from "../db/client"; -import { - acceptedContracts, - companies, - contractBoards, - discoveredLocations, - dispatches, - pilots, - players, - ships, -} from "../db/schema"; -import { gameError } from "../http/errors"; -import { acceptedContractIds } from "./boards"; -import { requireCompany, requirePlayer } from "./fleet"; -import { toApiDispatch } from "./mappers"; -import { VALIDATION_MESSAGES } from "./messages"; -import { requireOwnPilot, requireOwnShip } from "./pilots"; -import { shipUpgradeLevels } from "./upgrades"; - -/** The id the tutorial loaner uses in dispatch records (it is not a ship row). */ -export const LOANER_SHIP_ID = "loaner"; - -/** Finds a contract on any of the player's stored boards of a kind. */ -export async function findBoardContract( - tx: Queryable, - playerId: string, - kind: "location" | "tutorial", - contractId: string, -): Promise<{ contract: Contract; periodIndex: number } | undefined> { - const [board] = await tx - .select() - .from(contractBoards) - .where( - and( - eq(contractBoards.playerId, playerId), - eq(contractBoards.kind, kind), - sql`${contractBoards.contracts} @> ${JSON.stringify([{ id: contractId }])}::jsonb`, - ), - ); - const contract = board?.contracts.find((c) => c.id === contractId); - return board && contract ? { contract, periodIndex: board.periodIndex } : undefined; -} - -/** Loads a board contract the player may still accept, or throws. */ -export async function claimableContract( - tx: Queryable, - playerId: string, - kind: "location" | "tutorial", - contractId: string, -): Promise<{ contract: Contract; periodIndex: number }> { - const found = await findBoardContract(tx, playerId, kind, contractId); - if (!found) throw gameError("unknown-contract", "No such contract on your boards."); - if ((await acceptedContractIds(tx, playerId)).has(contractId)) { - throw gameError("contract-already-accepted", "That contract has already been taken."); - } - return found; -} - -export function pilotState(row: typeof pilots.$inferSelect): PilotState { - return { - id: row.id, - isAvatar: row.isAvatar, - assignedShipId: row.assignedShipId, - inFlight: row.status === "in_flight", - skills: row.skills, - restEndsAt: row.restEndsAt, - }; -} - -/** Stores a dispatch, marks its contract accepted, and charges fuel. */ -export async function storeDispatch( - tx: Queryable, - args: { - playerId: string; - record: DispatchRecord; - /** Null for the tutorial loaner. */ - shipId: string | null; - /** The board period the contract came from; null for a reposition. */ - periodIndex: number | null; - now: RealTime; - }, -): Promise { - const { playerId, record, shipId, now } = args; - const [row] = await tx - .insert(dispatches) - .values({ - id: record.id, - playerId, - shipId, - pilotId: record.pilotId, - tutorial: record.tutorial, - record, - arrivalAt: record.arrivalAt, - }) - .returning(); - if (record.contract && args.periodIndex !== null) { - await tx.insert(acceptedContracts).values({ - playerId, - contractId: record.contract.id, - periodIndex: args.periodIndex, - dispatchId: record.id, - acceptedAt: now, - }); - } - if (record.fuelCost > 0) { - await tx - .update(companies) - .set({ credits: sql`${companies.credits} - ${record.fuelCost}` }) - .where(eq(companies.playerId, playerId)); - } - return toApiDispatch(row!); -} - -/** - * The avatar flies the company loaner on a tutorial contract (Step 6.4). The - * company pays fuel, and only one tutorial run may be in flight. - */ -export async function dispatchTutorial( - tx: Queryable, - playerId: string, - contractId: string, - now: RealTime, -): Promise { - const [player] = await tx.select().from(players).where(eq(players.id, playerId)); - if (!player?.tutorialActive || !player.loanerLocationId) { - throw gameError("not-in-tutorial", "The tutorial is over."); - } - const [running] = await tx - .select({ id: dispatches.id }) - .from(dispatches) - .where( - and( - eq(dispatches.playerId, playerId), - eq(dispatches.tutorial, true), - eq(dispatches.status, "in_flight"), - ), - ); - if (running) throw gameError("tutorial-run-in-flight", "Your tutorial run is still flying."); - - const { contract, periodIndex } = await claimableContract(tx, playerId, "tutorial", contractId); - const [avatar] = await tx - .select() - .from(pilots) - .where(and(eq(pilots.playerId, playerId), eq(pilots.isAvatar, true))); - const [company] = await tx.select().from(companies).where(eq(companies.playerId, playerId)); - if (!avatar || !company) throw new Error(`Player ${playerId} is missing its avatar or company`); - - const loaner: ShipState = { - id: LOANER_SHIP_ID, - modelId: loanerModel(content).id, - locationId: player.loanerLocationId, - inFlight: false, - }; - const pilot = pilotState(avatar); - const check = validateDispatch( - { ship: loaner, pilot, contract, tutorial: true, credits: company.credits, now }, - content, - ); - if (!check.ok) throw gameError(check.reason, VALIDATION_MESSAGES[check.reason]); - - const record = createDispatchRecord( - { - id: randomUUID(), - ship: loaner, - pilot, - contract, - tutorial: true, - now, - worldSeed: player.worldSeed, - }, - content, - ); - const dispatch = await storeDispatch(tx, { playerId, record, shipId: null, periodIndex, now }); - await tx - .update(pilots) - .set({ status: "in_flight", locationId: null }) - .where(eq(pilots.id, avatar.id)); - return dispatch; -} - -export function shipState(row: typeof ships.$inferSelect, upgrades: UpgradeLevels = {}): ShipState { - return { - id: row.id, - modelId: row.modelId, - // An in-flight ship has no location; validation reports it as not idle first. - locationId: row.locationId ?? "", - inFlight: row.status === "in_flight", - // Effective stats and the shop block (CR-009). - upgrades, - inShopUntil: row.inShopUntil, - }; -} - -/** - * Dispatches an owned ship and its pilot on a location-board contract, from - * wherever the ship is (Steps 6.10, 10B.5; no bay involved): - * sim validation, fuel charged, contract accepted, ship and pilot in flight. - */ -export async function dispatchContract( - tx: Queryable, - playerId: string, - input: { contractId: string; shipId: string; pilotId: string }, - now: RealTime, -): Promise { - const { contract, periodIndex } = await claimableContract( - tx, - playerId, - "location", - input.contractId, - ); - const ship = await requireOwnShip(tx, playerId, input.shipId); - const pilot = await requireOwnPilot(tx, playerId, input.pilotId); - const player = await requirePlayer(tx, playerId); - const company = await requireCompany(tx, playerId); - const discovered = await tx - .select({ locationId: discoveredLocations.locationId }) - .from(discoveredLocations) - .where(eq(discoveredLocations.playerId, playerId)); - - const proposal = { - ship: shipState(ship, await shipUpgradeLevels(tx, ship.id)), - pilot: pilotState(pilot), - contract, - // The no-strand rule counts only discovered locations (decision 0.5.79). - discoveredIds: new Set(discovered.map((d) => d.locationId)), - tutorial: false, - credits: company.credits, - now, - }; - const check = validateDispatch(proposal, content); - if (!check.ok) throw gameError(check.reason, VALIDATION_MESSAGES[check.reason]); - - const record = createDispatchRecord( - { id: randomUUID(), ...proposal, worldSeed: player.worldSeed }, - content, - ); - const dispatch = await storeDispatch(tx, { playerId, record, shipId: ship.id, periodIndex, now }); - await setInFlight(tx, ship.id, pilot.id); - return dispatch; -} - -export async function setInFlight(tx: Queryable, shipId: string, pilotId: string) { - await tx.update(ships).set({ status: "in_flight", locationId: null }).where(eq(ships.id, shipId)); - await tx - .update(pilots) - .set({ status: "in_flight", locationId: null }) - .where(eq(pilots.id, pilotId)); -} - -/** Deadhead: flies a ship and its pilot empty to another discovered location, paying fuel only (Steps 6.11, 10B.2). */ -export async function dispatchReposition( - tx: Queryable, - playerId: string, - input: { shipId: string; pilotId: string; destinationId: string }, - now: RealTime, -): Promise { - const ship = await requireOwnShip(tx, playerId, input.shipId); - const pilot = await requireOwnPilot(tx, playerId, input.pilotId); - const player = await requirePlayer(tx, playerId); - const company = await requireCompany(tx, playerId); - const discovered = await tx - .select({ locationId: discoveredLocations.locationId }) - .from(discoveredLocations) - .where(eq(discoveredLocations.playerId, playerId)); - - const proposal = { - ship: shipState(ship, await shipUpgradeLevels(tx, ship.id)), - pilot: pilotState(pilot), - destinationId: input.destinationId, - discoveredIds: new Set(discovered.map((d) => d.locationId)), - credits: company.credits, - now, - }; - const check = validateReposition(proposal, content); - if (!check.ok) throw gameError(check.reason, VALIDATION_MESSAGES[check.reason]); - - const record = createRepositionRecord( - { id: randomUUID(), ...proposal, worldSeed: player.worldSeed }, - content, - ); - const dispatch = await storeDispatch(tx, { - playerId, - record, - shipId: ship.id, - periodIndex: null, - now, - }); - await setInFlight(tx, ship.id, pilot.id); - return dispatch; -} diff --git a/apps/server/src/game/fleet.ts b/apps/server/src/game/fleet.ts deleted file mode 100644 index 764972a..0000000 --- a/apps/server/src/game/fleet.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { randomUUID } from "node:crypto"; -import { content } from "@deadhead/content"; -import type { Bay, Ship } from "@deadhead/schema"; -import type { RealTime } from "@deadhead/sim"; -import { and, count, eq, sql } from "drizzle-orm"; -import type { Queryable } from "../db/client"; -import { bays, companies, discoveredLocations, players, ships } from "../db/schema"; -import { gameError } from "../http/errors"; -import { toApiShip } from "./mappers"; -import { shipName } from "./names"; - -export async function requireCompany(tx: Queryable, playerId: string) { - const [company] = await tx.select().from(companies).where(eq(companies.playerId, playerId)); - if (!company) throw new Error(`Player ${playerId} has no company`); - return company; -} - -export async function requirePlayer(tx: Queryable, playerId: string) { - const [player] = await tx.select().from(players).where(eq(players.id, playerId)); - if (!player) throw new Error(`Player ${playerId} not found`); - return player; -} - -/** Purchases are blocked while the tutorial runs (decision 0.5.16, CR-001). */ -export async function requireTutorialOver(tx: Queryable, playerId: string, what: string) { - const player = await requirePlayer(tx, playerId); - if (player.tutorialActive) { - throw gameError("tutorial-active", `Finish the company runs before ${what}.`); - } -} - -/** Deducts credits, or throws `insufficient-funds` without changing anything. */ -export async function spend(tx: Queryable, playerId: string, amount: number, what: string) { - const company = await requireCompany(tx, playerId); - if (company.credits < amount) { - throw gameError("insufficient-funds", `Not enough credits for ${what} (${amount} needed).`); - } - await tx - .update(companies) - .set({ credits: sql`${companies.credits} - ${amount}` }) - .where(eq(companies.playerId, playerId)); -} - -/** Leases a bay at a discovered location for its one-time price (Step 6.6). */ -export async function leaseBay( - tx: Queryable, - playerId: string, - locationId: string, - now: RealTime, -): Promise { - await requireTutorialOver(tx, playerId, "leasing a bay"); - const location = content.location(locationId); - if (!location) throw gameError("not-found", `No location "${locationId}".`); - - const [discovered] = await tx - .select() - .from(discoveredLocations) - .where( - and( - eq(discoveredLocations.playerId, playerId), - eq(discoveredLocations.locationId, locationId), - ), - ); - if (!discovered) { - throw gameError( - "location-undiscovered", - `Deliver to ${location.name} before leasing a bay there.`, - ); - } - const [existing] = await tx - .select() - .from(bays) - .where(and(eq(bays.playerId, playerId), eq(bays.locationId, locationId))); - if (existing) - throw gameError("bay-already-leased", `You already have a bay at ${location.name}.`); - - await spend(tx, playerId, location.bayLeasePrice, `a bay at ${location.name}`); - const [bay] = await tx - .insert(bays) - .values({ id: randomUUID(), playerId, locationId, createdAt: now }) - .returning(); - return { id: bay!.id, locationId: bay!.locationId }; -} - -/** - * Buys a ship model, delivered idle and uncrewed to one of the player's bays - * (Steps 6.5, 10B.6; decision 0.5.35). - */ -export async function buyShip( - tx: Queryable, - playerId: string, - input: { modelId: string; bayId: string }, - now: RealTime, -): Promise { - const { modelId } = input; - await requireTutorialOver(tx, playerId, "buying a ship"); - const model = content.shipModel(modelId); - if (!model?.purchasable) throw gameError("not-found", `No ship model "${modelId}" for sale.`); - const [bay] = await tx - .select() - .from(bays) - .where(and(eq(bays.playerId, playerId), eq(bays.id, input.bayId))); - if (!bay) throw gameError("not-found", "You have no such bay."); - await spend(tx, playerId, model.price, `a ${model.name}`); - - const player = await requirePlayer(tx, playerId); - const [owned] = await tx.select({ n: count() }).from(ships).where(eq(ships.playerId, playerId)); - const [ship] = await tx - .insert(ships) - .values({ - id: randomUUID(), - playerId, - modelId, - name: shipName(player.worldSeed, owned!.n), - locationId: bay.locationId, - createdAt: now, - }) - .returning(); - return toApiShip( - ship!, - { dispatchId: null, pilotId: null, lastArrivalAt: ship!.createdAt, upgrades: {} }, - now, - ); -} diff --git a/apps/server/src/game/goals.ts b/apps/server/src/game/goals.ts deleted file mode 100644 index 6801022..0000000 --- a/apps/server/src/game/goals.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { content } from "@deadhead/content"; -import type { Company, GoalState } from "@deadhead/schema"; -import { completedGoals, type GoalProgress, type RealTime } from "@deadhead/sim"; -import { and, count, eq, notInArray, sql, sum } from "drizzle-orm"; -import type { Queryable } from "../db/client"; -import { - claimedGoals, - companies, - discoveredLocations, - dispatches, - pilots, - ships, -} from "../db/schema"; -import { gameError } from "../http/errors"; -import { requireCompany, requirePlayer } from "./fleet"; - -const START_LOCATION_IDS = content.locations.filter((l) => l.startsDiscovered).map((l) => l.id); - -/** The facts goals are checked against, read from the database (Step 10A.10). */ -export async function goalProgress(tx: Queryable, playerId: string): Promise { - const player = await requirePlayer(tx, playerId); - const company = await requireCompany(tx, playerId); - const [deliveries] = await tx - .select({ n: count() }) - .from(dispatches) - .where( - and( - eq(dispatches.playerId, playerId), - eq(dispatches.status, "resolved"), - eq(dispatches.tutorial, false), - // Repositions store a null contract. - sql`${dispatches.record} -> 'contract' <> 'null'::jsonb`, - ), - ); - // Priority contracts delivered, by any pilot (CR-013). - const [priorityDeliveries] = await tx - .select({ n: count() }) - .from(dispatches) - .where( - and( - eq(dispatches.playerId, playerId), - eq(dispatches.status, "resolved"), - eq(dispatches.tutorial, false), - sql`(${dispatches.record} -> 'contract' ->> 'priority') = 'true'`, - ), - ); - const [spent] = await tx - .select({ n: sum(pilots.skillPointsSpent) }) - .from(pilots) - .where(eq(pilots.playerId, playerId)); - const [owned] = await tx.select({ n: count() }).from(ships).where(eq(ships.playerId, playerId)); - // The deal's ships don't count as Market purchases (CR-013). - const [bought] = await tx - .select({ n: count() }) - .from(ships) - .where(and(eq(ships.playerId, playerId), eq(ships.fromDeal, false))); - const [hired] = await tx - .select({ n: count() }) - .from(pilots) - .where(and(eq(pilots.playerId, playerId), eq(pilots.isAvatar, false))); - const [discovered] = await tx - .select({ n: count() }) - .from(discoveredLocations) - .where( - and( - eq(discoveredLocations.playerId, playerId), - notInArray(discoveredLocations.locationId, START_LOCATION_IDS), - ), - ); - return { - ownShipDeliveries: deliveries!.n, - skillPointsSpent: Number(spent?.n ?? 0), - shipsOwned: owned!.n, - marketShipsBought: bought!.n, - pilotsHired: hired!.n, - priorityDeliveries: priorityDeliveries!.n, - locationsDiscovered: discovered!.n, - dealAccepted: player.dealAccepted, - loanBalance: company.loanBalance, - }; -} - -async function claimedIds(tx: Queryable, playerId: string): Promise> { - const rows = await tx - .select({ goalId: claimedGoals.goalId }) - .from(claimedGoals) - .where(eq(claimedGoals.playerId, playerId)); - return new Set(rows.map((r) => r.goalId)); -} - -/** Every goal's status in content order; none while the tutorial runs (decision 0.5.34). */ -export async function goalStates(tx: Queryable, playerId: string): Promise { - const player = await requirePlayer(tx, playerId); - if (player.tutorialActive) return []; - const complete = new Set( - completedGoals(content.onboardingGoals, await goalProgress(tx, playerId)), - ); - const claimed = await claimedIds(tx, playerId); - return content.onboardingGoals.map((goal) => ({ - id: goal.id, - status: claimed.has(goal.id) ? "claimed" : complete.has(goal.id) ? "ready" : "incomplete", - })); -} - -/** Claims a completed goal's reward, once. Run inside the player lock. */ -export async function claimGoal( - tx: Queryable, - playerId: string, - goalId: string, - now: RealTime, -): Promise { - const goal = content.onboardingGoal(goalId); - if (!goal) throw gameError("unknown-goal", `No goal "${goalId}".`); - const status = (await goalStates(tx, playerId)).find((g) => g.id === goalId)?.status; - if (status === "claimed") - throw gameError("goal-already-claimed", "You already claimed that reward."); - if (status !== "ready") throw gameError("goal-not-ready", `Not done yet: ${goal.title}.`); - - await tx.insert(claimedGoals).values({ playerId, goalId, claimedAt: now }); - const [company] = await tx - .update(companies) - .set({ credits: sql`${companies.credits} + ${goal.reward}` }) - .where(eq(companies.playerId, playerId)) - .returning(); - return { - id: company!.id, - name: company!.name, - credits: company!.credits, - loanBalance: company!.loanBalance, - }; -} diff --git a/apps/server/src/game/loan.ts b/apps/server/src/game/loan.ts deleted file mode 100644 index 0df2c44..0000000 --- a/apps/server/src/game/loan.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { Company } from "@deadhead/schema"; -import { eq, sql } from "drizzle-orm"; -import type { Queryable } from "../db/client"; -import { companies } from "../db/schema"; -import { gameError } from "../http/errors"; -import { requireCompany } from "./fleet"; - -/** - * Pays the loan down by up to `amount` credits (decision 0.5.32): only the - * balance is taken, and paying more than the player has fails with no change. - * Run inside the player lock. - */ -export async function repayLoan(tx: Queryable, playerId: string, amount: number): Promise { - const company = await requireCompany(tx, playerId); - const payment = Math.min(amount, company.loanBalance); - if (payment > company.credits) { - throw gameError("insufficient-funds", `You only have ${company.credits} credits.`); - } - const [updated] = await tx - .update(companies) - .set({ - credits: sql`${companies.credits} - ${payment}`, - loanBalance: sql`${companies.loanBalance} - ${payment}`, - }) - .where(eq(companies.playerId, playerId)) - .returning(); - return { - id: updated!.id, - name: updated!.name, - credits: updated!.credits, - loanBalance: updated!.loanBalance, - }; -} diff --git a/apps/server/src/game/lock.ts b/apps/server/src/game/lock.ts deleted file mode 100644 index d2f4a57..0000000 --- a/apps/server/src/game/lock.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { eq } from "drizzle-orm"; -import type { Db, Queryable } from "../db/client"; -import { companies } from "../db/schema"; - -/** - * Runs `fn` in a transaction holding a row lock on the player's company, so - * changes to one player's money and fleet never interleave (Step 6.14). - */ -export function withPlayerLock( - db: Db, - playerId: string, - fn: (tx: Queryable) => Promise, -): Promise { - return db.transaction(async (tx) => { - await tx - .select({ id: companies.id }) - .from(companies) - .where(eq(companies.playerId, playerId)) - .for("update"); - return fn(tx); - }); -} diff --git a/apps/server/src/game/mappers.ts b/apps/server/src/game/mappers.ts deleted file mode 100644 index c49924c..0000000 --- a/apps/server/src/game/mappers.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { content } from "@deadhead/content"; -import type { Dispatch, Pilot, Ship } from "@deadhead/schema"; -import { - dutyLimit, - effectiveStats, - isInShop, - isResting, - pilotLevel, - skillPointsAvailable, - type RealTime, -} from "@deadhead/sim"; -import type { dispatches, pilots, ships } from "../db/schema"; - -/** Database status values use `in_flight`; the API uses `in-flight`. */ -export function toApiStatus( - status: S, -): S extends "in_flight" ? "in-flight" : S { - return (status === "in_flight" ? "in-flight" : status) as S extends "in_flight" ? "in-flight" : S; -} - -export function toApiDispatch(row: typeof dispatches.$inferSelect): Dispatch { - return { - ...row.record, - status: toApiStatus(row.status), - resolvedAt: row.resolvedAt, - }; -} - -/** - * API view of a pilot. An assigned pilot is wherever its ship is - * (`assignedShipLocationId`); an unassigned pilot stores its own location. - */ -export function toApiPilot( - row: typeof pilots.$inferSelect, - assignedShipLocationId: string | null, - now: RealTime, -): Pilot { - const resting = isResting(row.restEndsAt, now); - const level = pilotLevel(row.xp, content.config); - return { - id: row.id, - name: row.name, - isAvatar: row.isAvatar, - isVeteran: row.isVeteran, - xp: row.xp, - level, - skills: row.skills, - skillPointsAvailable: skillPointsAvailable(level, row.skillPointsSpent, content.config), - assignedShipId: row.assignedShipId, - status: toApiStatus(row.status), - locationId: - row.status === "in_flight" - ? null - : row.assignedShipId - ? assignedShipLocationId - : row.locationId, - dutyHours: row.dutyHours, - dutyLimit: dutyLimit(row.skills, content.config), - resting, - restEndsAt: resting ? row.restEndsAt : null, - }; -} - -/** A ship row as the API shows it, with its upgrades and effective stats (CR-009). */ -export function toApiShip( - row: typeof ships.$inferSelect, - extra: { - dispatchId: string | null; - pilotId: string | null; - lastArrivalAt: number; - upgrades: Record; - }, - now: RealTime, -): Ship { - const model = content.shipModel(row.modelId); - if (!model) throw new Error(`Ship ${row.id} has unknown model ${row.modelId}`); - const stats = effectiveStats(model, extra.upgrades, content); - return { - id: row.id, - modelId: row.modelId, - name: row.name, - status: toApiStatus(row.status), - locationId: row.status === "in_flight" ? null : row.locationId, - dispatchId: extra.dispatchId, - pilotId: extra.pilotId, - lastArrivalAt: extra.lastArrivalAt, - upgrades: extra.upgrades, - stats: { - cruiseSpeedKmPerGameHour: stats.cruiseSpeedKmPerGameHour, - cargoCapacityTonnes: stats.cargoCapacityTonnes, - fuelRangeMillionKm: stats.fuelRangeMillionKm, - overheadGameHours: stats.overheadGameHours, - handlingGameHoursPerTonne: stats.handlingGameHoursPerTonne, - }, - inShopUntil: isInShop(row, now) ? row.inShopUntil : null, - shop: - row.shopUpgradeId && row.shopLevel !== null && row.inShopUntil !== null - ? { upgradeId: row.shopUpgradeId, level: row.shopLevel, until: row.inShopUntil } - : null, - }; -} diff --git a/apps/server/src/game/messages.ts b/apps/server/src/game/messages.ts deleted file mode 100644 index a7d65c4..0000000 --- a/apps/server/src/game/messages.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { DispatchFailure, RepositionFailure, UpgradeFailure } from "@deadhead/sim"; - -/** Player-facing text for each sim validation reason. */ -export const VALIDATION_MESSAGES: Record = { - "ship-not-idle": "That ship is already flying.", - "ship-not-at-origin": "That ship is not at the contract's pickup location.", - "ship-in-shop": "That ship is in the upgrade shop until its work is done.", - "pilot-resting": "That pilot is resting and cannot fly until their rest ends.", - "pilot-not-idle": "That pilot is already flying.", - "pilot-not-assigned": "That pilot is not assigned to that ship.", - "over-capacity": "The cargo is too heavy for that ship.", - "cannot-land": "That ship cannot land at the destination.", - "out-of-range": "The destination is out of that ship's range right now.", - "would-strand": - "That ship couldn't fly anywhere else from the destination, so it would be stranded there.", - "too-slow": "That ship can't make this Priority delivery's window.", - "cargo-skill-too-low": "The pilot's cargo skill is too low for this contract.", - "long-haul-too-low": "The pilot's Long Haul skill is too low for this distance.", - "contract-expired": "This contract has expired.", - "insufficient-credits": "Not enough credits for fuel.", - "already-there": "The ship is already there.", - "location-undiscovered": "You can only fly empty to places you have discovered.", -}; - -/** Player-facing text for each upgrade refusal (CR-009). */ -export const UPGRADE_MESSAGES: Record = { - "tutorial-active": "Finish the company runs before upgrading ships.", - "not-upgradable": "The company loaner can't be upgraded.", - "ship-not-idle": "That ship is flying; upgrade it once it lands at an HQ.", - "ship-in-shop": "That ship is already in the shop.", - "not-at-hq": "Upgrades are installed at an HQ; bring the ship to one of your bays.", - "upgrade-maxed": "That upgrade is already at its highest level.", - "insufficient-funds": "Not enough credits for that upgrade.", -}; diff --git a/apps/server/src/game/names.ts b/apps/server/src/game/names.ts deleted file mode 100644 index 72e80b3..0000000 --- a/apps/server/src/game/names.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { createRng, deriveSeed, type Seed } from "@deadhead/sim"; - -const SHIP_ADJECTIVES = [ - "Dusty", - "Rusty", - "Lucky", - "Stubborn", - "Quiet", - "Restless", - "Patient", - "Wandering", - "Crooked", - "Faithful", - "Midnight", - "Copper", - "Iron", - "Silver", - "Old", - "Brave", -]; -const SHIP_NOUNS = [ - "Mule", - "Ox", - "Heron", - "Comet", - "Lantern", - "Drifter", - "Kettle", - "Anchor", - "Sparrow", - "Badger", - "Tortoise", - "Pilgrim", - "Barge", - "Nomad", - "Hauler", - "Wren", -]; -const FIRST_NAMES = [ - "Rae", - "Jonah", - "Mira", - "Tobias", - "Ines", - "Kofi", - "Lena", - "Arjun", - "Sasha", - "Dmitri", - "Yuki", - "Nadia", - "Emeka", - "Freya", - "Mateo", - "Priya", -]; -const LAST_NAMES = [ - "Okafor", - "Lindqvist", - "Moreau", - "Tanaka", - "Reyes", - "Novak", - "Haddad", - "Kowalski", - "Mbeki", - "Castillo", - "Sorensen", - "Iyer", - "Brennan", - "Vasquez", - "Kim", - "Abara", -]; - -/** The nth ship name in a world (decision 0.5.14): stable for a given seed and n. */ -export function shipName(worldSeed: Seed, n: number): string { - const rng = createRng(deriveSeed(worldSeed, `ship-name:${n}`)); - return `${rng.pick(SHIP_ADJECTIVES)} ${rng.pick(SHIP_NOUNS)}`; -} - -/** The nth hired pilot's name in a world: stable for a given seed and n. */ -export function pilotName(worldSeed: Seed, n: number): string { - const rng = createRng(deriveSeed(worldSeed, `pilot-name:${n}`)); - return `${rng.pick(FIRST_NAMES)} ${rng.pick(LAST_NAMES)}`; -} - -const COMPANY_FIRST = [ - "Halcyon", - "Meridian", - "Lodestar", - "Northstar", - "Bluewater", - "Redline", - "Ironclad", - "Kestrel", - "Copperhead", - "Longhaul", - "Brightwater", - "Stonebridge", -]; -const COMPANY_LAST = [ - "Freight", - "Haulage", - "Cargo", - "Logistics", - "Shipping", - "Transit", - "Lines", - "Carriers", -]; - -/** A company's default name, stable for its world seed (decision 0.5.75). */ -export function companyName(worldSeed: Seed): string { - const rng = createRng(deriveSeed(worldSeed, "company-name")); - return `${rng.pick(COMPANY_FIRST)} ${rng.pick(COMPANY_LAST)}`; -} diff --git a/apps/server/src/game/new-company.ts b/apps/server/src/game/new-company.ts deleted file mode 100644 index 62d49d9..0000000 --- a/apps/server/src/game/new-company.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { randomInt, randomUUID } from "node:crypto"; -import type { RealTime } from "@deadhead/sim"; -import { eq } from "drizzle-orm"; -import type { Queryable } from "../db/client"; -import { - acceptedContracts, - bays, - claimedGoals, - companies, - contractBoards, - discoveredLocations, - dispatches, - pilots, - players, - ships, -} from "../db/schema"; -import { requirePlayer } from "./fleet"; -import { createStartingState } from "./new-player"; - -/** - * "Start a new company" (decision 0.5.74), inside the caller's player-locked - * transaction: deletes everything the company owns, then builds the starting - * state exactly as guest creation does, with a new world seed. The player - * (and so their sign-ins and backup code) stays. - */ -export async function newCompany(tx: Queryable, playerId: string, now: RealTime): Promise { - const player = await requirePlayer(tx, playerId); - // Children before parents; ship upgrades go with their ships. - await tx.delete(claimedGoals).where(eq(claimedGoals.playerId, playerId)); - await tx.delete(acceptedContracts).where(eq(acceptedContracts.playerId, playerId)); - await tx.delete(dispatches).where(eq(dispatches.playerId, playerId)); - await tx.delete(contractBoards).where(eq(contractBoards.playerId, playerId)); - await tx.delete(discoveredLocations).where(eq(discoveredLocations.playerId, playerId)); - await tx.delete(bays).where(eq(bays.playerId, playerId)); - await tx.delete(pilots).where(eq(pilots.playerId, playerId)); - await tx.delete(ships).where(eq(ships.playerId, playerId)); - await tx.delete(companies).where(eq(companies.playerId, playerId)); - - // A 32-bit seed (decision 0.5.10), never the old one. - let worldSeed = randomInt(0, 2 ** 32); - while (worldSeed === player.worldSeed) worldSeed = randomInt(0, 2 ** 32); - await tx - .update(players) - .set({ - worldSeed, - tutorialActive: true, - loanerLocationId: "earth", - tutorialRunsCompleted: 0, - dealAccepted: false, - }) - .where(eq(players.id, playerId)); - await createStartingState(tx, { - playerId, - companyId: randomUUID(), - avatarId: randomUUID(), - worldSeed, - now, - }); -} diff --git a/apps/server/src/game/new-player.ts b/apps/server/src/game/new-player.ts deleted file mode 100644 index e27ea4b..0000000 --- a/apps/server/src/game/new-player.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { randomUUID } from "node:crypto"; -import { content } from "@deadhead/content"; -import type { RealTime, Seed } from "@deadhead/sim"; -import type { Db, Queryable } from "../db/client"; -import { companies, discoveredLocations, pilots, players } from "../db/schema"; -import { companyName } from "./names"; - -export const AVATAR_NAME = "Captain"; - -export interface NewPlayer { - playerId: string; - companyId: string; - avatarId: string; -} - -/** - * Creates a fresh player in tutorial mode: starting credits, every location - * that starts discovered, the avatar (level 0, unassigned), and the loaner at - * Earth. The avatar's position comes from the loaner while the tutorial runs. - */ -export async function createPlayer( - db: Db, - args: { userId: string; worldSeed: Seed; now: RealTime; playerId?: string }, -): Promise { - const { worldSeed, now } = args; - const playerId = args.playerId ?? randomUUID(); - const companyId = randomUUID(); - const avatarId = randomUUID(); - - await db.transaction(async (tx) => { - await tx.insert(players).values({ - id: playerId, - userId: args.userId, - worldSeed, - tutorialActive: true, - loanerLocationId: "earth", - createdAt: now, - }); - await createStartingState(tx, { playerId, companyId, avatarId, worldSeed, now }); - }); - - return { playerId, companyId, avatarId }; -} - -/** - * A player's starting company (CR-001 tutorial state): starting credits, every - * location that starts discovered, and the avatar at Earth beside the loaner. - * Shared by guest creation and "Start a new company" (decision 0.5.74). - */ -export async function createStartingState( - tx: Queryable, - ids: { playerId: string; companyId: string; avatarId: string; worldSeed: Seed; now: RealTime }, -): Promise { - const { playerId, companyId, avatarId, worldSeed, now } = ids; - await tx.insert(companies).values({ - id: companyId, - playerId, - name: companyName(worldSeed), - credits: content.config.startingCredits, - }); - await tx - .insert(discoveredLocations) - .values( - content.locations - .filter((l) => l.startsDiscovered) - .map((l) => ({ playerId, locationId: l.id, discoveredAt: now })), - ); - await tx.insert(pilots).values({ - id: avatarId, - playerId, - name: AVATAR_NAME, - isAvatar: true, - locationId: "earth", - createdAt: now, - }); -} diff --git a/apps/server/src/game/pilots.ts b/apps/server/src/game/pilots.ts deleted file mode 100644 index 96e8ee2..0000000 --- a/apps/server/src/game/pilots.ts +++ /dev/null @@ -1,217 +0,0 @@ -import { randomUUID } from "node:crypto"; -import { content } from "@deadhead/content"; -import type { Pilot } from "@deadhead/schema"; -import { - isResting, - pilotLevel, - restEndsAt, - skillPointsAvailable, - type RealTime, -} from "@deadhead/sim"; -import { and, count, eq } from "drizzle-orm"; -import type { Queryable } from "../db/client"; -import { bays, pilots, ships } from "../db/schema"; -import { gameError } from "../http/errors"; -import { requirePlayer, requireTutorialOver, spend } from "./fleet"; -import { toApiPilot } from "./mappers"; -import { VALIDATION_MESSAGES } from "./messages"; -import { pilotName } from "./names"; - -export async function hasBayAt( - tx: Queryable, - playerId: string, - locationId: string, -): Promise { - const [bay] = await tx - .select({ id: bays.id }) - .from(bays) - .where(and(eq(bays.playerId, playerId), eq(bays.locationId, locationId))); - return bay !== undefined; -} - -export async function requireOwnPilot(tx: Queryable, playerId: string, pilotId: string) { - const [pilot] = await tx - .select() - .from(pilots) - .where(and(eq(pilots.id, pilotId), eq(pilots.playerId, playerId))); - if (!pilot) throw gameError("not-found", "No such pilot."); - return pilot; -} - -export async function requireOwnShip(tx: Queryable, playerId: string, shipId: string) { - const [ship] = await tx - .select() - .from(ships) - .where(and(eq(ships.id, shipId), eq(ships.playerId, playerId))); - if (!ship) throw gameError("not-found", "No such ship."); - return ship; -} - -/** Hires a level-0 pilot at one of the player's bays (Step 6.8, decision 0.5.22). */ -export async function hirePilot( - tx: Queryable, - playerId: string, - locationId: string, - now: RealTime, -): Promise { - await requireTutorialOver(tx, playerId, "hiring pilots"); - if (!(await hasBayAt(tx, playerId, locationId))) { - throw gameError("no-bay-at-location", "Pilots can only be hired at one of your bays."); - } - await spend(tx, playerId, content.config.pilotHireCost, "a new pilot"); - - const player = await requirePlayer(tx, playerId); - const [hired] = await tx - .select({ n: count() }) - .from(pilots) - .where(and(eq(pilots.playerId, playerId), eq(pilots.isAvatar, false))); - const [pilot] = await tx - .insert(pilots) - .values({ - id: randomUUID(), - playerId, - name: pilotName(player.worldSeed, hired!.n), - locationId, - createdAt: now, - }) - .returning(); - return toApiPilot(pilot!, null, now); -} - -/** - * Assigns an idle, unassigned pilot to an idle, uncrewed ship at the same - * location. The avatar is locked to the loaner during the tutorial (decision 0.5.23). - */ -export async function assignPilot( - tx: Queryable, - playerId: string, - pilotId: string, - shipId: string, - now: RealTime, -): Promise { - const pilot = await requireOwnPilot(tx, playerId, pilotId); - const ship = await requireOwnShip(tx, playerId, shipId); - const player = await requirePlayer(tx, playerId); - - if (pilot.isAvatar && player.tutorialActive) { - throw gameError("avatar-in-tutorial", "You fly the company loaner until the tutorial ends."); - } - if (pilot.status === "in_flight") { - throw gameError("pilot-not-idle", VALIDATION_MESSAGES["pilot-not-idle"]); - } - if (pilot.assignedShipId !== null) { - throw gameError( - "pilot-already-assigned", - "That pilot already has a ship. Unassign them first.", - ); - } - if (ship.status === "in_flight") { - throw gameError("ship-not-idle", VALIDATION_MESSAGES["ship-not-idle"]); - } - if (ship.locationId !== pilot.locationId) { - throw gameError("ship-not-idle-here", "The pilot and the ship must be at the same location."); - } - const [crew] = await tx - .select({ id: pilots.id }) - .from(pilots) - .where(eq(pilots.assignedShipId, shipId)); - if (crew) throw gameError("ship-crewed", "That ship already has a pilot."); - - const [updated] = await tx - .update(pilots) - .set({ assignedShipId: shipId, locationId: null }) - .where(eq(pilots.id, pilotId)) - .returning(); - return toApiPilot(updated!, ship.locationId, now); -} - -/** Unassigns a pilot from its ship; only allowed where the player has a bay (decision 0.5.2). */ -export async function unassignPilot( - tx: Queryable, - playerId: string, - pilotId: string, - now: RealTime, -): Promise { - const pilot = await requireOwnPilot(tx, playerId, pilotId); - if (pilot.assignedShipId === null) { - throw gameError("pilot-not-assigned-to-ship", "That pilot has no ship."); - } - if (pilot.status === "in_flight") { - throw gameError("pilot-not-idle", VALIDATION_MESSAGES["pilot-not-idle"]); - } - const ship = await requireOwnShip(tx, playerId, pilot.assignedShipId); - if (ship.locationId === null || !(await hasBayAt(tx, playerId, ship.locationId))) { - throw gameError("no-bay-at-location", "Pilots can only leave their ship at one of your bays."); - } - - const [updated] = await tx - .update(pilots) - .set({ assignedShipId: null, locationId: ship.locationId }) - .where(eq(pilots.id, pilotId)) - .returning(); - return toApiPilot(updated!, null, now); -} - -/** - * Rests an idle pilot now, at any duty level (CR-004): duty resets and they - * cannot fly until the rest ends. No bay is needed; crew swaps stay allowed. - */ -export async function restPilot( - tx: Queryable, - playerId: string, - pilotId: string, - now: RealTime, -): Promise { - await requireTutorialOver(tx, playerId, "resting a pilot"); - const pilot = await requireOwnPilot(tx, playerId, pilotId); - if (pilot.status === "in_flight") { - throw gameError("pilot-not-idle", VALIDATION_MESSAGES["pilot-not-idle"]); - } - if (isResting(pilot.restEndsAt, now)) { - throw gameError("pilot-resting", "That pilot is already resting."); - } - const [updated] = await tx - .update(pilots) - .set({ dutyHours: 0, restEndsAt: restEndsAt(now, content.config) }) - .where(eq(pilots.id, pilotId)) - .returning(); - const ship = updated!.assignedShipId - ? await requireOwnShip(tx, playerId, updated!.assignedShipId) - : null; - return toApiPilot(updated!, ship?.locationId ?? null, now); -} - -/** Spends one available skill point on a skill below its max level (Step 6.12). */ -export async function spendSkillPoint( - tx: Queryable, - playerId: string, - pilotId: string, - skillId: string, - now: RealTime, -): Promise { - const pilot = await requireOwnPilot(tx, playerId, pilotId); - const skill = content.skill(skillId); - if (!skill) throw gameError("unknown-skill", `No skill "${skillId}".`); - - const level = pilotLevel(pilot.xp, content.config); - if (skillPointsAvailable(level, pilot.skillPointsSpent, content.config) <= 0) { - throw gameError("no-skill-points", "That pilot has no skill points to spend."); - } - const current = pilot.skills[skillId] ?? 0; - if (current >= skill.maxLevel) { - throw gameError("skill-maxed", `${skill.name} is already at its maximum level.`); - } - - const [updated] = await tx - .update(pilots) - .set({ - skills: { ...pilot.skills, [skillId]: current + 1 }, - skillPointsSpent: pilot.skillPointsSpent + 1, - }) - .where(eq(pilots.id, pilotId)) - .returning(); - const [ship] = updated!.assignedShipId - ? await tx.select().from(ships).where(eq(ships.id, updated!.assignedShipId)) - : []; - return toApiPilot(updated!, ship?.locationId ?? null, now); -} diff --git a/apps/server/src/game/player-state.ts b/apps/server/src/game/player-state.ts deleted file mode 100644 index 48bd00b..0000000 --- a/apps/server/src/game/player-state.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { content } from "@deadhead/content"; -import type { PlayerState } from "@deadhead/schema"; -import type { RealTime } from "@deadhead/sim"; -import { and, eq, isNull, max } from "drizzle-orm"; -import type { Queryable } from "../db/client"; -import { - bays, - companies, - discoveredLocations, - dispatches, - pilots, - players, - ships, -} from "../db/schema"; -import { isDealAvailable } from "./deal"; -import { goalStates } from "./goals"; -import { toApiDispatch, toApiPilot, toApiShip } from "./mappers"; -import { loadUpgradeLevels } from "./upgrades"; - -/** Reads everything the client needs about one player. */ -export async function loadPlayerState( - db: Queryable, - playerId: string, - now: RealTime, -): Promise { - const [player] = await db.select().from(players).where(eq(players.id, playerId)); - const [company] = await db.select().from(companies).where(eq(companies.playerId, playerId)); - if (!player || !company) throw new Error(`Player ${playerId} not found`); - - const shipRows = await db.select().from(ships).where(eq(ships.playerId, playerId)); - const pilotRows = await db.select().from(pilots).where(eq(pilots.playerId, playerId)); - const bayRows = await db.select().from(bays).where(eq(bays.playerId, playerId)); - const discovered = await db - .select({ locationId: discoveredLocations.locationId }) - .from(discoveredLocations) - .where(eq(discoveredLocations.playerId, playerId)); - const active = await db - .select() - .from(dispatches) - .where(and(eq(dispatches.playerId, playerId), eq(dispatches.status, "in_flight"))); - const arrivals = await db - .select({ shipId: dispatches.shipId, at: max(dispatches.arrivalAt) }) - .from(dispatches) - .where(and(eq(dispatches.playerId, playerId), eq(dispatches.status, "resolved"))) - .groupBy(dispatches.shipId); - const unacknowledged = await db - .select() - .from(dispatches) - .where( - and( - eq(dispatches.playerId, playerId), - eq(dispatches.status, "resolved"), - isNull(dispatches.acknowledgedAt), - ), - ); - - const discoveredIds = new Set(discovered.map((d) => d.locationId)); - const shipById = new Map(shipRows.map((s) => [s.id, s])); - const activeByShip = new Map(active.filter((d) => d.shipId).map((d) => [d.shipId!, d.id])); - const upgradesByShip = await loadUpgradeLevels( - db, - shipRows.map((s) => s.id), - ); - const lastArrivalByShip = new Map(arrivals.map((a) => [a.shipId, a.at])); - const pilotByShip = new Map( - pilotRows.filter((p) => p.assignedShipId).map((p) => [p.assignedShipId!, p.id]), - ); - - return { - playerId, - company: { - id: company.id, - name: company.name, - credits: company.credits, - loanBalance: company.loanBalance, - }, - ships: shipRows.map((s) => - toApiShip( - s, - { - dispatchId: activeByShip.get(s.id) ?? null, - pilotId: pilotByShip.get(s.id) ?? null, - lastArrivalAt: lastArrivalByShip.get(s.id) ?? s.createdAt, - upgrades: upgradesByShip.get(s.id) ?? {}, - }, - now, - ), - ), - pilots: pilotRows.map((p) => - toApiPilot( - p, - p.assignedShipId ? (shipById.get(p.assignedShipId)?.locationId ?? null) : null, - now, - ), - ), - bays: bayRows.map((b) => ({ id: b.id, locationId: b.locationId })), - locations: content.locations.map((l) => ({ id: l.id, discovered: discoveredIds.has(l.id) })), - tutorial: - player.tutorialActive && player.loanerLocationId - ? { - loanerLocationId: player.loanerLocationId as "earth" | "moon", - runInFlight: active.some((d) => d.tutorial), - runsCompleted: player.tutorialRunsCompleted, - dealAvailable: isDealAvailable( - player, - active.some((d) => d.tutorial), - ), - } - : null, - activeDispatches: active.map(toApiDispatch), - summaries: unacknowledged.map((d) => ({ - dispatchId: d.id, - shipId: d.record.shipId, - pilotId: d.pilotId, - originId: d.record.originId, - destinationId: d.record.destinationId, - tutorial: d.tutorial, - deadhead: d.record.contract === null, - fuelCost: d.record.fuelCost, - goodId: d.record.contract?.goodId ?? null, - massTonnes: d.record.contract?.massTonnes ?? null, - shipper: d.record.contract?.shipper ?? null, - credits: d.resolution?.credits ?? 0, - loanRepayment: d.resolution?.loanRepayment ?? 0, - pilotPay: d.resolution?.pilotPay ?? 0, - pilotXp: d.resolution?.pilotXp ?? 0, - discoveredLocationId: d.resolution?.discoversDestination ? d.record.destinationId : null, - scriptedId: d.record.contract?.scriptedId ?? null, - restEndsAt: d.resolution?.pilotRestEndsAt ?? null, - resolvedAt: d.resolvedAt ?? d.arrivalAt, - })), - goals: await goalStates(db, playerId), - }; -} diff --git a/apps/server/src/game/resolve.ts b/apps/server/src/game/resolve.ts deleted file mode 100644 index 519d2d3..0000000 --- a/apps/server/src/game/resolve.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { content } from "@deadhead/content"; -import { resolveDispatch, type RealTime } from "@deadhead/sim"; -import { and, asc, eq, lte, sql } from "drizzle-orm"; -import type { Queryable } from "../db/client"; -import { companies, discoveredLocations, dispatches, pilots, players, ships } from "../db/schema"; - -/** - * Resolves every in-flight dispatch of a player that has arrived by `now`, in - * arrival order, applying credits (net of loan repayment), the repayment, XP, - * ship/pilot location and discovery. Run - * it inside `withPlayerLock`. Each dispatch moves from in_flight to resolved - * exactly once: the status update is conditional, and effects are applied only - * when it changed a row. Returns how many dispatches were resolved. - */ -export async function resolveArrivedDispatches( - tx: Queryable, - playerId: string, - now: RealTime, -): Promise { - const due = await tx - .select() - .from(dispatches) - .where( - and( - eq(dispatches.playerId, playerId), - eq(dispatches.status, "in_flight"), - lte(dispatches.arrivalAt, now), - ), - ) - .orderBy(asc(dispatches.arrivalAt)); - if (due.length === 0) return 0; - - const discovered = await tx - .select({ locationId: discoveredLocations.locationId }) - .from(discoveredLocations) - .where(eq(discoveredLocations.playerId, playerId)); - const discoveredIds = new Set(discovered.map((d) => d.locationId)); - const [company] = await tx.select().from(companies).where(eq(companies.playerId, playerId)); - // Carried forward so each arrival repays against the balance left by the previous one. - let loanBalance = company?.loanBalance ?? 0; - - let resolvedCount = 0; - for (const dispatch of due) { - const [pilot] = await tx.select().from(pilots).where(eq(pilots.id, dispatch.pilotId)); - if (!pilot) throw new Error(`Dispatch ${dispatch.id} has no pilot`); - - const result = resolveDispatch( - { - record: dispatch.record, - pilotIsAvatar: pilot.isAvatar, - discoveredIds, - loanBalance, - pilotDutyHours: pilot.dutyHours, - pilotSkills: pilot.skills, - now, - }, - content, - ); - if (result.status !== "resolved") continue; - const { resolution } = result; - - const claimed = await tx - .update(dispatches) - .set({ status: "resolved", resolution, resolvedAt: now }) - .where(and(eq(dispatches.id, dispatch.id), eq(dispatches.status, "in_flight"))) - .returning({ id: dispatches.id }); - if (claimed.length === 0) continue; - resolvedCount++; - loanBalance -= resolution.loanRepayment; - - await tx - .update(companies) - .set({ - credits: sql`${companies.credits} + ${resolution.credits}`, - loanBalance: sql`${companies.loanBalance} - ${resolution.loanRepayment}`, - }) - .where(eq(companies.playerId, playerId)); - - await tx - .update(pilots) - .set({ - xp: sql`${pilots.xp} + ${resolution.pilotXp}`, - status: "idle", - // Duty and mandatory rest from the arrival time (decisions 0.5.42–0.5.45). - dutyHours: resolution.pilotDutyHours, - ...(resolution.pilotRestEndsAt !== null ? { restEndsAt: resolution.pilotRestEndsAt } : {}), - // Only unassigned pilots store a location (decision 0.5.27): the tutorial avatar. - ...(pilot.assignedShipId === null ? { locationId: resolution.shipLocationId } : {}), - }) - .where(eq(pilots.id, pilot.id)); - - if (dispatch.shipId) { - await tx - .update(ships) - .set({ status: "idle", locationId: resolution.shipLocationId }) - .where(eq(ships.id, dispatch.shipId)); - } - if (dispatch.tutorial) { - await tx - .update(players) - .set({ - loanerLocationId: resolution.shipLocationId, - tutorialRunsCompleted: sql`${players.tutorialRunsCompleted} + 1`, - }) - .where(eq(players.id, playerId)); - } - if (resolution.discoversDestination) { - await tx - .insert(discoveredLocations) - .values({ playerId, locationId: dispatch.record.destinationId, discoveredAt: now }) - .onConflictDoNothing(); - discoveredIds.add(dispatch.record.destinationId); - } - } - return resolvedCount; -} diff --git a/apps/server/src/game/summaries.ts b/apps/server/src/game/summaries.ts deleted file mode 100644 index fb111c5..0000000 --- a/apps/server/src/game/summaries.ts +++ /dev/null @@ -1,56 +0,0 @@ -import type { RealTime } from "@deadhead/sim"; -import { and, eq, inArray, isNotNull, isNull, lte } from "drizzle-orm"; -import type { Queryable } from "../db/client"; -import { dispatches, ships } from "../db/schema"; - -/** - * Marks "while you were away" summaries as seen (Step 6.13). Only this - * player's resolved, unacknowledged dispatches are touched; other ids are - * ignored. Returns how many were acknowledged. - */ -export async function acknowledgeSummaries( - tx: Queryable, - playerId: string, - dispatchIds: string[], - now: RealTime, -): Promise { - const updated = await tx - .update(dispatches) - .set({ acknowledgedAt: now }) - .where( - and( - eq(dispatches.playerId, playerId), - inArray(dispatches.id, dispatchIds), - eq(dispatches.status, "resolved"), - isNull(dispatches.acknowledgedAt), - ), - ) - .returning({ id: dispatches.id }); - return updated.length; -} - -/** - * Clears finished upgrades the player has now seen ("out of the shop", CR-009). - * Ships still in the shop keep their job. - */ -export async function acknowledgeFinishedUpgrades( - tx: Queryable, - playerId: string, - shipIds: string[], - now: RealTime, -): Promise { - if (shipIds.length === 0) return 0; - const cleared = await tx - .update(ships) - .set({ shopUpgradeId: null, shopLevel: null }) - .where( - and( - eq(ships.playerId, playerId), - inArray(ships.id, shipIds), - isNotNull(ships.shopUpgradeId), - lte(ships.inShopUntil, now), - ), - ) - .returning({ id: ships.id }); - return cleared.length; -} diff --git a/apps/server/src/game/upgrades.ts b/apps/server/src/game/upgrades.ts deleted file mode 100644 index 6673da1..0000000 --- a/apps/server/src/game/upgrades.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { content } from "@deadhead/content"; -import type { Ship } from "@deadhead/schema"; -import { validateUpgrade, type RealTime, type UpgradeLevels } from "@deadhead/sim"; -import { and, eq, inArray, max, sql } from "drizzle-orm"; -import type { Queryable } from "../db/client"; -import { companies, dispatches, pilots, players, shipUpgrades, ships } from "../db/schema"; -import { gameError } from "../http/errors"; -import { shipState } from "./dispatch"; -import { toApiShip } from "./mappers"; -import { UPGRADE_MESSAGES } from "./messages"; -import { hasBayAt, requireOwnShip } from "./pilots"; - -/** Installed upgrade levels for each of the given ships (CR-009). */ -export async function loadUpgradeLevels( - tx: Queryable, - shipIds: string[], -): Promise>> { - const levels = new Map>(); - if (shipIds.length === 0) return levels; - const rows = await tx.select().from(shipUpgrades).where(inArray(shipUpgrades.shipId, shipIds)); - for (const row of rows) { - const ship = levels.get(row.shipId) ?? {}; - ship[row.upgradeId] = row.level; - levels.set(row.shipId, ship); - } - return levels; -} - -/** One ship's installed upgrade levels. */ -export async function shipUpgradeLevels(tx: Queryable, shipId: string): Promise { - return (await loadUpgradeLevels(tx, [shipId])).get(shipId) ?? {}; -} - -/** - * Starts the next level of an upgrade on a ship (CR-009, decisions 0.5.64–0.5.66), - * inside the caller's player-locked transaction: validate, charge, raise the - * level now, and keep the ship in the shop until the work is done. - */ -export async function startUpgrade( - tx: Queryable, - playerId: string, - input: { shipId: string; upgradeId: string }, - now: RealTime, -): Promise { - const upgrade = content.upgrade(input.upgradeId); - if (!upgrade) throw gameError("not-found", `No upgrade "${input.upgradeId}".`); - const row = await requireOwnShip(tx, playerId, input.shipId); - const [player] = await tx.select().from(players).where(eq(players.id, playerId)); - const [company] = await tx.select().from(companies).where(eq(companies.playerId, playerId)); - if (!player || !company) throw new Error(`Player ${playerId} not found`); - - const levels = await shipUpgradeLevels(tx, row.id); - const check = validateUpgrade( - { - ship: shipState(row, levels), - upgrade, - atHq: row.locationId !== null && (await hasBayAt(tx, playerId, row.locationId)), - tutorialActive: player.tutorialActive, - credits: company.credits, - now, - }, - content, - ); - if (!check.ok) throw gameError(check.reason, UPGRADE_MESSAGES[check.reason]); - - await tx - .update(companies) - .set({ credits: sql`${companies.credits} - ${check.cost}` }) - .where(eq(companies.playerId, playerId)); - await tx - .insert(shipUpgrades) - .values({ shipId: row.id, upgradeId: upgrade.id, level: check.level }) - .onConflictDoUpdate({ - target: [shipUpgrades.shipId, shipUpgrades.upgradeId], - set: { level: check.level }, - }); - const [updated] = await tx - .update(ships) - .set({ inShopUntil: check.shopUntil, shopUpgradeId: upgrade.id, shopLevel: check.level }) - .where(eq(ships.id, row.id)) - .returning(); - - const [pilot] = await tx - .select({ id: pilots.id }) - .from(pilots) - .where(eq(pilots.assignedShipId, row.id)); - const [arrival] = await tx - .select({ at: max(dispatches.arrivalAt) }) - .from(dispatches) - .where(and(eq(dispatches.shipId, row.id), eq(dispatches.status, "resolved"))); - return toApiShip( - updated!, - { - dispatchId: null, - pilotId: pilot?.id ?? null, - lastArrivalAt: arrival?.at ?? row.createdAt, - upgrades: { ...levels, [upgrade.id]: check.level }, - }, - now, - ); -} diff --git a/apps/server/src/http/errors.ts b/apps/server/src/http/errors.ts deleted file mode 100644 index cbeaa08..0000000 --- a/apps/server/src/http/errors.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { ErrorCode, ErrorResponse } from "@deadhead/schema"; -import type { FastifyInstance } from "fastify"; - -/** An expected failure, sent as `{ error: { code, message } }` with its HTTP status. */ -export class ApiError extends Error { - constructor( - readonly status: number, - readonly code: ErrorCode, - message: string, - ) { - super(message); - } -} - -const NOT_FOUND_CODES: ReadonlySet = new Set([ - "not-found", - "unknown-contract", - "unknown-skill", - "unknown-goal", -]); - -/** A game-rule failure: 404 for unknown things, 409 for everything else. */ -export function gameError(code: ErrorCode, message: string): ApiError { - return new ApiError(NOT_FOUND_CODES.has(code) ? 404 : 409, code, message); -} - -export function errorBody(code: ErrorCode, message: string): ErrorResponse { - return { error: { code, message } }; -} - -/** Turns thrown `ApiError`s into the documented error format; anything else is a 500. */ -export function registerErrorHandler(app: FastifyInstance): void { - app.setErrorHandler((error, request, reply) => { - if (error instanceof ApiError) { - return reply.status(error.status).send(errorBody(error.code, error.message)); - } - // Fastify's own 4xx errors, e.g. a body that is not valid JSON. - const status = (error as { statusCode?: number }).statusCode; - if (status !== undefined && status >= 400 && status < 500) { - return reply.status(400).send(errorBody("validation-error", (error as Error).message)); - } - request.log.error(error); - return reply.status(500).send(errorBody("internal-error", "Something went wrong.")); - }); - app.setNotFoundHandler((_request, reply) => - reply.status(404).send(errorBody("not-found", "No such endpoint.")), - ); -} diff --git a/apps/server/src/http/route.ts b/apps/server/src/http/route.ts deleted file mode 100644 index 097d9b1..0000000 --- a/apps/server/src/http/route.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type { RouteDef } from "@deadhead/schema"; -import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify"; -import type { z } from "zod"; -import { ApiError } from "./errors"; - -export interface RouteContext { - /** The signed-in player; only present on routes that require a session. */ - playerId: Auth extends true ? string : null; - request: FastifyRequest; - reply: FastifyReply; -} - -export interface RouteOptions { - /** Resolves the session to a player id, or throws `401 unauthorized`. */ - requirePlayer: (request: FastifyRequest) => Promise; - /** Check responses against their schema (everywhere except production). */ - validateResponses: boolean; -} - -function describeIssues(error: z.ZodError): string { - return error.issues - .map((issue) => (issue.path.length ? `${issue.path.join(".")}: ` : "") + issue.message) - .join("; "); -} - -/** - * Registers one catalog route. The request (path parameters, query and body - * merged) is validated before anything else, then the session is checked if the - * route needs one, and the response is checked against its schema. - */ -export function registerRoute( - app: FastifyInstance, - route: R, - options: RouteOptions, - handler: ( - input: z.output, - context: RouteContext, - ) => Promise>, -): void { - app.route({ - method: route.method, - url: route.path, - handler: async (request, reply) => { - const raw = { - ...(request.query as object), - ...(typeof request.body === "object" && request.body !== null ? request.body : {}), - ...(request.params as object), - }; - const parsed = route.request.safeParse(raw); - if (!parsed.success) { - throw new ApiError(400, "validation-error", describeIssues(parsed.error)); - } - - const playerId = route.auth ? await options.requirePlayer(request) : null; - const result = await handler(parsed.data as z.output, { - playerId: playerId as RouteContext["playerId"], - request, - reply, - }); - - if (options.validateResponses) { - const checked = route.response.safeParse(result); - if (!checked.success) { - throw new Error( - `Response for ${route.method} ${route.path} violates its schema: ${describeIssues(checked.error)}`, - ); - } - } - return result; - }, - }); -} diff --git a/apps/server/src/http/session.ts b/apps/server/src/http/session.ts deleted file mode 100644 index 3deb7bc..0000000 --- a/apps/server/src/http/session.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { eq } from "drizzle-orm"; -import { fromNodeHeaders } from "better-auth/node"; -import type { FastifyRequest } from "fastify"; -import type { Auth } from "../auth"; -import type { Db } from "../db/client"; -import { playerLogins, players } from "../db/schema"; -import { ApiError } from "./errors"; - -/** The signed-in player's id, or a 401 `unauthorized` error. */ -export async function requirePlayerId( - request: FastifyRequest, - auth: Auth, - db: Db, -): Promise { - const session = await auth.api.getSession({ headers: fromNodeHeaders(request.headers) }); - if (!session) throw new ApiError(401, "unauthorized", "Sign in to continue."); - - const [player] = await db - .select({ id: players.id }) - .from(players) - .where(eq(players.userId, session.user.id)); - if (player) return player.id; - // A device signed in with a backup code (decision 0.5.72). - const [login] = await db - .select({ playerId: playerLogins.playerId }) - .from(playerLogins) - .where(eq(playerLogins.userId, session.user.id)); - if (!login) throw new ApiError(401, "unauthorized", "No player for this account."); - return login.playerId; -} diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts deleted file mode 100644 index b5971c7..0000000 --- a/apps/server/src/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { buildApp, type AppDeps } from "./app"; -export { systemClock, type Clock } from "./clock"; -export { loadConfig, type ServerConfig } from "./config"; diff --git a/apps/server/src/jobs/cleanup.ts b/apps/server/src/jobs/cleanup.ts deleted file mode 100644 index a85658e..0000000 --- a/apps/server/src/jobs/cleanup.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { content } from "@deadhead/content"; -import { boardPeriodIndex, type RealTime } from "@deadhead/sim"; -import { lt } from "drizzle-orm"; -import type { Db } from "../db/client"; -import { acceptedContracts, contractBoards } from "../db/schema"; - -/** - * How many past board periods to keep. Older boards are never shown, and their - * contracts are expired; dispatches keep their own contract snapshot. - */ -export const BOARD_RETENTION_PERIODS = 24; - -/** - * Deletes stored contract boards and accepted-contract records from periods - * older than the retention window (Step 7.3). Returns how many rows went. - */ -export async function cleanupExpiredContracts( - db: Db, - now: RealTime, - retentionPeriods = BOARD_RETENTION_PERIODS, -): Promise<{ boards: number; acceptedContracts: number }> { - const cutoff = boardPeriodIndex(now, content.config) - retentionPeriods; - return db.transaction(async (tx) => { - const boards = await tx - .delete(contractBoards) - .where(lt(contractBoards.periodIndex, cutoff)) - .returning({ id: contractBoards.id }); - const accepted = await tx - .delete(acceptedContracts) - .where(lt(acceptedContracts.periodIndex, cutoff)) - .returning({ id: acceptedContracts.contractId }); - return { boards: boards.length, acceptedContracts: accepted.length }; - }); -} diff --git a/apps/server/src/jobs/definitions.ts b/apps/server/src/jobs/definitions.ts deleted file mode 100644 index f2e6afb..0000000 --- a/apps/server/src/jobs/definitions.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { Clock } from "../clock"; -import type { Db } from "../db/client"; -import { withPlayerLock } from "../game/lock"; -import { resolveArrivedDispatches } from "../game/resolve"; -import { cleanupExpiredContracts } from "./cleanup"; -import type { JobDefinition } from "./queue"; - -export const RESOLVE_ARRIVALS_JOB = "resolve-arrivals"; -export const CLEANUP_CONTRACTS_JOB = "cleanup-expired-contracts"; - -export interface JobContext { - db: Db; - clock: Clock; -} - -/** - * Resolves a player's arrived dispatches, exactly like lazy resolution on read - * (Step 7.2). Scheduled for each dispatch's arrival; does nothing if a request - * already resolved it. - */ -export async function resolveArrivalsJob(ctx: JobContext, data: Record) { - const playerId = data.playerId; - if (typeof playerId !== "string") throw new Error("resolve-arrivals job needs a playerId"); - await withPlayerLock(ctx.db, playerId, (tx) => - resolveArrivedDispatches(tx, playerId, ctx.clock()), - ); -} - -/** Every background job the app runs. */ -export function appJobs(ctx: JobContext): JobDefinition[] { - return [ - { name: RESOLVE_ARRIVALS_JOB, handler: (data) => resolveArrivalsJob(ctx, data) }, - { - name: CLEANUP_CONTRACTS_JOB, - // Hourly, on the hour (UTC). - cron: "0 * * * *", - handler: async () => { - await cleanupExpiredContracts(ctx.db, ctx.clock()); - }, - }, - ]; -} diff --git a/apps/server/src/jobs/queue.ts b/apps/server/src/jobs/queue.ts deleted file mode 100644 index 7d651f0..0000000 --- a/apps/server/src/jobs/queue.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { PgBoss } from "pg-boss"; - -export type JobHandler = (data: Record) => Promise; - -export interface JobDefinition { - name: string; - handler: JobHandler; - /** Cron expression for a recurring job (UTC). */ - cron?: string; -} - -/** Background work. The real one is pg-boss; tests can pass a recording fake. */ -export interface JobQueue { - start(): Promise; - stop(): Promise; - /** Enqueues a job, optionally not before `startAfter` (Unix ms). */ - send(name: string, data: Record, startAfter?: number): Promise; -} - -/** - * pg-boss in the same Postgres database (its own `pgboss` schema). Queues are - * always created so the app can enqueue; workers and cron schedules run only - * when `workers` is true (Step 7.1). - */ -export function createPgBossQueue( - databaseUrl: string, - options: { workers: boolean; jobs: JobDefinition[]; onError?: (error: Error) => void }, -): JobQueue & { boss: PgBoss } { - const boss = new PgBoss({ connectionString: databaseUrl, schedule: options.workers }); - if (options.onError) boss.on("error", options.onError); - - return { - boss, - async start() { - await boss.start(); - for (const job of options.jobs) { - await boss.createQueue(job.name); - if (!options.workers) continue; - await boss.work>(job.name, async (batch) => { - for (const item of batch) await job.handler(item.data); - }); - if (job.cron) await boss.schedule(job.name, job.cron); - } - }, - async stop() { - await boss.stop({ graceful: true }); - }, - send(name, data, startAfter) { - return boss.send( - name, - data, - startAfter === undefined ? {} : { startAfter: new Date(startAfter) }, - ); - }, - }; -} - -/** A queue that only records what was sent; for tests that call handlers directly. */ -export function createRecordingQueue(): JobQueue & { - sent: { name: string; data: Record; startAfter?: number }[]; -} { - const sent: { name: string; data: Record; startAfter?: number }[] = []; - return { - sent, - async start() {}, - async stop() {}, - async send(name, data, startAfter) { - sent.push({ name, data, ...(startAfter === undefined ? {} : { startAfter }) }); - return `recorded-${sent.length}`; - }, - }; -} diff --git a/apps/server/src/main.ts b/apps/server/src/main.ts deleted file mode 100644 index 6e87615..0000000 --- a/apps/server/src/main.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { existsSync } from "node:fs"; -import { fileURLToPath } from "node:url"; -import { buildApp } from "./app"; -import { loadConfig } from "./config"; - -// Local development reads the repo-root .env; deployments set real env vars. -const envFile = fileURLToPath(new URL("../../../.env", import.meta.url)); -if (existsSync(envFile)) process.loadEnvFile(envFile); - -const config = loadConfig(process.env); -const app = buildApp(config); - -try { - await app.listen({ host: "0.0.0.0", port: config.port }); -} catch (error) { - app.log.error(error); - process.exit(1); -} diff --git a/apps/server/src/routes/boards.ts b/apps/server/src/routes/boards.ts deleted file mode 100644 index a423317..0000000 --- a/apps/server/src/routes/boards.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { routes } from "@deadhead/schema"; -import type { FastifyInstance } from "fastify"; -import { locationBoard, tutorialBoard } from "../game/boards"; -import { registerRoute } from "../http/route"; -import type { AppContext } from "./context"; -import { withPlayer } from "./with-player"; - -/** Contract boards. */ -export function registerBoardRoutes(app: FastifyInstance, ctx: AppContext): void { - registerRoute(app, routes.getTutorialBoard, ctx.options, async (_input, { playerId }) => - withPlayer(ctx, playerId, (tx, now) => tutorialBoard(tx, playerId, now)), - ); - - registerRoute(app, routes.getContractBoard, ctx.options, async (input, { playerId }) => - withPlayer(ctx, playerId, (tx, now) => locationBoard(tx, playerId, input.locationId, now)), - ); -} diff --git a/apps/server/src/routes/context.ts b/apps/server/src/routes/context.ts deleted file mode 100644 index 5b5ee86..0000000 --- a/apps/server/src/routes/context.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { Auth } from "../auth"; -import type { Clock } from "../clock"; -import type { Db } from "../db/client"; -import type { RouteOptions } from "../http/route"; -import type { JobQueue } from "../jobs/queue"; - -/** What every route module needs. */ -export interface AppContext { - db: Db; - auth: Auth; - clock: Clock; - /** Converts an app-clock time to the real clock pg-boss uses (they differ only in tests). */ - toBaseTime: (time: number) => number; - jobs: JobQueue; - options: RouteOptions; -} diff --git a/apps/server/src/routes/dev.ts b/apps/server/src/routes/dev.ts deleted file mode 100644 index 2419eaa..0000000 --- a/apps/server/src/routes/dev.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { content, upgradeMaxLevel } from "@deadhead/content"; -import { routes } from "@deadhead/schema"; -import { and, eq, sql } from "drizzle-orm"; -import type { FastifyInstance } from "fastify"; -import type { OffsetClock } from "../clock"; -import { companies, discoveredLocations, pilots, ships, shipUpgrades } from "../db/schema"; -import { gameError } from "../http/errors"; -import { registerRoute } from "../http/route"; -import { requireOwnPilot, requireOwnShip } from "../game/pilots"; -import { loadPlayerState } from "../game/player-state"; -import type { AppContext } from "./context"; -import { withPlayer } from "./with-player"; - -/** - * Developer tools (CR-011): fast-forward the clock, add credits, and adjust - * pilots, ships and discoveries for testing. Only registered outside - * production, like the clock-offset route (decision 0.5.18). - */ -export function registerDevRoutes(app: FastifyInstance, ctx: AppContext, clock: OffsetClock): void { - registerRoute(app, routes.devAdvanceClock, ctx.options, async (input) => { - clock.setOffset(clock.offsetMs() + input.advanceMs); - return { offsetMs: clock.offsetMs(), realTime: clock.now() }; - }); - - registerRoute(app, routes.devAddCredits, ctx.options, async (input, { playerId }) => - withPlayer(ctx, playerId, async (tx) => { - const [company] = await tx - .update(companies) - .set({ credits: sql`${companies.credits} + ${input.amount}` }) - .where(eq(companies.playerId, playerId)) - .returning(); - return { - company: { - id: company!.id, - name: company!.name, - credits: company!.credits, - loanBalance: company!.loanBalance, - }, - }; - }), - ); - - // Level up: XP to the next level's threshold (skill points follow from XP). - // Clear rest: duty back to zero and any rest ended now. - registerRoute(app, routes.devPilot, ctx.options, async (input, { playerId }) => - withPlayer(ctx, playerId, async (tx, now) => { - const pilot = await requireOwnPilot(tx, playerId, input.pilotId); - if (input.action === "level-up") { - const thresholds = content.config.pilotLevelXpThresholds; - const next = thresholds.find((xp) => xp > pilot.xp); - if (next !== undefined) { - await tx.update(pilots).set({ xp: next }).where(eq(pilots.id, pilot.id)); - } - } else { - await tx - .update(pilots) - .set({ dutyHours: 0, restEndsAt: null }) - .where(eq(pilots.id, pilot.id)); - } - return loadPlayerState(tx, playerId, now); - }), - ); - - registerRoute(app, routes.devShip, ctx.options, async (input, { playerId }) => - withPlayer(ctx, playerId, async (tx, now) => { - const ship = await requireOwnShip(tx, playerId, input.shipId); - switch (input.action) { - case "max-upgrades": { - // Every upgrade at its cap for this model, installed at once and free. - const rows = content.upgrades - .map((u) => ({ - shipId: ship.id, - upgradeId: u.id, - level: upgradeMaxLevel(u, ship.modelId), - })) - .filter((r) => r.level > 0); - await tx.delete(shipUpgrades).where(eq(shipUpgrades.shipId, ship.id)); - if (rows.length > 0) await tx.insert(shipUpgrades).values(rows); - break; - } - case "reset-upgrades": - await tx.delete(shipUpgrades).where(eq(shipUpgrades.shipId, ship.id)); - break; - case "finish-shop": - // Done now: the "out of the shop" note still shows, as for a finished job. - if (ship.inShopUntil !== null && ship.inShopUntil > now) { - await tx.update(ships).set({ inShopUntil: now }).where(eq(ships.id, ship.id)); - } - break; - case "move": { - if (ship.status !== "idle") { - throw gameError("ship-not-idle", "Only an idle ship can be moved."); - } - const [known] = await tx - .select() - .from(discoveredLocations) - .where( - and( - eq(discoveredLocations.playerId, playerId), - eq(discoveredLocations.locationId, input.locationId), - ), - ); - if (!known) { - throw gameError( - "location-undiscovered", - "Ships can only be moved to discovered places.", - ); - } - await tx.update(ships).set({ locationId: input.locationId }).where(eq(ships.id, ship.id)); - break; - } - } - return loadPlayerState(tx, playerId, now); - }), - ); - - // Every location discovered, for testing range and reach. - registerRoute(app, routes.devDiscoverAll, ctx.options, async (_input, { playerId }) => - withPlayer(ctx, playerId, async (tx, now) => { - await tx - .insert(discoveredLocations) - .values(content.locations.map((l) => ({ playerId, locationId: l.id, discoveredAt: now }))) - .onConflictDoNothing(); - return loadPlayerState(tx, playerId, now); - }), - ); -} diff --git a/apps/server/src/routes/dispatches.ts b/apps/server/src/routes/dispatches.ts deleted file mode 100644 index 84a3279..0000000 --- a/apps/server/src/routes/dispatches.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { routes } from "@deadhead/schema"; -import type { FastifyInstance } from "fastify"; -import { dispatchContract, dispatchReposition, dispatchTutorial } from "../game/dispatch"; -import { registerRoute } from "../http/route"; -import { RESOLVE_ARRIVALS_JOB } from "../jobs/definitions"; -import type { AppContext } from "./context"; -import { withPlayer } from "./with-player"; - -/** Contract, tutorial and reposition dispatches. */ -export function registerDispatchRoutes(app: FastifyInstance, ctx: AppContext): void { - registerRoute(app, routes.createDispatch, ctx.options, async (input, { playerId, request }) => { - const result = await withPlayer(ctx, playerId, async (tx, now) => { - switch (input.kind) { - case "contract": - return { dispatch: await dispatchContract(tx, playerId, input, now) }; - case "tutorial": - return { dispatch: await dispatchTutorial(tx, playerId, input.contractId, now) }; - case "reposition": - return { dispatch: await dispatchReposition(tx, playerId, input, now) }; - } - }); - - // Resolve at arrival even if the player never comes back (Step 7.2). Sent after - // the dispatch commits; if sending fails, lazy resolution on read still covers it. - try { - await ctx.jobs.send( - RESOLVE_ARRIVALS_JOB, - { playerId, dispatchId: result.dispatch.id }, - ctx.toBaseTime(result.dispatch.arrivalAt), - ); - } catch (error) { - request.log.error({ err: error }, "Could not schedule the arrival job"); - } - return result; - }); -} diff --git a/apps/server/src/routes/fleet.ts b/apps/server/src/routes/fleet.ts deleted file mode 100644 index 592c7b4..0000000 --- a/apps/server/src/routes/fleet.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { routes } from "@deadhead/schema"; -import type { FastifyInstance } from "fastify"; -import { buyShip, leaseBay } from "../game/fleet"; -import { assignPilot, hirePilot, restPilot, spendSkillPoint, unassignPilot } from "../game/pilots"; -import { startUpgrade } from "../game/upgrades"; -import { registerRoute } from "../http/route"; -import type { AppContext } from "./context"; -import { withPlayer } from "./with-player"; - -/** Ships, bays and pilots. */ -export function registerFleetRoutes(app: FastifyInstance, ctx: AppContext): void { - registerRoute(app, routes.buyShip, ctx.options, async (input, { playerId }) => - withPlayer(ctx, playerId, async (tx, now) => ({ - ship: await buyShip(tx, playerId, input, now), - })), - ); - - registerRoute(app, routes.leaseBay, ctx.options, async (input, { playerId }) => - withPlayer(ctx, playerId, async (tx, now) => ({ - bay: await leaseBay(tx, playerId, input.locationId, now), - })), - ); - - registerRoute(app, routes.hirePilot, ctx.options, async (input, { playerId }) => - withPlayer(ctx, playerId, async (tx, now) => ({ - pilot: await hirePilot(tx, playerId, input.locationId, now), - })), - ); - - registerRoute(app, routes.assignPilot, ctx.options, async (input, { playerId }) => - withPlayer(ctx, playerId, async (tx, now) => ({ - pilot: await assignPilot(tx, playerId, input.pilotId, input.shipId, now), - })), - ); - - registerRoute(app, routes.unassignPilot, ctx.options, async (input, { playerId }) => - withPlayer(ctx, playerId, async (tx, now) => ({ - pilot: await unassignPilot(tx, playerId, input.pilotId, now), - })), - ); - - registerRoute(app, routes.startUpgrade, ctx.options, async (input, { playerId }) => - withPlayer(ctx, playerId, async (tx, now) => ({ - ship: await startUpgrade(tx, playerId, input, now), - })), - ); - - registerRoute(app, routes.restPilot, ctx.options, async (input, { playerId }) => - withPlayer(ctx, playerId, async (tx, now) => ({ - pilot: await restPilot(tx, playerId, input.pilotId, now), - })), - ); - - registerRoute(app, routes.spendSkillPoint, ctx.options, async (input, { playerId }) => - withPlayer(ctx, playerId, async (tx, now) => ({ - pilot: await spendSkillPoint(tx, playerId, input.pilotId, input.skillId, now), - })), - ); -} diff --git a/apps/server/src/routes/player.ts b/apps/server/src/routes/player.ts deleted file mode 100644 index 5f0b174..0000000 --- a/apps/server/src/routes/player.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { randomInt } from "node:crypto"; -import { content } from "@deadhead/content"; -import { routes } from "@deadhead/schema"; -import { realToGame } from "@deadhead/sim"; -import { fromNodeHeaders } from "better-auth/node"; -import type { FastifyInstance } from "fastify"; -import { acceptDeal } from "../game/deal"; -import { claimGoal } from "../game/goals"; -import { - backupCodeStatus, - createBackupCode, - playerForBackupCode, - RestoreLimiter, -} from "../game/backup"; -import { renameCompany } from "../game/company"; -import { repayLoan } from "../game/loan"; -import { newCompany } from "../game/new-company"; -import { createPlayer } from "../game/new-player"; -import { loadPlayerState } from "../game/player-state"; -import { acknowledgeFinishedUpgrades, acknowledgeSummaries } from "../game/summaries"; -import { playerLogins } from "../db/schema"; -import { ApiError } from "../http/errors"; -import { registerRoute } from "../http/route"; -import type { AppContext } from "./context"; -import { withPlayer } from "./with-player"; - -/** Server time, guest sign-up and player state. */ -export function registerPlayerRoutes(app: FastifyInstance, ctx: AppContext): void { - const { db, auth, clock, options } = ctx; - - registerRoute(app, routes.getServerTime, options, async () => { - const realTime = clock(); - return { realTime, gameTime: realToGame(realTime, content.config) }; - }); - - registerRoute(app, routes.createGuest, options, async (_input, { request, reply }) => { - const signIn = await auth.api.signInAnonymous({ - headers: fromNodeHeaders(request.headers), - returnHeaders: true, - }); - if (!signIn.response) throw new ApiError(500, "internal-error", "Could not create a guest."); - - const { playerId } = await createPlayer(db, { - userId: signIn.response.user.id, - // A 32-bit unsigned seed (decision 0.5.10). - worldSeed: randomInt(0, 2 ** 32), - now: clock(), - }); - // Pass Better Auth's session cookie through to the client. - reply.header("set-cookie", signIn.headers.getSetCookie()); - return { playerId, state: await loadPlayerState(db, playerId, clock()) }; - }); - - // Lazy resolution on read (Step 6.1): apply every arrival before returning state. - registerRoute(app, routes.getPlayerState, options, async (_input, { playerId }) => - withPlayer(ctx, playerId, (tx, now) => loadPlayerState(tx, playerId, now)), - ); - - registerRoute(app, routes.acknowledgeSummaries, options, async (input, { playerId }) => - withPlayer(ctx, playerId, async (tx, now) => ({ - acknowledged: - (await acknowledgeSummaries(tx, playerId, input.dispatchIds, now)) + - (await acknowledgeFinishedUpgrades(tx, playerId, input.shipIds ?? [], now)), - })), - ); - - registerRoute(app, routes.acceptDeal, options, async (_input, { playerId }) => - withPlayer(ctx, playerId, async (tx, now) => { - await acceptDeal(tx, playerId, now); - return loadPlayerState(tx, playerId, now); - }), - ); - - registerRoute(app, routes.getBackupCode, options, async (_input, { playerId }) => - withPlayer(ctx, playerId, (tx) => backupCodeStatus(tx, playerId)), - ); - - registerRoute(app, routes.createBackupCode, options, async (_input, { playerId }) => - withPlayer(ctx, playerId, (tx, now) => createBackupCode(tx, playerId, now)), - ); - - // Restoring signs this device in as the code's player (decisions 0.5.72, 0.5.73). - const limiter = new RestoreLimiter(); - registerRoute(app, routes.restoreCompany, options, async (input, { request, reply }) => { - const now = clock(); - const device = request.headers["x-device-id"]; - const keys = [`ip:${request.ip}`, ...(typeof device === "string" ? [`device:${device}`] : [])]; - if (limiter.blocked(keys, now)) { - throw new ApiError( - 429, - "rate-limited", - "Too many attempts. Wait a few minutes and try again.", - ); - } - const playerId = await playerForBackupCode(db, input.code); - if (!playerId) { - limiter.fail(keys, now); - throw new ApiError( - 409, - "restore-failed", - "That code didn't open a company. Check it and try again.", - ); - } - // A fresh guest sign-in for this device (any old session is left behind), - // linked to the code's player. - const headers = fromNodeHeaders(request.headers); - headers.delete("cookie"); - const signIn = await auth.api.signInAnonymous({ headers, returnHeaders: true }); - if (!signIn.response) - throw new ApiError(500, "internal-error", "Could not sign this device in."); - await db - .insert(playerLogins) - .values({ userId: signIn.response.user.id, playerId, createdAt: now }); - reply.header("set-cookie", signIn.headers.getSetCookie()); - return { playerId, state: await loadPlayerState(db, playerId, now) }; - }); - - registerRoute(app, routes.newCompany, options, async (_input, { playerId }) => - withPlayer(ctx, playerId, async (tx, now) => { - await newCompany(tx, playerId, now); - return loadPlayerState(tx, playerId, now); - }), - ); - - registerRoute(app, routes.renameCompany, options, async (input, { playerId }) => - withPlayer(ctx, playerId, async (tx) => ({ - company: await renameCompany(tx, playerId, input.name), - })), - ); - - registerRoute(app, routes.repayLoan, options, async (input, { playerId }) => - withPlayer(ctx, playerId, async (tx) => ({ - company: await repayLoan(tx, playerId, input.amount), - })), - ); - - registerRoute(app, routes.claimGoal, options, async (input, { playerId }) => - withPlayer(ctx, playerId, async (tx, now) => ({ - company: await claimGoal(tx, playerId, input.goalId, now), - })), - ); -} diff --git a/apps/server/src/routes/test-clock.ts b/apps/server/src/routes/test-clock.ts deleted file mode 100644 index db85727..0000000 --- a/apps/server/src/routes/test-clock.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { FastifyInstance } from "fastify"; -import { z } from "zod"; -import type { OffsetClock } from "../clock"; -import { ApiError } from "../http/errors"; - -const bodySchema = z.object({ offsetMs: z.number().int() }).strict(); - -/** - * Test-only: shifts the server clock so E2E tests can fast-forward to arrivals - * (decision 0.5.18). Never registered in production; not part of the catalog. - */ -export function registerTestClockRoute(app: FastifyInstance, clock: OffsetClock): void { - app.post("/api/test/clock-offset", async (request) => { - const parsed = bodySchema.safeParse(request.body ?? {}); - if (!parsed.success) - throw new ApiError(400, "validation-error", "Expected { offsetMs: integer }."); - clock.setOffset(parsed.data.offsetMs); - return { offsetMs: clock.offsetMs(), realTime: clock.now() }; - }); -} diff --git a/apps/server/src/routes/with-player.ts b/apps/server/src/routes/with-player.ts deleted file mode 100644 index e96bbdd..0000000 --- a/apps/server/src/routes/with-player.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { Queryable } from "../db/client"; -import { withPlayerLock } from "../game/lock"; -import { resolveArrivedDispatches } from "../game/resolve"; -import type { AppContext } from "./context"; - -/** - * Runs a game action for a player: takes the player lock, applies any arrivals - * that are due (so the action sees current state), then runs `fn`. - */ -export function withPlayer( - ctx: AppContext, - playerId: string, - fn: (tx: Queryable, now: number) => Promise, -): Promise { - const now = ctx.clock(); - return withPlayerLock(ctx.db, playerId, async (tx) => { - await resolveArrivedDispatches(tx, playerId, now); - return fn(tx, now); - }); -} diff --git a/apps/server/test/arrival-job.test.ts b/apps/server/test/arrival-job.test.ts deleted file mode 100644 index 1a94221..0000000 --- a/apps/server/test/arrival-job.test.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { eq } from "drizzle-orm"; -import { describe, expect, it } from "vitest"; -import { buildApp } from "../src/app"; -import { companies, dispatches, ships } from "../src/db/schema"; -import { RESOLVE_ARRIVALS_JOB, resolveArrivalsJob } from "../src/jobs/definitions"; -import { createRecordingQueue } from "../src/jobs/queue"; -import { makeOwner, setCredits, useTestDatabase } from "./db"; -import { call, getState, putBoard, signUp, testContract, useTestApp } from "./game"; -import { testConfig } from "./helpers"; - -const database = useTestDatabase(); -const testApp = useTestApp(database); - -/** An owner who has just dispatched a 2,000-credit Earth→Moon contract. */ -async function dispatched() { - const { app } = testApp(); - const { db } = database(); - const guest = await signUp(app); - const { shipId } = await makeOwner(db, guest.playerId, guest.avatarId); - await setCredits(db, guest.playerId, 10_000); - const contract = testContract(); - await putBoard(db, guest.playerId, [contract]); - const response = await call(app, guest, "POST", "/api/dispatches", { - kind: "contract", - contractId: contract.id, - shipId, - pilotId: guest.avatarId, - }); - const dispatch = response.json().dispatch as { id: string; arrivalAt: number; fuelCost: number }; - return { guest, shipId, dispatch }; -} - -const credits = async (playerId: string) => - (await database().db.select().from(companies).where(eq(companies.playerId, playerId)))[0]! - .credits; - -describe("arrival resolution job", () => { - it("is scheduled for the dispatch's arrival time", async () => { - const { jobs } = testApp(); - const { guest, dispatch } = await dispatched(); - expect(jobs.sent).toEqual([ - { - name: RESOLVE_ARRIVALS_JOB, - data: { playerId: guest.playerId, dispatchId: dispatch.id }, - startAfter: dispatch.arrivalAt, - }, - ]); - }); - - it("resolves the dispatch at arrival without any player state fetch", async () => { - const { clock, jobs } = testApp(); - const { db } = database(); - const { guest, shipId, dispatch } = await dispatched(); - const jobCtx = { db, clock: clock.read }; - - // Too early: nothing happens. - clock.now = dispatch.arrivalAt - 1; - await resolveArrivalsJob(jobCtx, jobs.sent[0]!.data); - expect((await db.select().from(dispatches))[0]!.status).toBe("in_flight"); - - clock.now = dispatch.arrivalAt; - await resolveArrivalsJob(jobCtx, jobs.sent[0]!.data); - const [row] = await db.select().from(dispatches).where(eq(dispatches.id, dispatch.id)); - expect(row!.status).toBe("resolved"); - expect(await credits(guest.playerId)).toBe(10_000 - dispatch.fuelCost + 2000); - const [ship] = await db.select().from(ships).where(eq(ships.id, shipId)); - expect(ship).toMatchObject({ status: "idle", locationId: "moon" }); - }); - - it("does nothing if lazy resolution already happened", async () => { - const { app, clock, jobs } = testApp(); - const { db } = database(); - const { guest, dispatch } = await dispatched(); - clock.now = dispatch.arrivalAt + 1000; - - const state = await getState(app, guest); - await resolveArrivalsJob({ db, clock: clock.read }, jobs.sent[0]!.data); - expect(await credits(guest.playerId)).toBe(state.company.credits); - expect((await getState(app, guest)).summaries).toHaveLength(1); - }); -}); - -describe("test-only clock offset endpoint", () => { - it("shifts the server clock outside production", async () => { - const { app, clock } = testApp(); - const response = await app.inject({ - method: "POST", - url: "/api/test/clock-offset", - payload: { offsetMs: 3_600_000 }, - }); - expect(response.statusCode).toBe(200); - const time = (await app.inject({ method: "GET", url: "/api/time" })).json(); - expect(time.realTime).toBe(clock.now + 3_600_000); - }); - - it("schedules arrival jobs on the real clock while an offset is set", async () => { - const { app, jobs } = testApp(); - await app.inject({ - method: "POST", - url: "/api/test/clock-offset", - payload: { offsetMs: 60_000 }, - }); - const { dispatch } = await dispatched(); - expect(jobs.sent[0]!.startAfter).toBe(dispatch.arrivalAt - 60_000); - }); - - it("does not exist in production", async () => { - const app = buildApp(testConfig({ NODE_ENV: "production" }), { - database: database(), - jobs: createRecordingQueue(), - }); - const response = await app.inject({ - method: "POST", - url: "/api/test/clock-offset", - payload: { offsetMs: 1 }, - }); - expect(response.statusCode).toBe(404); - await app.close(); - }); -}); diff --git a/apps/server/test/backup.test.ts b/apps/server/test/backup.test.ts deleted file mode 100644 index 5b40917..0000000 --- a/apps/server/test/backup.test.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { - backupCodeResponseSchema, - createGuestResponseSchema, - errorResponseSchema, -} from "@deadhead/schema"; -import { sql } from "drizzle-orm"; -import { describe, expect, it } from "vitest"; -import { BACKUP_ALPHABET, normalizeBackupCode } from "../src/game/backup"; -import { setCredits, useTestDatabase } from "./db"; -import { call, cookieFrom, getState, signUp, signUpOwner, useTestApp, type Guest } from "./game"; - -const database = useTestDatabase(); -const testApp = useTestApp(database); - -const makeCode = async (guest: Guest) => - backupCodeResponseSchema.parse( - (await call(testApp().app, guest, "POST", "/api/backup-code", {})).json(), - ).code; - -/** Restores on a new device (no cookie); `device` sets the device-id header. */ -const restore = (code: string, device = "device-a", ip = "10.0.0.1") => - testApp().app.inject({ - method: "POST", - url: "/api/restore", - headers: { "x-device-id": device }, - remoteAddress: ip, - payload: { code }, - }); - -describe("backup codes (Step 10K.8)", () => { - it("makes a 20-character code in four groups, without look-alike characters", async () => { - const guest = await signUp(testApp().app); - const code = await makeCode(guest); - expect(code).toMatch(/^[A-Z2-9]{5}-[A-Z2-9]{5}-[A-Z2-9]{5}-[A-Z2-9]{5}$/); - for (const ch of normalizeBackupCode(code)) expect(BACKUP_ALPHABET).toContain(ch); - expect(code).not.toMatch(/[01OIL]/); - expect((await call(testApp().app, guest, "GET", "/api/backup-code")).json()).toMatchObject({ - hasCode: true, - }); - }); - - it("restores the same player on a second device, and both see the same state", async () => { - const { app } = testApp(); - const owner = await signUpOwner(app, database().db); - await setCredits(database().db, owner.playerId, 4321); - const code = await makeCode(owner); - - const response = await restore(code.toLowerCase().replaceAll("-", " ")); - expect(response.statusCode).toBe(200); - const body = createGuestResponseSchema.parse(response.json()); - expect(body.playerId).toBe(owner.playerId); - const second: Guest = { ...owner, cookie: cookieFrom(response.headers["set-cookie"]) }; - expect(second.cookie).not.toBe(owner.cookie); - - await call(app, second, "POST", "/api/company/name", { name: "Second Device Co" }); - expect((await getState(app, owner)).company).toMatchObject({ - name: "Second Device Co", - credits: 4321, - }); - }); - - it("stops the old code working when a new one is made", async () => { - const guest = await signUp(testApp().app); - const first = await makeCode(guest); - const second = await makeCode(guest); - expect(second).not.toBe(first); - expect((await restore(first)).statusCode).toBe(409); - expect((await restore(second)).statusCode).toBe(200); - }); - - it("fails a wrong code with a generic error, and limits attempts", async () => { - const wrong = await restore("ABCDE-FGHJK-MNPQR-STUVW", "device-b", "10.0.0.2"); - expect(wrong.statusCode).toBe(409); - expect(errorResponseSchema.parse(wrong.json()).error.code).toBe("restore-failed"); - for (let i = 0; i < 4; i++) await restore("NOPE", "device-b", "10.0.0.2"); - const limited = await restore("NOPE", "device-b", "10.0.0.2"); - expect(limited.statusCode).toBe(429); - expect(errorResponseSchema.parse(limited.json()).error.code).toBe("rate-limited"); - // The same device from another network is still limited. - expect((await restore("NOPE", "device-b", "10.0.0.3")).statusCode).toBe(429); - }); - - it("never stores the plain code", async () => { - const guest = await signUp(testApp().app); - const code = await makeCode(guest); - const plain = normalizeBackupCode(code); - const rows = await database().db.execute( - sql`select row_to_json(p)::text as row from players p`, - ); - for (const { row } of rows.rows as { row: string }[]) { - expect(row).not.toContain(code); - expect(row).not.toContain(plain); - } - }); - - it("still restores the player after they start a new company", async () => { - const owner = await signUpOwner(testApp().app, database().db); - const code = await makeCode(owner); - await call(testApp().app, owner, "POST", "/api/company/new", {}); - const response = await restore(code, "device-c", "10.0.0.4"); - expect(createGuestResponseSchema.parse(response.json())).toMatchObject({ - playerId: owner.playerId, - state: { tutorial: { runsCompleted: 0 } }, - }); - }); -}); diff --git a/apps/server/test/board-keys.test.ts b/apps/server/test/board-keys.test.ts deleted file mode 100644 index e27cb9f..0000000 --- a/apps/server/test/board-keys.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { useTestDatabase } from "./db"; -import { putBoard, signUp, testContract, useTestApp } from "./game"; - -const database = useTestDatabase(); -const testApp = useTestApp(database); - -describe("contract boards keyed by location (CR-002)", () => { - it("lets one player have boards at two locations in the same period", async () => { - const { db } = database(); - const guest = await signUp(testApp().app); - await putBoard(db, guest.playerId, [testContract()], undefined, "earth"); - await putBoard( - db, - guest.playerId, - [testContract({ originId: "moon", destinationId: "earth" })], - undefined, - "moon", - ); - }); - - it("rejects a second board for the same player, location and period", async () => { - const { db } = database(); - const guest = await signUp(testApp().app); - await putBoard(db, guest.playerId, [testContract()], undefined, "moon"); - await expect( - putBoard(db, guest.playerId, [testContract()], undefined, "moon"), - ).rejects.toThrow(); - }); -}); diff --git a/apps/server/test/buy-ship.test.ts b/apps/server/test/buy-ship.test.ts deleted file mode 100644 index 0cbea74..0000000 --- a/apps/server/test/buy-ship.test.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { randomUUID } from "node:crypto"; -import { content } from "@deadhead/content"; -import { errorResponseSchema, shipResponseSchema } from "@deadhead/schema"; -import { describe, expect, it } from "vitest"; -import { bays } from "../src/db/schema"; -import { shipName } from "../src/game/names"; -import { setCredits, TEST_NOW, useTestDatabase } from "./db"; -import { call, getState, signUp, signUpOwner, useTestApp } from "./game"; - -const database = useTestDatabase(); -const testApp = useTestApp(database); - -const errorCode = (response: { json(): unknown }) => - errorResponseSchema.parse(response.json()).error.code; - -describe("buy a ship", () => { - it("deducts the price and adds an idle, uncrewed ship at the Earth bay", async () => { - const { app } = testApp(); - const owner = await signUpOwner(app, database().db); - const price = content.shipModel("courier")!.price; - await setCredits(database().db, owner.playerId, price + 500); - - const response = await call(app, owner, "POST", "/api/ships", { - modelId: "courier", - bayId: owner.earthBayId, - }); - expect(response.statusCode).toBe(200); - const { ship } = shipResponseSchema.parse(response.json()); - expect(ship).toMatchObject({ - modelId: "courier", - status: "idle", - locationId: "earth", - pilotId: null, - dispatchId: null, - }); - expect(ship.name.length).toBeGreaterThan(0); - - const state = await getState(app, owner); - expect(state.company.credits).toBe(500); - expect(state.ships.map((s) => s.id)).toEqual([owner.shipId, ship.id]); - }); - - it("returns insufficient-funds and changes nothing without enough credits", async () => { - const { app } = testApp(); - const owner = await signUpOwner(app, database().db); - const price = content.shipModel("freighter")!.price; - await setCredits(database().db, owner.playerId, price - 1); - - const response = await call(app, owner, "POST", "/api/ships", { - modelId: "freighter", - bayId: owner.earthBayId, - }); - expect(response.statusCode).toBe(409); - expect(errorCode(response)).toBe("insufficient-funds"); - - const state = await getState(app, owner); - expect(state.company.credits).toBe(price - 1); - expect(state.ships).toHaveLength(1); - }); - - it("returns tutorial-active during the tutorial and changes nothing", async () => { - const { app } = testApp(); - const guest = await signUp(app); - await setCredits(database().db, guest.playerId, 1_000_000); - - const response = await call(app, guest, "POST", "/api/ships", { - modelId: "courier", - bayId: "any", - }); - expect(response.statusCode).toBe(409); - expect(errorCode(response)).toBe("tutorial-active"); - - const state = await getState(app, guest); - expect(state.company.credits).toBe(1_000_000); - expect(state.ships).toHaveLength(0); - }); - - it("refuses to sell the company loaner", async () => { - const { app } = testApp(); - const owner = await signUpOwner(app, database().db); - await setCredits(database().db, owner.playerId, 1_000_000); - const response = await call(app, owner, "POST", "/api/ships", { - modelId: "company-courier", - bayId: owner.earthBayId, - }); - expect(response.statusCode).toBe(404); - }); - - it("rejects an unknown model", async () => { - const { app } = testApp(); - const owner = await signUpOwner(app, database().db); - const response = await call(app, owner, "POST", "/api/ships", { - modelId: "x-wing", - bayId: owner.earthBayId, - }); - expect(response.statusCode).toBe(404); - expect(errorCode(response)).toBe("not-found"); - }); -}); - -describe("ship delivery to a bay (CR-002)", () => { - it("delivers a Freighter bought for the Moon bay to the Moon", async () => { - const { app } = testApp(); - const owner = await signUpOwner(app, database().db); - const moonBayId = randomUUID(); - await database() - .db.insert(bays) - .values({ id: moonBayId, playerId: owner.playerId, locationId: "moon", createdAt: TEST_NOW }); - await setCredits(database().db, owner.playerId, content.shipModel("freighter")!.price); - - const response = await call(app, owner, "POST", "/api/ships", { - modelId: "freighter", - bayId: moonBayId, - }); - expect(response.statusCode).toBe(200); - expect(shipResponseSchema.parse(response.json()).ship).toMatchObject({ - locationId: "moon", - status: "idle", - pilotId: null, - }); - }); - - it("rejects a purchase with no bay id as a validation error", async () => { - const { app } = testApp(); - const owner = await signUpOwner(app, database().db); - await setCredits(database().db, owner.playerId, 1_000_000); - const response = await call(app, owner, "POST", "/api/ships", { modelId: "courier" }); - expect(response.statusCode).toBe(400); - expect(errorCode(response)).toBe("validation-error"); - expect((await getState(app, owner)).company.credits).toBe(1_000_000); - }); - - it("refuses another player's bay and changes nothing", async () => { - const { app } = testApp(); - const owner = await signUpOwner(app, database().db); - const other = await signUpOwner(app, database().db); - await setCredits(database().db, owner.playerId, 1_000_000); - - const response = await call(app, owner, "POST", "/api/ships", { - modelId: "courier", - bayId: other.earthBayId, - }); - expect(response.statusCode).toBe(404); - expect(errorCode(response)).toBe("not-found"); - const state = await getState(app, owner); - expect(state.company.credits).toBe(1_000_000); - expect(state.ships).toHaveLength(1); - expect((await getState(app, other)).ships).toHaveLength(1); - }); -}); - -describe("ship names", () => { - it("are stable for a world seed and differ between ships", () => { - expect(shipName(42, 0)).toBe(shipName(42, 0)); - const names = new Set(Array.from({ length: 10 }, (_, i) => shipName(42, i))); - expect(names.size).toBeGreaterThan(1); - }); -}); diff --git a/apps/server/test/cleanup.test.ts b/apps/server/test/cleanup.test.ts deleted file mode 100644 index 3b7b737..0000000 --- a/apps/server/test/cleanup.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { content } from "@deadhead/content"; -import { boardPeriodIndex, boardPeriodMs } from "@deadhead/sim"; -import { describe, expect, it } from "vitest"; -import { acceptedContracts, contractBoards } from "../src/db/schema"; -import { BOARD_RETENTION_PERIODS, cleanupExpiredContracts } from "../src/jobs/cleanup"; -import { appJobs, CLEANUP_CONTRACTS_JOB } from "../src/jobs/definitions"; -import { makeOwner, TEST_NOW, useTestDatabase } from "./db"; -import { insertDispatch, putBoard, signUp, testContract, useTestApp } from "./game"; - -const database = useTestDatabase(); -const testApp = useTestApp(database); - -describe("expired contract cleanup", () => { - it("deletes old boards and accepted contracts and keeps current ones", async () => { - const { db } = database(); - const guest = await signUp(testApp().app); - const { shipId } = await makeOwner(db, guest.playerId, guest.avatarId); - const period = boardPeriodMs(content.config); - const current = boardPeriodIndex(TEST_NOW, content.config); - const old = current - BOARD_RETENTION_PERIODS - 1; - - // A board from long ago and one from the current period. - await putBoard(db, guest.playerId, [testContract()], TEST_NOW - (current - old) * period); - await putBoard(db, guest.playerId, [testContract()], TEST_NOW); - - // An accepted contract from each period, both pointing at real dispatches. - const record = await insertDispatch(db, { - playerId: guest.playerId, - shipId, - pilotId: guest.avatarId, - departAt: TEST_NOW, - }); - await db.insert(acceptedContracts).values([ - { - playerId: guest.playerId, - contractId: "old", - periodIndex: old, - dispatchId: record.id, - acceptedAt: TEST_NOW, - }, - { - playerId: guest.playerId, - contractId: "new", - periodIndex: current, - dispatchId: record.id, - acceptedAt: TEST_NOW, - }, - ]); - - expect(await cleanupExpiredContracts(db, TEST_NOW)).toEqual({ - boards: 1, - acceptedContracts: 1, - }); - expect((await db.select().from(contractBoards)).map((b) => b.periodIndex)).toEqual([current]); - expect((await db.select().from(acceptedContracts)).map((a) => a.contractId)).toEqual(["new"]); - }); - - it("keeps boards inside the retention window", async () => { - const { db } = database(); - const guest = await signUp(testApp().app); - const period = boardPeriodMs(content.config); - await putBoard( - db, - guest.playerId, - [testContract()], - TEST_NOW - BOARD_RETENTION_PERIODS * period, - ); - expect(await cleanupExpiredContracts(db, TEST_NOW)).toEqual({ - boards: 0, - acceptedContracts: 0, - }); - }); - - it("runs as an hourly job", () => { - const job = appJobs({ db: database().db, clock: () => TEST_NOW }).find( - (j) => j.name === CLEANUP_CONTRACTS_JOB, - ); - expect(job?.cron).toBe("0 * * * *"); - }); -}); diff --git a/apps/server/test/company-name.test.ts b/apps/server/test/company-name.test.ts deleted file mode 100644 index 51f89c2..0000000 --- a/apps/server/test/company-name.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { companyResponseSchema, errorResponseSchema } from "@deadhead/schema"; -import { describe, expect, it } from "vitest"; -import { companyName } from "../src/game/names"; -import { useTestDatabase } from "./db"; -import { call, getState, signUp, useTestApp } from "./game"; - -const database = useTestDatabase(); -const testApp = useTestApp(database); - -const rename = async (name: string) => { - const guest = await signUp(testApp().app); - const before = (await getState(testApp().app, guest)).company.name; - const response = await call(testApp().app, guest, "POST", "/api/company/name", { name }); - return { guest, before, response }; -}; - -describe("company name (Step 10K.5)", () => { - it("starts with a name made from the world seed", async () => { - const guest = await signUp(testApp().app); - const state = await getState(testApp().app, guest); - expect(state.company.name.length).toBeGreaterThanOrEqual(3); - expect(companyName(42)).toBe(companyName(42)); - const names = new Set(Array.from({ length: 30 }, (_, i) => companyName(i))); - expect(names.size).toBeGreaterThan(1); - }); - - it("saves a valid name, trimmed", async () => { - const { guest, response } = await rename(" Rosa & Sons Haulage! "); - expect(response.statusCode).toBe(200); - expect(companyResponseSchema.parse(response.json()).company.name).toBe("Rosa & Sons Haulage!"); - expect((await getState(testApp().app, guest)).company.name).toBe("Rosa & Sons Haulage!"); - }); - - it.each([ - ["too short", "Ab"], - ["too long", "A".repeat(33)], - ["blank after trimming", " "], - ["disallowed characters", "Freight