1
0
Fork 0

chore(migrations): track schema snapshots and require explicit apply

This commit is contained in:
Jason Fraley 2026-08-26 00:53:57 -04:00
parent 83f203d736
commit b22a1836e4
30 changed files with 250316 additions and 23 deletions

View file

@ -185,7 +185,7 @@ gameTick → POST `/api/game-tick/notify` (guarded by `x-game-tick-secret` heade
- **Server actions**: `src/app/(frontend)/logistics/banking/actions.ts`. `createBankAccount` (regular users may only create their own personal account; treasury/faction require a manager), `ensureMyAccount`, `depositFunds`/`withdrawFunds`/`transferFunds`. Permission model: personal accounts are owner- or manager-only; **treasury/faction accounts are manager-only**. Manager = admin/developer or logistics-qualified (`hasLogisticsQualification`). Emits `finance:deposit` / `finance:withdraw` / `finance:transfer` / `bank:account-create` events. - **Server actions**: `src/app/(frontend)/logistics/banking/actions.ts`. `createBankAccount` (regular users may only create their own personal account; treasury/faction require a manager), `ensureMyAccount`, `depositFunds`/`withdrawFunds`/`transferFunds`. Permission model: personal accounts are owner- or manager-only; **treasury/faction accounts are manager-only**. Manager = admin/developer or logistics-qualified (`hasLogisticsQualification`). Emits `finance:deposit` / `finance:withdraw` / `finance:transfer` / `bank:account-create` events.
- **UI**: `src/app/(frontend)/logistics/banking/` (overview + `[id]` detail), components in `src/components/frontend/banking/` (`BankingOverview`, `AccountCard`, `AccountDetail`, `CreateAccountDialog`, `BankTransactionDialog`, `LedgerTable`, `MyWalletCard`). Sidebar entry "Banking" under Logistics. - **UI**: `src/app/(frontend)/logistics/banking/` (overview + `[id]` detail), components in `src/components/frontend/banking/` (`BankingOverview`, `AccountCard`, `AccountDetail`, `CreateAccountDialog`, `BankTransactionDialog`, `LedgerTable`, `MyWalletCard`). Sidebar entry "Banking" under Logistics.
- **Event targets**: `bank-accounts`, `bank-transactions`, `ledger-entries` added to `GameEventLogs` `TARGET_COLLECTIONS` and `emit.ts` `targetCollection` union. Finance event types in `eventTypes.ts`. - **Event targets**: `bank-accounts`, `bank-transactions`, `ledger-entries` added to `GameEventLogs` `TARGET_COLLECTIONS` and `emit.ts` `targetCollection` union. Finance event types in `eventTypes.ts`.
- **Gotchas**: Payload's create TS overloads reject `undefined` on relationship/required fields — pass `null` for empty relationships and a concrete value (`""` for `transactionNumber`) or TS falls through to the draft-variant and errors `Property 'draft' is missing`. No migration has been added for these collections yet (project relies on dev `push: true`). - **Gotchas**: Payload's create TS overloads reject `undefined` on relationship/required fields — pass `null` for empty relationships and a concrete value (`""` for `transactionNumber`) or TS falls through to the draft-variant and errors `Property 'draft' is missing`. No migration has been added for these collections yet (dev uses `push: false`; run `bun run payload migrate` after schema changes).
## Trading Marketplace ## Trading Marketplace
@ -213,7 +213,7 @@ gameTick → POST `/api/game-tick/notify` (guarded by `x-game-tick-secret` heade
- **NPC attribution**: auto entries belong to a `game-npcs` NPC instead of a user. `src/lib/market/npcs.ts` — `resolveVendorNpc(payload, assetId)` prefers an in-game vendor NPC that `vendor.sells` the asset, falls back to any enabled vendor NPC selling it, otherwise `createGeneratedNpc` (random name/occupation, `isGenerated: true`, `isInGame: false`, set up as a vendor for that asset). `applyNpcPriceModifier(base, npc)` applies `vendor.priceModifier`. GMs create real NPCs in the admin panel and tick `isInGame` on generated placeholders to promote them. - **NPC attribution**: auto entries belong to a `game-npcs` NPC instead of a user. `src/lib/market/npcs.ts` — `resolveVendorNpc(payload, assetId)` prefers an in-game vendor NPC that `vendor.sells` the asset, falls back to any enabled vendor NPC selling it, otherwise `createGeneratedNpc` (random name/occupation, `isGenerated: true`, `isInGame: false`, set up as a vendor for that asset). `applyNpcPriceModifier(base, npc)` applies `vendor.priceModifier`. GMs create real NPCs in the admin panel and tick `isInGame` on generated placeholders to promote them.
- **UI**: `src/app/(frontend)/logistics/market/` page + `src/components/frontend/market/` (`MarketView` with search/rarity/type filters, `ListingCard` with Buy/Offer/Cancel, `CreateListingDialog`, `NegotiationPanel`, `MakeOfferDialog`, `NpcChat`, `Countdown`). Sidebar entry "Market" under Logistics. Uses `formatAmount` from `src/lib/banking/format` for prices. Auto entries show the NPC name + occupation in the "from" line (`npcLabel`), falling back to "Market vendor" for legacy entries with no NPC. - **UI**: `src/app/(frontend)/logistics/market/` page + `src/components/frontend/market/` (`MarketView` with search/rarity/type filters, `ListingCard` with Buy/Offer/Cancel, `CreateListingDialog`, `NegotiationPanel`, `MakeOfferDialog`, `NpcChat`, `Countdown`). Sidebar entry "Market" under Logistics. Uses `formatAmount` from `src/lib/banking/format` for prices. Auto entries show the NPC name + occupation in the "from" line (`npcLabel`), falling back to "Market vendor" for legacy entries with no NPC.
- **Event targets**: `market-listings` + `market-negotiations` added to `GameEventLogs` `TARGET_COLLECTIONS` and `emit.ts` `targetCollection` union. Market event types in `eventTypes.ts`. - **Event targets**: `market-listings` + `market-negotiations` added to `GameEventLogs` `TARGET_COLLECTIONS` and `emit.ts` `targetCollection` union. Market event types in `eventTypes.ts`.
- **Gotchas**: The `market-tick` bin must be run regularly (external cron) for expiry + vendor stock to update. `buyListing` credits the locker before clearing the payment — if the buyer's locker is full, the payment already moved and the purchase throws; consider escrow/refund handling in a later pass. When a seller accepts a buyer's offer, `buyListing` runs as the **buyer** (from `negotiation.buyer`), never the accepting actor. No migration added yet (dev relies on `push: true`; the `quantity` column on `market-negotiations` was added directly to the dev DB via `ALTER TABLE` because `bun run db push` fails with `must be owner of table spatial_ref_sys`). - **Gotchas**: The `market-tick` bin must be run regularly (external cron) for expiry + vendor stock to update. `buyListing` credits the locker before clearing the payment — if the buyer's locker is full, the payment already moved and the purchase throws; consider escrow/refund handling in a later pass. When a seller accepts a buyer's offer, `buyListing` runs as the **buyer** (from `negotiation.buyer`), never the accepting actor. No migration added yet (dev uses `push: false`; run `bun run payload migrate` after schema changes). `bun run db push` fails with `must be owner of table spatial_ref_sys` — use Payload's migration runner instead.
## Notifications ## Notifications
@ -268,7 +268,7 @@ Username-based login (no email login). Users log in via Payload admin with `user
PostgreSQL via `@payloadcms/db-postgres`. Schema defined in `src/payload-generated-schema.ts`. Migrations in `src/migrations/` (timestamp-named .ts + .json pairs, registered in `migrations/index.ts`). Drizzle config reads `DATABASE_URI` from env. PostgreSQL via `@payloadcms/db-postgres`. Schema defined in `src/payload-generated-schema.ts`. Migrations in `src/migrations/` (timestamp-named .ts + .json pairs, registered in `migrations/index.ts`). Drizzle config reads `DATABASE_URI` from env.
In development, `postgresAdapter` uses `push: true` to auto-sync schema. In development, `postgresAdapter` uses `push: false` — run `bun run payload migrate` after schema changes to apply them locally.
## Code style ## Code style
@ -302,7 +302,7 @@ shadcn/ui components live in `src/components/ui/`. Use `bunx shadcn@latest add <
- Playwright tests auto-start the dev server — make sure port 3000 is free before running e2e. - Playwright tests auto-start the dev server — make sure port 3000 is free before running e2e.
- Payload admin layout and importMap are auto-generated — do not edit by hand. - Payload admin layout and importMap are auto-generated — do not edit by hand.
- `.npmrc` sets `legacy-peer-deps=true` for dependency resolution compatibility. - `.npmrc` sets `legacy-peer-deps=true` for dependency resolution compatibility.
- `bun run db push` fails with `must be owner of table spatial_ref_sys` (a PostGIS table owned by the DB superuser) — drizzle-kit push does a full-schema diff and trips on it. Workaround: apply the needed `ALTER TABLE` directly (via node + `pg` reading `DATABASE_URI` from `.env`) or start the dev server, whose Payload `push: true` path may skip the offending table. - `bun run db push` fails with `must be owner of table spatial_ref_sys` (a PostGIS table owned by the DB superuser) — drizzle-kit push does a full-schema diff and trips on it. Workaround: apply the needed `ALTER TABLE` directly (via node + `pg` reading `DATABASE_URI` from `.env`) or use Payload's migration runner (`bun run payload migrate`).
## Server Actions convention ## Server Actions convention

View file

@ -162,7 +162,7 @@ docker exec <container-name> node node_modules/payload/bin.js migrate
Coolify Scheduled Tasks support ad-hoc runs of the same command — fire it manually, watch the logs, then commit. **Never** run drizzle-kit (`bun run db migrate`) in prod; `bun run db push` fails on this DB because of the PostGIS `spatial_ref_sys` ownership issue (see `AGENTS.md` Gotchas). Use Payload's migration runner exclusively. Coolify Scheduled Tasks support ad-hoc runs of the same command — fire it manually, watch the logs, then commit. **Never** run drizzle-kit (`bun run db migrate`) in prod; `bun run db push` fails on this DB because of the PostGIS `spatial_ref_sys` ownership issue (see `AGENTS.md` Gotchas). Use Payload's migration runner exclusively.
For dev (where `push: true` in `payload.config.ts`), starting the dev server auto-applies schema changes — that's intentional and fine for dev databases. For dev (where `push: false` in `payload.config.ts`), run `bun run payload migrate` after schema changes to apply them locally.
--- ---

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,175 @@
import { MigrateUpArgs, MigrateDownArgs, sql } from "@payloadcms/db-postgres";
/**
* Adds targeting, path matching, and enabled fields to the shims global.
* This is a diff migration — production already has the base shims_shims table
* with (name, summary, content, type). This adds the new columns, enum types,
* and junction tables without recreating anything that already exists.
*/
export async function up({ db }: MigrateUpArgs): Promise<void> {
// --- New enum types ---
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum_shims_shims_targeting_audience"
AS ENUM('all', 'percentage', 'users', 'roles');
EXCEPTION WHEN duplicate_object THEN null;
END $$;
`);
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum_shims_shims_targeting_roles"
AS ENUM('guest', 'user', 'trusted', 'admin', 'developer');
EXCEPTION WHEN duplicate_object THEN null;
END $$;
`);
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum_shims_shims_paths_match_type"
AS ENUM('all', 'include', 'exclude');
EXCEPTION WHEN duplicate_object THEN null;
END $$;
`);
// --- New columns on shims_shims ---
await db.execute(sql`
ALTER TABLE "shims_shims"
ADD COLUMN IF NOT EXISTS "enabled" boolean DEFAULT false NOT NULL;
`);
await db.execute(sql`
ALTER TABLE "shims_shims"
ADD COLUMN IF NOT EXISTS "targeting_audience"
"enum_shims_shims_targeting_audience" DEFAULT 'all' NOT NULL;
`);
await db.execute(sql`
ALTER TABLE "shims_shims"
ADD COLUMN IF NOT EXISTS "targeting_percentage" numeric;
`);
await db.execute(sql`
ALTER TABLE "shims_shims"
ADD COLUMN IF NOT EXISTS "paths_match_type"
"enum_shims_shims_paths_match_type" DEFAULT 'all' NOT NULL;
`);
// --- Junction table: shims_shims_targeting_roles (hasMany select) ---
await db.execute(sql`
CREATE TABLE IF NOT EXISTS "shims_shims_targeting_roles" (
"order" integer NOT NULL,
"parent_id" varchar NOT NULL,
"value" "enum_shims_shims_targeting_roles",
"id" serial PRIMARY KEY NOT NULL
);
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "shims_shims_targeting_roles"
ADD CONSTRAINT "shims_shims_targeting_roles_parent_id_fk"
FOREIGN KEY ("parent_id") REFERENCES "public"."shims_shims"("id")
ON DELETE cascade ON UPDATE no action;
EXCEPTION WHEN duplicate_object THEN null;
END $$;
`);
await db.execute(sql`
CREATE INDEX IF NOT EXISTS "shims_shims_targeting_roles_order_idx"
ON "shims_shims_targeting_roles" USING btree ("order");
CREATE INDEX IF NOT EXISTS "shims_shims_targeting_roles_parent_id_idx"
ON "shims_shims_targeting_roles" USING btree ("parent_id");
`);
// --- Junction table: shims_shims_targeting_users (hasMany relationship) ---
await db.execute(sql`
CREATE TABLE IF NOT EXISTS "shims_shims_targeting_users" (
"_order" integer NOT NULL,
"_parent_id" varchar NOT NULL,
"id" varchar PRIMARY KEY NOT NULL,
"value" integer NOT NULL
);
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "shims_shims_targeting_users"
ADD CONSTRAINT "shims_shims_targeting_users_parent_id_fk"
FOREIGN KEY ("_parent_id") REFERENCES "public"."shims_shims"("id")
ON DELETE cascade ON UPDATE no action;
EXCEPTION WHEN duplicate_object THEN null;
END $$;
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "shims_shims_targeting_users"
ADD CONSTRAINT "shims_shims_targeting_users_value_fk"
FOREIGN KEY ("value") REFERENCES "public"."users"("id")
ON DELETE set null ON UPDATE no action;
EXCEPTION WHEN duplicate_object THEN null;
END $$;
`);
await db.execute(sql`
CREATE INDEX IF NOT EXISTS "shims_shims_targeting_users_order_idx"
ON "shims_shims_targeting_users" USING btree ("_order");
CREATE INDEX IF NOT EXISTS "shims_shims_targeting_users_parent_id_idx"
ON "shims_shims_targeting_users" USING btree ("_parent_id");
CREATE INDEX IF NOT EXISTS "shims_shims_targeting_users_value_idx"
ON "shims_shims_targeting_users" USING btree ("value");
`);
// --- Junction table: shims_shims_paths_paths (hasMany text) ---
await db.execute(sql`
CREATE TABLE IF NOT EXISTS "shims_shims_paths_paths" (
"_order" integer NOT NULL,
"_parent_id" varchar NOT NULL,
"id" varchar PRIMARY KEY NOT NULL,
"value" varchar
);
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "shims_shims_paths_paths"
ADD CONSTRAINT "shims_shims_paths_paths_parent_id_fk"
FOREIGN KEY ("_parent_id") REFERENCES "public"."shims_shims"("id")
ON DELETE cascade ON UPDATE no action;
EXCEPTION WHEN duplicate_object THEN null;
END $$;
`);
await db.execute(sql`
CREATE INDEX IF NOT EXISTS "shims_shims_paths_paths_order_idx"
ON "shims_shims_paths_paths" USING btree ("_order");
CREATE INDEX IF NOT EXISTS "shims_shims_paths_paths_parent_id_idx"
ON "shims_shims_paths_paths" USING btree ("_parent_id");
`);
}
export async function down({ db }: MigrateDownArgs): Promise<void> {
// Drop junction tables (cascade removes FK rows)
await db.execute(sql`DROP TABLE IF EXISTS "shims_shims_paths_paths";`);
await db.execute(sql`DROP TABLE IF EXISTS "shims_shims_targeting_users";`);
await db.execute(sql`DROP TABLE IF EXISTS "shims_shims_targeting_roles";`);
// Drop columns
await db.execute(sql`
ALTER TABLE "shims_shims" DROP COLUMN IF EXISTS "enabled";
ALTER TABLE "shims_shims" DROP COLUMN IF EXISTS "targeting_audience";
ALTER TABLE "shims_shims" DROP COLUMN IF EXISTS "targeting_percentage";
ALTER TABLE "shims_shims" DROP COLUMN IF EXISTS "paths_match_type";
`);
// Drop enum types
await db.execute(sql`DROP TYPE IF EXISTS "public"."enum_shims_shims_paths_match_type";`);
await db.execute(sql`DROP TYPE IF EXISTS "public"."enum_shims_shims_targeting_roles";`);
await db.execute(sql`DROP TYPE IF EXISTS "public"."enum_shims_shims_targeting_audience";`);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,74 @@
import { MigrateDownArgs, MigrateUpArgs, sql } from "@payloadcms/db-postgres";
/**
* Repairs auxiliary tables required by Payload's current Shims global schema.
*
* The original Shims migrations created the global and its array/targeting
* tables, but the later relationship/text fields also require these two
* Payload-managed tables. Existing databases need this additive repair.
*/
export async function up({ db }: MigrateUpArgs): Promise<void> {
await db.execute(sql`
CREATE TABLE IF NOT EXISTS "shims_texts" (
"id" serial PRIMARY KEY NOT NULL,
"order" integer NOT NULL,
"parent_id" integer NOT NULL,
"path" varchar NOT NULL,
"text" varchar
);
CREATE TABLE IF NOT EXISTS "shims_rels" (
"id" serial PRIMARY KEY NOT NULL,
"order" integer,
"parent_id" integer NOT NULL,
"path" varchar NOT NULL,
"users_id" integer
);
`);
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "shims_texts"
ADD CONSTRAINT "shims_texts_parent_fk"
FOREIGN KEY ("parent_id") REFERENCES "public"."shims"("id")
ON DELETE cascade ON UPDATE no action;
EXCEPTION WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
ALTER TABLE "shims_rels"
ADD CONSTRAINT "shims_rels_parent_fk"
FOREIGN KEY ("parent_id") REFERENCES "public"."shims"("id")
ON DELETE cascade ON UPDATE no action;
EXCEPTION WHEN duplicate_object THEN null;
END $$;
DO $$ BEGIN
ALTER TABLE "shims_rels"
ADD CONSTRAINT "shims_rels_users_fk"
FOREIGN KEY ("users_id") REFERENCES "public"."users"("id")
ON DELETE cascade ON UPDATE no action;
EXCEPTION WHEN duplicate_object THEN null;
END $$;
`);
await db.execute(sql`
CREATE INDEX IF NOT EXISTS "shims_texts_order_parent"
ON "shims_texts" USING btree ("order", "parent_id");
CREATE INDEX IF NOT EXISTS "shims_rels_order_idx"
ON "shims_rels" USING btree ("order");
CREATE INDEX IF NOT EXISTS "shims_rels_parent_idx"
ON "shims_rels" USING btree ("parent_id");
CREATE INDEX IF NOT EXISTS "shims_rels_path_idx"
ON "shims_rels" USING btree ("path");
CREATE INDEX IF NOT EXISTS "shims_rels_users_id_idx"
ON "shims_rels" USING btree ("users_id");
`);
}
export async function down({ db }: MigrateDownArgs): Promise<void> {
await db.execute(sql`
DROP TABLE IF EXISTS "shims_rels";
DROP TABLE IF EXISTS "shims_texts";
`);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,276 @@
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
// Idempotent migration: safe to run on databases where push: true already applied
// the schema (dev), and on fresh production databases that need these objects.
// Each DDL block is wrapped in exception handlers to skip already-existing objects.
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
// --- Enums ---
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum_users_preferences_display_lead_or_actual" AS ENUM('lead', 'actual');
EXCEPTION WHEN duplicate_object THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum_roles_permissions" AS ENUM('system:admin-access', 'users:create', 'users:read', 'users:update', 'users:delete', 'users:unlock', 'users:assign-roles', 'ranks:create', 'ranks:read', 'ranks:update', 'ranks:delete', 'profiles:create', 'profiles:read', 'profiles:update', 'profiles:delete', 'awards:create', 'awards:read', 'awards:update', 'awards:delete', 'qualifications:create', 'qualifications:read', 'qualifications:update', 'qualifications:delete', 'assignments:create', 'assignments:read', 'assignments:update', 'assignments:delete', 'experience:create', 'experience:read', 'experience:update', 'experience:delete', 'user-notifications:create', 'user-notifications:read', 'user-notifications:update', 'user-notifications:delete', 'missions:create', 'missions:read', 'missions:update', 'missions:delete', 'missions:read-sensitive', 'mission-attendances:create', 'mission-attendances:read', 'mission-attendances:update', 'mission-attendances:delete', 'campaigns:create', 'campaigns:read', 'campaigns:update', 'campaigns:delete', 'factions:create', 'factions:read', 'factions:update', 'factions:delete', 'technologies:create', 'technologies:read', 'technologies:update', 'technologies:delete', 'assets:create', 'assets:read', 'assets:update', 'assets:delete', 'resources:create', 'resources:read', 'resources:update', 'resources:delete', 'vehicles:create', 'vehicles:read', 'vehicles:update', 'vehicles:delete', 'structures:create', 'structures:read', 'structures:update', 'structures:delete', 'shipments:create', 'shipments:read', 'shipments:update', 'shipments:delete', 'bank-accounts:create', 'bank-accounts:read', 'bank-accounts:update', 'bank-accounts:delete', 'bank-transactions:create', 'bank-transactions:read', 'bank-transactions:update', 'bank-transactions:delete', 'ledger-entries:create', 'ledger-entries:read', 'ledger-entries:update', 'ledger-entries:delete', 'locker-storages:create', 'locker-storages:read', 'locker-storages:update', 'locker-storages:delete', 'loadouts:create', 'loadouts:read', 'loadouts:update', 'loadouts:delete', 'market-listings:create', 'market-listings:read', 'market-listings:update', 'market-listings:delete', 'market-negotiations:create', 'market-negotiations:read', 'market-negotiations:update', 'market-negotiations:delete', 'tickets:create', 'tickets:read', 'tickets:update', 'tickets:delete', 'game-structures:create', 'game-structures:read', 'game-structures:update', 'game-structures:delete', 'game-vehicles:create', 'game-vehicles:read', 'game-vehicles:update', 'game-vehicles:delete', 'game-npcs:create', 'game-npcs:read', 'game-npcs:update', 'game-npcs:delete', 'game-hard-resources:create', 'game-hard-resources:read', 'game-hard-resources:update', 'game-hard-resources:delete', 'game-event-logs:create', 'game-event-logs:read', 'game-event-logs:update', 'game-event-logs:delete', 'maps:create', 'maps:read', 'maps:update', 'maps:delete', 'narrative-events:create', 'narrative-events:read', 'narrative-events:update', 'narrative-events:delete', 'mission-files:create', 'mission-files:read', 'mission-files:update', 'mission-files:delete', 'mod-lists:create', 'mod-lists:read', 'mod-lists:update', 'mod-lists:delete', 'game-rules:read', 'game-rules:update', 'shims:read', 'shims:update', 'logistics:manage', 'intelligence:manage', 'profiles:intel_excerpt:update', 'banking:manage', 'tickets:staff', 'discord:staff', 'discord:announce');
EXCEPTION WHEN duplicate_object THEN null;
END $$;
`)
// --- Tables ---
await db.execute(sql`
DO $$ BEGIN
CREATE TABLE "users_rels" (
"id" serial PRIMARY KEY NOT NULL,
"order" integer,
"parent_id" integer NOT NULL,
"path" varchar NOT NULL,
"roles_id" integer
);
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE TABLE "roles_permissions" (
"order" integer NOT NULL,
"parent_id" integer NOT NULL,
"value" "enum_roles_permissions",
"id" serial PRIMARY KEY NOT NULL
);
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE TABLE "roles" (
"id" serial PRIMARY KEY NOT NULL,
"name" varchar NOT NULL,
"slug" varchar NOT NULL,
"description" varchar,
"is_system" boolean DEFAULT false,
"is_superuser" boolean DEFAULT false,
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
);
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE TABLE "roles_rels" (
"id" serial PRIMARY KEY NOT NULL,
"order" integer,
"parent_id" integer NOT NULL,
"path" varchar NOT NULL,
"roles_id" integer
);
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
// --- Columns (ADD COLUMN with DEFAULT for NOT NULL columns to handle existing rows) ---
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "users" ADD COLUMN "preferences_display_lead_or_actual" "enum_users_preferences_display_lead_or_actual" DEFAULT 'lead';
EXCEPTION WHEN duplicate_column THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "users" ADD COLUMN "preferences_display_show_version_overlay" boolean DEFAULT true;
EXCEPTION WHEN duplicate_column THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "profiles" ADD COLUMN "dossier_enlistment_date" timestamp(3) with time zone DEFAULT now() NOT NULL;
EXCEPTION WHEN duplicate_column THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "payload_locked_documents_rels" ADD COLUMN "roles_id" integer;
EXCEPTION WHEN duplicate_column THEN null;
END $$;
`)
// --- Foreign keys ---
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "users_rels" ADD CONSTRAINT "users_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION WHEN duplicate_object THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "users_rels" ADD CONSTRAINT "users_rels_roles_fk" FOREIGN KEY ("roles_id") REFERENCES "public"."roles"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION WHEN duplicate_object THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "roles_permissions" ADD CONSTRAINT "roles_permissions_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."roles"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION WHEN duplicate_object THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "roles_rels" ADD CONSTRAINT "roles_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."roles"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION WHEN duplicate_object THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "roles_rels" ADD CONSTRAINT "roles_rels_roles_fk" FOREIGN KEY ("roles_id") REFERENCES "public"."roles"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION WHEN duplicate_object THEN null;
END $$;
`)
// --- Indexes (catch duplicate_table OR duplicate_object — PostgreSQL uses 42P07 for indexes) ---
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "users_rels_order_idx" ON "users_rels" USING btree ("order");
EXCEPTION WHEN duplicate_object OR duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "users_rels_parent_idx" ON "users_rels" USING btree ("parent_id");
EXCEPTION WHEN duplicate_object OR duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "users_rels_path_idx" ON "users_rels" USING btree ("path");
EXCEPTION WHEN duplicate_object OR duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "users_rels_roles_id_idx" ON "users_rels" USING btree ("roles_id");
EXCEPTION WHEN duplicate_object OR duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "roles_permissions_order_idx" ON "roles_permissions" USING btree ("order");
EXCEPTION WHEN duplicate_object OR duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "roles_permissions_parent_idx" ON "roles_permissions" USING btree ("parent_id");
EXCEPTION WHEN duplicate_object OR duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE UNIQUE INDEX "roles_name_idx" ON "roles" USING btree ("name");
EXCEPTION WHEN duplicate_object OR duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE UNIQUE INDEX "roles_slug_idx" ON "roles" USING btree ("slug");
EXCEPTION WHEN duplicate_object OR duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "roles_updated_at_idx" ON "roles" USING btree ("updated_at");
EXCEPTION WHEN duplicate_object OR duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "roles_created_at_idx" ON "roles" USING btree ("created_at");
EXCEPTION WHEN duplicate_object OR duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "roles_rels_order_idx" ON "roles_rels" USING btree ("order");
EXCEPTION WHEN duplicate_object OR duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "roles_rels_parent_idx" ON "roles_rels" USING btree ("parent_id");
EXCEPTION WHEN duplicate_object OR duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "roles_rels_path_idx" ON "roles_rels" USING btree ("path");
EXCEPTION WHEN duplicate_object OR duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "roles_rels_roles_id_idx" ON "roles_rels" USING btree ("roles_id");
EXCEPTION WHEN duplicate_object OR duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_roles_fk" FOREIGN KEY ("roles_id") REFERENCES "public"."roles"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION WHEN duplicate_object OR duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "payload_locked_documents_rels_roles_id_idx" ON "payload_locked_documents_rels" USING btree ("roles_id");
EXCEPTION WHEN duplicate_object OR duplicate_table THEN null;
END $$;
`)
}
export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
await db.execute(sql`
ALTER TABLE "users_rels" DISABLE ROW LEVEL SECURITY;
ALTER TABLE "roles_permissions" DISABLE ROW LEVEL SECURITY;
ALTER TABLE "roles" DISABLE ROW LEVEL SECURITY;
ALTER TABLE "roles_rels" DISABLE ROW LEVEL SECURITY;
DROP TABLE "users_rels" CASCADE;
DROP TABLE "roles_permissions" CASCADE;
DROP TABLE "roles" CASCADE;
DROP TABLE "roles_rels" CASCADE;
ALTER TABLE "payload_locked_documents_rels" DROP CONSTRAINT "payload_locked_documents_rels_roles_fk";
DROP INDEX "payload_locked_documents_rels_roles_id_idx";
ALTER TABLE "users" DROP COLUMN "preferences_display_lead_or_actual";
ALTER TABLE "users" DROP COLUMN "preferences_display_show_version_overlay";
ALTER TABLE "profiles" DROP COLUMN "dossier_enlistment_date";
ALTER TABLE "payload_locked_documents_rels" DROP COLUMN "roles_id";
DROP TYPE "public"."enum_users_preferences_display_lead_or_actual";
DROP TYPE "public"."enum_roles_permissions";
`)
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,510 @@
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
// Idempotent migration: safe to run on databases where push: true already applied
// the schema (dev), and on production databases where prior migrations already ran.
// Each DDL block is wrapped in exception handlers to skip already-existing objects.
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
// --- Enums ---
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum_game_servers_status" AS ENUM('online', 'offline');
EXCEPTION WHEN duplicate_object THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE TYPE "public"."enum_arma_commands_status" AS ENUM('queued', 'delivered', 'succeeded', 'failed');
EXCEPTION WHEN duplicate_object THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
ALTER TYPE "public"."enum_roles_permissions" ADD VALUE 'campaigns:manage' BEFORE 'factions:create';
EXCEPTION WHEN duplicate_object THEN null;
END $$;
`)
// --- Tables ---
await db.execute(sql`
DO $$ BEGIN
CREATE TABLE "profiles_progression_awards" (
"_order" integer NOT NULL,
"_parent_id" integer NOT NULL,
"id" varchar PRIMARY KEY NOT NULL,
"award_id" integer NOT NULL,
"awarded_by_id" integer,
"awarded_at" timestamp(3) with time zone NOT NULL,
"award_reason" varchar
);
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE TABLE "game_servers" (
"id" serial PRIMARY KEY NOT NULL,
"server_id" varchar NOT NULL,
"name" varchar NOT NULL,
"status" "enum_game_servers_status" DEFAULT 'offline' NOT NULL,
"last_seen_at" timestamp(3) with time zone,
"metadata" jsonb,
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
);
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE TABLE "arma_sync_events" (
"id" serial PRIMARY KEY NOT NULL,
"event_id" varchar NOT NULL,
"server_id" integer NOT NULL,
"type" varchar NOT NULL,
"occurred_at" timestamp(3) with time zone NOT NULL,
"payload" jsonb NOT NULL,
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
);
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE TABLE "arma_commands" (
"id" serial PRIMARY KEY NOT NULL,
"command_id" varchar NOT NULL,
"server_id" integer NOT NULL,
"type" varchar NOT NULL,
"payload" jsonb NOT NULL,
"status" "enum_arma_commands_status" DEFAULT 'queued' NOT NULL,
"result" jsonb,
"error" varchar,
"delivered_at" timestamp(3) with time zone,
"completed_at" timestamp(3) with time zone,
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
);
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE TABLE "payload_mcp_api_keys" (
"id" serial PRIMARY KEY NOT NULL,
"user_id" integer NOT NULL,
"label" varchar,
"description" varchar,
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
"enable_a_p_i_key" boolean,
"api_key" varchar,
"api_key_index" varchar
);
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
// --- Drop existing constraint/index/column (may already be dropped by prior migrations) ---
await db.execute(sql`
ALTER TABLE "profiles_rels" DROP CONSTRAINT IF EXISTS "profiles_rels_awards_fk";
`)
await db.execute(sql`
DROP INDEX IF EXISTS "profiles_rels_awards_id_idx";
`)
// --- Columns ---
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "users" ADD COLUMN "preferences_display_xp_display_mini" boolean DEFAULT false;
EXCEPTION WHEN duplicate_column THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "missions_mission_objectives" ADD COLUMN "redacted" boolean DEFAULT false;
EXCEPTION WHEN duplicate_column THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "missions" ADD COLUMN "discord_attendance_sent_at" timestamp(3) with time zone;
EXCEPTION WHEN duplicate_column THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "campaigns" ADD COLUMN "owner_id" integer;
EXCEPTION WHEN duplicate_column THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "payload_locked_documents_rels" ADD COLUMN "game_servers_id" integer;
EXCEPTION WHEN duplicate_column THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "payload_locked_documents_rels" ADD COLUMN "arma_sync_events_id" integer;
EXCEPTION WHEN duplicate_column THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "payload_locked_documents_rels" ADD COLUMN "arma_commands_id" integer;
EXCEPTION WHEN duplicate_column THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "payload_locked_documents_rels" ADD COLUMN "payload_mcp_api_keys_id" integer;
EXCEPTION WHEN duplicate_column THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "payload_preferences_rels" ADD COLUMN "payload_mcp_api_keys_id" integer;
EXCEPTION WHEN duplicate_column THEN null;
END $$;
`)
// --- Foreign keys (profiles_progression_awards) ---
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "profiles_progression_awards" ADD CONSTRAINT "profiles_progression_awards_award_id_awards_id_fk" FOREIGN KEY ("award_id") REFERENCES "public"."awards"("id") ON DELETE set null ON UPDATE no action;
EXCEPTION WHEN duplicate_object THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "profiles_progression_awards" ADD CONSTRAINT "profiles_progression_awards_awarded_by_id_profiles_id_fk" FOREIGN KEY ("awarded_by_id") REFERENCES "public"."profiles"("id") ON DELETE set null ON UPDATE no action;
EXCEPTION WHEN duplicate_object THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "profiles_progression_awards" ADD CONSTRAINT "profiles_progression_awards_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."profiles"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION WHEN duplicate_object THEN null;
END $$;
`)
// --- Foreign keys (arma tables) ---
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "arma_sync_events" ADD CONSTRAINT "arma_sync_events_server_id_game_servers_id_fk" FOREIGN KEY ("server_id") REFERENCES "public"."game_servers"("id") ON DELETE set null ON UPDATE no action;
EXCEPTION WHEN duplicate_object THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "arma_commands" ADD CONSTRAINT "arma_commands_server_id_game_servers_id_fk" FOREIGN KEY ("server_id") REFERENCES "public"."game_servers"("id") ON DELETE set null ON UPDATE no action;
EXCEPTION WHEN duplicate_object THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "payload_mcp_api_keys" ADD CONSTRAINT "payload_mcp_api_keys_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;
EXCEPTION WHEN duplicate_object THEN null;
END $$;
`)
// --- Indexes (profiles_progression_awards) ---
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "profiles_progression_awards_order_idx" ON "profiles_progression_awards" USING btree ("_order");
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "profiles_progression_awards_parent_id_idx" ON "profiles_progression_awards" USING btree ("_parent_id");
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "profiles_progression_awards_award_idx" ON "profiles_progression_awards" USING btree ("award_id");
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "profiles_progression_awards_awarded_by_idx" ON "profiles_progression_awards" USING btree ("awarded_by_id");
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
// --- Indexes (game_servers) ---
await db.execute(sql`
DO $$ BEGIN
CREATE UNIQUE INDEX "game_servers_server_id_idx" ON "game_servers" USING btree ("server_id");
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "game_servers_updated_at_idx" ON "game_servers" USING btree ("updated_at");
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "game_servers_created_at_idx" ON "game_servers" USING btree ("created_at");
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
// --- Indexes (arma_sync_events) ---
await db.execute(sql`
DO $$ BEGIN
CREATE UNIQUE INDEX "arma_sync_events_event_id_idx" ON "arma_sync_events" USING btree ("event_id");
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "arma_sync_events_server_idx" ON "arma_sync_events" USING btree ("server_id");
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "arma_sync_events_type_idx" ON "arma_sync_events" USING btree ("type");
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "arma_sync_events_updated_at_idx" ON "arma_sync_events" USING btree ("updated_at");
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "arma_sync_events_created_at_idx" ON "arma_sync_events" USING btree ("created_at");
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
// --- Indexes (arma_commands) ---
await db.execute(sql`
DO $$ BEGIN
CREATE UNIQUE INDEX "arma_commands_command_id_idx" ON "arma_commands" USING btree ("command_id");
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "arma_commands_server_idx" ON "arma_commands" USING btree ("server_id");
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "arma_commands_updated_at_idx" ON "arma_commands" USING btree ("updated_at");
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "arma_commands_created_at_idx" ON "arma_commands" USING btree ("created_at");
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
// --- Indexes (payload_mcp_api_keys) ---
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "payload_mcp_api_keys_user_idx" ON "payload_mcp_api_keys" USING btree ("user_id");
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "payload_mcp_api_keys_updated_at_idx" ON "payload_mcp_api_keys" USING btree ("updated_at");
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "payload_mcp_api_keys_created_at_idx" ON "payload_mcp_api_keys" USING btree ("created_at");
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
// --- Foreign keys (campaigns) ---
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "campaigns" ADD CONSTRAINT "campaigns_owner_id_users_id_fk" FOREIGN KEY ("owner_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;
EXCEPTION WHEN duplicate_object THEN null;
END $$;
`)
// --- Foreign keys (payload locked/preferences rels) ---
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_game_servers_fk" FOREIGN KEY ("game_servers_id") REFERENCES "public"."game_servers"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION WHEN duplicate_object THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_arma_sync_events_fk" FOREIGN KEY ("arma_sync_events_id") REFERENCES "public"."arma_sync_events"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION WHEN duplicate_object THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_arma_commands_fk" FOREIGN KEY ("arma_commands_id") REFERENCES "public"."arma_commands"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION WHEN duplicate_object THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_payload_mcp_api_keys_fk" FOREIGN KEY ("payload_mcp_api_keys_id") REFERENCES "public"."payload_mcp_api_keys"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION WHEN duplicate_object THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
ALTER TABLE "payload_preferences_rels" ADD CONSTRAINT "payload_preferences_rels_payload_mcp_api_keys_fk" FOREIGN KEY ("payload_mcp_api_keys_id") REFERENCES "public"."payload_mcp_api_keys"("id") ON DELETE cascade ON UPDATE no action;
EXCEPTION WHEN duplicate_object THEN null;
END $$;
`)
// --- Remaining indexes ---
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "campaigns_owner_idx" ON "campaigns" USING btree ("owner_id");
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "payload_locked_documents_rels_game_servers_id_idx" ON "payload_locked_documents_rels" USING btree ("game_servers_id");
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "payload_locked_documents_rels_arma_sync_events_id_idx" ON "payload_locked_documents_rels" USING btree ("arma_sync_events_id");
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "payload_locked_documents_rels_arma_commands_id_idx" ON "payload_locked_documents_rels" USING btree ("arma_commands_id");
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "payload_locked_documents_rels_payload_mcp_api_keys_id_idx" ON "payload_locked_documents_rels" USING btree ("payload_mcp_api_keys_id");
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
await db.execute(sql`
DO $$ BEGIN
CREATE INDEX "payload_preferences_rels_payload_mcp_api_keys_id_idx" ON "payload_preferences_rels" USING btree ("payload_mcp_api_keys_id");
EXCEPTION WHEN duplicate_table THEN null;
END $$;
`)
// --- Drop old column (may already be dropped by prior migrations) ---
await db.execute(sql`
ALTER TABLE "profiles_rels" DROP COLUMN IF EXISTS "awards_id";
`)
}
export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
await db.execute(sql`
ALTER TABLE "profiles_progression_awards" DISABLE ROW LEVEL SECURITY;
ALTER TABLE "game_servers" DISABLE ROW LEVEL SECURITY;
ALTER TABLE "arma_sync_events" DISABLE ROW LEVEL SECURITY;
ALTER TABLE "arma_commands" DISABLE ROW LEVEL SECURITY;
ALTER TABLE "payload_mcp_api_keys" DISABLE ROW LEVEL SECURITY;
DROP TABLE "profiles_progression_awards" CASCADE;
DROP TABLE "game_servers" CASCADE;
DROP TABLE "arma_sync_events" CASCADE;
DROP TABLE "arma_commands" CASCADE;
DROP TABLE "payload_mcp_api_keys" CASCADE;
ALTER TABLE "campaigns" DROP CONSTRAINT "campaigns_owner_id_users_id_fk";
ALTER TABLE "payload_locked_documents_rels" DROP CONSTRAINT "payload_locked_documents_rels_game_servers_fk";
ALTER TABLE "payload_locked_documents_rels" DROP CONSTRAINT "payload_locked_documents_rels_arma_sync_events_fk";
ALTER TABLE "payload_locked_documents_rels" DROP CONSTRAINT "payload_locked_documents_rels_arma_commands_fk";
ALTER TABLE "payload_locked_documents_rels" DROP CONSTRAINT "payload_locked_documents_rels_payload_mcp_api_keys_fk";
ALTER TABLE "payload_preferences_rels" DROP CONSTRAINT "payload_preferences_rels_payload_mcp_api_keys_fk";
ALTER TABLE "roles_permissions" ALTER COLUMN "value" SET DATA TYPE text;
DROP TYPE "public"."enum_roles_permissions";
CREATE TYPE "public"."enum_roles_permissions" AS ENUM('system:admin-access', 'users:create', 'users:read', 'users:update', 'users:delete', 'users:unlock', 'users:assign-roles', 'ranks:create', 'ranks:read', 'ranks:update', 'ranks:delete', 'profiles:create', 'profiles:read', 'profiles:update', 'profiles:delete', 'awards:create', 'awards:read', 'awards:update', 'awards:delete', 'qualifications:create', 'qualifications:read', 'qualifications:update', 'qualifications:delete', 'assignments:create', 'assignments:read', 'assignments:update', 'assignments:delete', 'experience:create', 'experience:read', 'experience:update', 'experience:delete', 'user-notifications:create', 'user-notifications:read', 'user-notifications:update', 'user-notifications:delete', 'missions:create', 'missions:read', 'missions:update', 'missions:delete', 'missions:read-sensitive', 'mission-attendances:create', 'mission-attendances:read', 'mission-attendances:update', 'mission-attendances:delete', 'campaigns:create', 'campaigns:read', 'campaigns:update', 'campaigns:delete', 'factions:create', 'factions:read', 'factions:update', 'factions:delete', 'technologies:create', 'technologies:read', 'technologies:update', 'technologies:delete', 'assets:create', 'assets:read', 'assets:update', 'assets:delete', 'resources:create', 'resources:read', 'resources:update', 'resources:delete', 'vehicles:create', 'vehicles:read', 'vehicles:update', 'vehicles:delete', 'structures:create', 'structures:read', 'structures:update', 'structures:delete', 'shipments:create', 'shipments:read', 'shipments:update', 'shipments:delete', 'bank-accounts:create', 'bank-accounts:read', 'bank-accounts:update', 'bank-accounts:delete', 'bank-transactions:create', 'bank-transactions:read', 'bank-transactions:update', 'bank-transactions:delete', 'ledger-entries:create', 'ledger-entries:read', 'ledger-entries:update', 'ledger-entries:delete', 'locker-storages:create', 'locker-storages:read', 'locker-storages:update', 'locker-storages:delete', 'game-structures:create', 'game-structures:read', 'game-structures:update', 'game-structures:delete', 'game-hard-resources:create', 'game-hard-resources:read', 'game-hard-resources:update', 'game-hard-resources:delete', 'game-event-logs:create', 'game-event-logs:read', 'game-event-logs:update', 'game-event-logs:delete', 'game-vehicles:create', 'game-vehicles:read', 'game-vehicles:update', 'game-vehicles:delete', 'game-npcs:create', 'game-npcs:read', 'game-npcs:update', 'game-npcs:delete', 'mission-files:create', 'mission-files:read', 'mission-files:update', 'mission-files:delete', 'mod-lists:create', 'mod-lists:read', 'mod-lists:update', 'mod-lists:delete', 'maps:create', 'maps:read', 'maps:update', 'maps:delete', 'narrative-events:create', 'narrative-events:read', 'narrative-events:update', 'narrative-events:delete', 'market-listings:create', 'market-listings:read', 'market-listings:update', 'market-listings:delete', 'market-negotiations:create', 'market-negotiations:read', 'market-negotiations:update', 'market-negotiations:delete', 'game-rules:create', 'game-rules:read', 'game-rules:update', 'game-rules:delete');
ALTER TABLE "roles_permissions" ALTER COLUMN "value" SET DATA TYPE "public"."enum_roles_permissions" USING "value"::"public"."enum_roles_permissions";
DROP INDEX "campaigns_owner_idx";
DROP INDEX "payload_locked_documents_rels_game_servers_id_idx";
DROP INDEX "payload_locked_documents_rels_arma_sync_events_id_idx";
DROP INDEX "payload_locked_documents_rels_arma_commands_id_idx";
DROP INDEX "payload_locked_documents_rels_payload_mcp_api_keys_id_idx";
DROP INDEX "payload_preferences_rels_payload_mcp_api_keys_id_idx";
ALTER TABLE "profiles_rels" ADD COLUMN "awards_id" integer;
ALTER TABLE "profiles_rels" ADD CONSTRAINT "profiles_rels_awards_fk" FOREIGN KEY ("awards_id") REFERENCES "public"."awards"("id") ON DELETE cascade ON UPDATE no action;
CREATE INDEX "profiles_rels_awards_id_idx" ON "profiles_rels" USING btree ("awards_id");
ALTER TABLE "users" DROP COLUMN "preferences_display_xp_display_mini";
ALTER TABLE "missions_mission_objectives" DROP COLUMN "redacted";
ALTER TABLE "missions" DROP COLUMN "discord_attendance_sent_at";
ALTER TABLE "campaigns" DROP COLUMN "owner_id";
ALTER TABLE "payload_locked_documents_rels" DROP COLUMN "game_servers_id";
ALTER TABLE "payload_locked_documents_rels" DROP COLUMN "arma_sync_events_id";
ALTER TABLE "payload_locked_documents_rels" DROP COLUMN "arma_commands_id";
ALTER TABLE "payload_locked_documents_rels" DROP COLUMN "payload_mcp_api_keys_id";
ALTER TABLE "payload_preferences_rels" DROP COLUMN "payload_mcp_api_keys_id";
DROP TYPE "public"."enum_game_servers_status";
DROP TYPE "public"."enum_arma_commands_status";`)
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,28 +1,21 @@
import * as migration_20260818_024510 from './_archived/20260818_024510'; import * as migration_20260820_004157 from "./20260820_004157";
import * as migration_20260820_004157 from './20260820_004157'; import * as migration_20260825_220000 from "./20260825_220000";
import * as migration_20260821_043007 from './20260821_043007'; import * as migration_20260826_050000_repair_shims_auxiliary_tables from "./20260826_050000_repair_shims_auxiliary_tables";
import * as migration_20260821_060000 from './20260821_060000';
import * as migration_20260821_120000 from './20260821_120000';
export const migrations = [ export const migrations = [
{ {
up: migration_20260820_004157.up, up: migration_20260820_004157.up,
down: migration_20260820_004157.down, down: migration_20260820_004157.down,
name: '20260820_004157', name: "20260820_004157",
}, },
{ {
up: migration_20260821_043007.up, up: migration_20260825_220000.up,
down: migration_20260821_043007.down, down: migration_20260825_220000.down,
name: '20260821_043007' name: "20260825_220000",
}, },
{ {
up: migration_20260821_060000.up, up: migration_20260826_050000_repair_shims_auxiliary_tables.up,
down: migration_20260821_060000.down, down: migration_20260826_050000_repair_shims_auxiliary_tables.down,
name: '20260821_060000' name: "20260826_050000_repair_shims_auxiliary_tables",
},
{
up: migration_20260821_120000.up,
down: migration_20260821_120000.down,
name: '20260821_120000'
}, },
]; ];

View file

@ -264,7 +264,8 @@ export default buildConfig({
pool: { pool: {
connectionString: process.env.DATABASE_URI || "", connectionString: process.env.DATABASE_URI || "",
}, },
push: process.env.NODE_ENV === "development", // push: process.env.NODE_ENV === "development",
push: false,
prodMigrations: migrations, prodMigrations: migrations,
}), }),
sharp, sharp,