feat: schema for finalist confirmation flow + per-category quotas

Adds 4 new models for grand-finale logistics PR 1:
- FinalistSlotQuota: per-category mutable quotas
- WaitlistEntry: ranked per-category waitlist
- FinalistConfirmation: token-gated confirmation lifecycle (PENDING /
  CONFIRMED / DECLINED / EXPIRED / SUPERSEDED) with optional decline reason
- AttendingMember: who from each team is attending, with visa flag

Plus Program.defaultAttendeeCap (default 3) for the per-edition team
attendance cap.

Migration is purely additive: no DROP/ALTER COLUMN/RENAME on existing
schema. All FKs ON DELETE CASCADE only fire on parent deletion.
This commit is contained in:
Matt
2026-04-28 17:49:26 +02:00
parent d0058b46ed
commit dff18b17f7
2 changed files with 219 additions and 0 deletions

View File

@@ -0,0 +1,119 @@
-- CreateEnum
CREATE TYPE "WaitlistEntryStatus" AS ENUM ('WAITING', 'PROMOTED', 'USED');
-- CreateEnum
CREATE TYPE "FinalistConfirmationStatus" AS ENUM ('PENDING', 'CONFIRMED', 'DECLINED', 'EXPIRED', 'SUPERSEDED');
-- AlterTable
ALTER TABLE "Program" ADD COLUMN "defaultAttendeeCap" INTEGER NOT NULL DEFAULT 3;
-- CreateTable
CREATE TABLE "FinalistSlotQuota" (
"id" TEXT NOT NULL,
"programId" TEXT NOT NULL,
"category" "CompetitionCategory" NOT NULL,
"quota" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "FinalistSlotQuota_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "WaitlistEntry" (
"id" TEXT NOT NULL,
"programId" TEXT NOT NULL,
"projectId" TEXT NOT NULL,
"category" "CompetitionCategory" NOT NULL,
"rank" INTEGER NOT NULL,
"status" "WaitlistEntryStatus" NOT NULL DEFAULT 'WAITING',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "WaitlistEntry_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "FinalistConfirmation" (
"id" TEXT NOT NULL,
"projectId" TEXT NOT NULL,
"category" "CompetitionCategory" NOT NULL,
"status" "FinalistConfirmationStatus" NOT NULL DEFAULT 'PENDING',
"deadline" TIMESTAMP(3) NOT NULL,
"token" TEXT NOT NULL,
"confirmedAt" TIMESTAMP(3),
"declinedAt" TIMESTAMP(3),
"declineReason" TEXT,
"expiredAt" TIMESTAMP(3),
"promotedFromWaitlistEntryId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "FinalistConfirmation_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "AttendingMember" (
"id" TEXT NOT NULL,
"confirmationId" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"needsVisa" BOOLEAN NOT NULL DEFAULT false,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "AttendingMember_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "FinalistSlotQuota_programId_idx" ON "FinalistSlotQuota"("programId");
-- CreateIndex
CREATE UNIQUE INDEX "FinalistSlotQuota_programId_category_key" ON "FinalistSlotQuota"("programId", "category");
-- CreateIndex
CREATE UNIQUE INDEX "WaitlistEntry_projectId_key" ON "WaitlistEntry"("projectId");
-- CreateIndex
CREATE INDEX "WaitlistEntry_programId_category_status_idx" ON "WaitlistEntry"("programId", "category", "status");
-- CreateIndex
CREATE UNIQUE INDEX "WaitlistEntry_programId_category_rank_key" ON "WaitlistEntry"("programId", "category", "rank");
-- CreateIndex
CREATE UNIQUE INDEX "FinalistConfirmation_projectId_key" ON "FinalistConfirmation"("projectId");
-- CreateIndex
CREATE UNIQUE INDEX "FinalistConfirmation_token_key" ON "FinalistConfirmation"("token");
-- CreateIndex
CREATE UNIQUE INDEX "FinalistConfirmation_promotedFromWaitlistEntryId_key" ON "FinalistConfirmation"("promotedFromWaitlistEntryId");
-- CreateIndex
CREATE INDEX "FinalistConfirmation_status_deadline_idx" ON "FinalistConfirmation"("status", "deadline");
-- CreateIndex
CREATE INDEX "FinalistConfirmation_category_status_idx" ON "FinalistConfirmation"("category", "status");
-- CreateIndex
CREATE INDEX "AttendingMember_userId_idx" ON "AttendingMember"("userId");
-- CreateIndex
CREATE UNIQUE INDEX "AttendingMember_confirmationId_userId_key" ON "AttendingMember"("confirmationId", "userId");
-- AddForeignKey
ALTER TABLE "FinalistSlotQuota" ADD CONSTRAINT "FinalistSlotQuota_programId_fkey" FOREIGN KEY ("programId") REFERENCES "Program"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "WaitlistEntry" ADD CONSTRAINT "WaitlistEntry_programId_fkey" FOREIGN KEY ("programId") REFERENCES "Program"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "WaitlistEntry" ADD CONSTRAINT "WaitlistEntry_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "FinalistConfirmation" ADD CONSTRAINT "FinalistConfirmation_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "AttendingMember" ADD CONSTRAINT "AttendingMember_confirmationId_fkey" FOREIGN KEY ("confirmationId") REFERENCES "FinalistConfirmation"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "AttendingMember" ADD CONSTRAINT "AttendingMember_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;