From 1dfbb58daef92f559bb5886951562ea7829708a9 Mon Sep 17 00:00:00 2001 From: Z8MB1E Date: Fri, 21 Aug 2026 15:38:54 -0400 Subject: [PATCH] fix(migrations): migrate profile awards to array storage --- src/migrations/20260821_120000.ts | 122 ++++++++++++++++++++++++++++++ src/migrations/index.ts | 6 ++ 2 files changed, 128 insertions(+) create mode 100644 src/migrations/20260821_120000.ts diff --git a/src/migrations/20260821_120000.ts b/src/migrations/20260821_120000.ts new file mode 100644 index 0000000..4506672 --- /dev/null +++ b/src/migrations/20260821_120000.ts @@ -0,0 +1,122 @@ +import { MigrateDownArgs, MigrateUpArgs, sql } from "@payloadcms/db-postgres"; + +const awardsTable = "profiles_progression_awards"; + +export async function up({ db }: MigrateUpArgs): Promise { + await db.execute(sql` + CREATE TABLE IF NOT EXISTS "${sql.raw(awardsTable)}" ( + "_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 + ); + `); + + await db.execute(sql` + DO $$ BEGIN + ALTER TABLE "${sql.raw(awardsTable)}" + 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 $$; + `); + + await db.execute(sql` + DO $$ BEGIN + ALTER TABLE "${sql.raw(awardsTable)}" + ADD CONSTRAINT "profiles_progression_awards_award_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 "${sql.raw(awardsTable)}" + ADD CONSTRAINT "profiles_progression_awards_awarded_by_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` + CREATE INDEX IF NOT EXISTS "profiles_progression_awards_order_idx" + ON "${sql.raw(awardsTable)}" USING btree ("_order"); + CREATE INDEX IF NOT EXISTS "profiles_progression_awards_parent_id_idx" + ON "${sql.raw(awardsTable)}" USING btree ("_parent_id"); + CREATE INDEX IF NOT EXISTS "profiles_progression_awards_award_id_idx" + ON "${sql.raw(awardsTable)}" USING btree ("award_id"); + CREATE INDEX IF NOT EXISTS "profiles_progression_awards_awarded_by_id_idx" + ON "${sql.raw(awardsTable)}" USING btree ("awarded_by_id"); + `); + + // The old relationship did not record award metadata. Preserve its award, + // profile, and ordering; use the profile creation date as the best available + // timestamp for these historical entries. + await db.execute(sql` + INSERT INTO "${sql.raw(awardsTable)}" + ("_order", "_parent_id", "id", "award_id", "awarded_at") + SELECT + ROW_NUMBER() OVER ( + PARTITION BY rels."parent_id" + ORDER BY rels."order" NULLS LAST, rels."id" + ) - 1, + rels."parent_id", + md5('profile-award:' || rels."id"), + rels."awards_id", + profiles."created_at" + FROM "profiles_rels" rels + INNER JOIN "profiles" profiles ON profiles."id" = rels."parent_id" + WHERE rels."awards_id" IS NOT NULL + AND NOT EXISTS ( + SELECT 1 + FROM "${sql.raw(awardsTable)}" existing + WHERE existing."id" = md5('profile-award:' || rels."id") + ); + `); + + await db.execute(sql` + ALTER TABLE "profiles_rels" + DROP CONSTRAINT IF EXISTS "profiles_rels_awards_fk"; + DROP INDEX IF EXISTS "profiles_rels_awards_id_idx"; + ALTER TABLE "profiles_rels" DROP COLUMN IF EXISTS "awards_id"; + `); +} + +export async function down({ db }: MigrateDownArgs): Promise { + await db.execute(sql` + ALTER TABLE "profiles_rels" ADD COLUMN IF NOT EXISTS "awards_id" integer; + `); + + await db.execute(sql` + INSERT INTO "profiles_rels" ("order", "parent_id", "path", "awards_id") + SELECT awards."_order", awards."_parent_id", 'progression.awards', awards."award_id" + FROM "${sql.raw(awardsTable)}" awards + WHERE NOT EXISTS ( + SELECT 1 + FROM "profiles_rels" rels + WHERE rels."parent_id" = awards."_parent_id" + AND rels."awards_id" = awards."award_id" + AND rels."path" = 'progression.awards' + ); + `); + + await db.execute(sql` + DO $$ BEGIN + 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; + EXCEPTION WHEN duplicate_object THEN null; + END $$; + CREATE INDEX IF NOT EXISTS "profiles_rels_awards_id_idx" + ON "profiles_rels" USING btree ("awards_id"); + DROP TABLE IF EXISTS "${sql.raw(awardsTable)}"; + `); +} diff --git a/src/migrations/index.ts b/src/migrations/index.ts index 3cc036a..7837bbc 100644 --- a/src/migrations/index.ts +++ b/src/migrations/index.ts @@ -2,6 +2,7 @@ import * as migration_20260818_024510 from './_archived/20260818_024510'; import * as migration_20260820_004157 from './20260820_004157'; import * as migration_20260821_043007 from './20260821_043007'; import * as migration_20260821_060000 from './20260821_060000'; +import * as migration_20260821_120000 from './20260821_120000'; export const migrations = [ { @@ -19,4 +20,9 @@ export const migrations = [ down: migration_20260821_060000.down, name: '20260821_060000' }, + { + up: migration_20260821_120000.up, + down: migration_20260821_120000.down, + name: '20260821_120000' + }, ];