-- Universal Apply Page: Make Project.roundId nullable and add programId FK -- This migration enables projects to be submitted to a program/edition without being assigned to a specific round -- Step 1: Add Program.slug for edition-wide apply URLs (nullable for existing programs) ALTER TABLE "Program" ADD COLUMN "slug" TEXT; CREATE UNIQUE INDEX "Program_slug_key" ON "Program"("slug"); -- Step 2: Add programId column (nullable initially to handle existing data) ALTER TABLE "Project" ADD COLUMN "programId" TEXT; -- Step 3: Backfill programId from existing round relationships -- Every project currently has a roundId, so we can populate programId from Round.programId UPDATE "Project" p SET "programId" = r."programId" FROM "Round" r WHERE p."roundId" = r.id; -- Step 4: Verify backfill succeeded (should be 0 rows with NULL programId) -- If this fails, manual intervention is needed DO $$ DECLARE null_count INTEGER; BEGIN SELECT COUNT(*) INTO null_count FROM "Project" WHERE "programId" IS NULL; IF null_count > 0 THEN RAISE EXCEPTION 'Migration failed: % projects have NULL programId after backfill', null_count; END IF; END $$; -- Step 5: Make programId required (NOT NULL constraint) ALTER TABLE "Project" ALTER COLUMN "programId" SET NOT NULL; -- Step 6: Add foreign key constraint for programId ALTER TABLE "Project" ADD CONSTRAINT "Project_programId_fkey" FOREIGN KEY ("programId") REFERENCES "Program"("id") ON DELETE CASCADE; -- Step 7: Make roundId nullable (allow projects without round assignment) ALTER TABLE "Project" ALTER COLUMN "roundId" DROP NOT NULL; -- Step 8: Update round FK to SET NULL on delete (instead of CASCADE) -- Projects should remain in the database if their round is deleted ALTER TABLE "Project" DROP CONSTRAINT "Project_roundId_fkey"; ALTER TABLE "Project" ADD CONSTRAINT "Project_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE SET NULL; -- Step 9: Add performance indexes -- Index for filtering unassigned projects (WHERE roundId IS NULL) CREATE INDEX "Project_programId_idx" ON "Project"("programId"); CREATE INDEX "Project_programId_roundId_idx" ON "Project"("programId", "roundId"); -- Note: The existing "Project_roundId_idx" remains for queries filtering by round