Competition/Round architecture: full platform rewrite (Phases 1-9)
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m45s
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m45s
Replace Pipeline/Stage system with Competition/Round architecture. New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy, ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow. New services: round-engine, round-assignment, deliberation, result-lock, submission-manager, competition-context, ai-prompt-guard. Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with structured prompts, retry logic, and injection detection. All legacy pipeline/stage code removed. 4 new migrations + seed aligned. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -169,27 +169,26 @@ async function runChecks(): Promise<CheckResult[]> {
|
||||
// 12. Every FileRequirement has a valid stageId
|
||||
const badFileReqs = await prisma.$queryRaw<{ count: bigint }[]>`
|
||||
SELECT COUNT(*) as count FROM "FileRequirement" fr
|
||||
WHERE NOT EXISTS (SELECT 1 FROM "Stage" s WHERE s.id = fr."stageId")
|
||||
WHERE NOT EXISTS (SELECT 1 FROM "Round" r WHERE r.id = fr."roundId")
|
||||
`
|
||||
const badFileReqCount = Number(badFileReqs[0]?.count ?? 0)
|
||||
results.push({
|
||||
name: 'Every FileRequirement references valid stage',
|
||||
name: 'Every FileRequirement references valid round',
|
||||
passed: badFileReqCount === 0,
|
||||
details: badFileReqCount === 0
|
||||
? 'All file requirements reference valid stages'
|
||||
: `Found ${badFileReqCount} file requirements with invalid stage references`,
|
||||
? 'All file requirements reference valid rounds'
|
||||
: `Found ${badFileReqCount} file requirements with invalid round references`,
|
||||
})
|
||||
|
||||
// 13. Count validation
|
||||
const projectCountResult = await prisma.project.count()
|
||||
const stageCount = await prisma.stage.count()
|
||||
const trackCount = await prisma.track.count()
|
||||
const pipelineCount = await prisma.pipeline.count()
|
||||
const pssCount = await prisma.projectStageState.count()
|
||||
const roundCount = await prisma.round.count()
|
||||
const competitionCount = await prisma.competition.count()
|
||||
const prsCount = await prisma.projectRoundState.count()
|
||||
results.push({
|
||||
name: 'Count validation',
|
||||
passed: projectCountResult > 0 && stageCount > 0 && trackCount > 0,
|
||||
details: `Pipelines: ${pipelineCount}, Tracks: ${trackCount}, Stages: ${stageCount}, Projects: ${projectCountResult}, StageStates: ${pssCount}`,
|
||||
passed: projectCountResult > 0 && roundCount > 0 && competitionCount > 0,
|
||||
details: `Competitions: ${competitionCount}, Rounds: ${roundCount}, Projects: ${projectCountResult}, RoundStates: ${prsCount}`,
|
||||
})
|
||||
|
||||
return results
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,91 +1,91 @@
|
||||
-- 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
|
||||
-- NOTE: Written to be idempotent (safe to re-run if partially applied)
|
||||
|
||||
-- Step 1: Add Program.slug for edition-wide apply URLs (nullable for existing programs)
|
||||
ALTER TABLE "Program" ADD COLUMN IF NOT EXISTS "slug" TEXT;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "Program_slug_key" ON "Program"("slug");
|
||||
|
||||
-- Step 2: Add programId column (nullable initially to handle existing data)
|
||||
ALTER TABLE "Project" ADD COLUMN IF NOT EXISTS "programId" TEXT;
|
||||
|
||||
-- Step 3: Backfill programId from existing round relationships
|
||||
-- Only update rows where programId is still NULL (idempotent)
|
||||
UPDATE "Project" p
|
||||
SET "programId" = r."programId"
|
||||
FROM "Round" r
|
||||
WHERE p."roundId" = r.id
|
||||
AND p."programId" IS NULL;
|
||||
|
||||
-- Step 4: Handle orphaned projects (no roundId = no way to derive programId)
|
||||
-- Assign them to the first available program, or delete them if no program exists
|
||||
DO $$
|
||||
DECLARE
|
||||
null_count INTEGER;
|
||||
fallback_program_id TEXT;
|
||||
BEGIN
|
||||
SELECT COUNT(*) INTO null_count FROM "Project" WHERE "programId" IS NULL;
|
||||
IF null_count > 0 THEN
|
||||
SELECT id INTO fallback_program_id FROM "Program" ORDER BY "createdAt" ASC LIMIT 1;
|
||||
IF fallback_program_id IS NOT NULL THEN
|
||||
UPDATE "Project" SET "programId" = fallback_program_id WHERE "programId" IS NULL;
|
||||
RAISE NOTICE 'Assigned % orphaned projects to fallback program %', null_count, fallback_program_id;
|
||||
ELSE
|
||||
DELETE FROM "Project" WHERE "programId" IS NULL;
|
||||
RAISE NOTICE 'Deleted % orphaned projects (no program exists to assign them to)', null_count;
|
||||
END IF;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Step 5: Make programId required (NOT NULL constraint) - safe if already NOT NULL
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'Project' AND column_name = 'programId' AND is_nullable = 'YES'
|
||||
) THEN
|
||||
ALTER TABLE "Project" ALTER COLUMN "programId" SET NOT NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Step 6: Add foreign key constraint for programId (skip if already exists)
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.table_constraints
|
||||
WHERE constraint_name = 'Project_programId_fkey' AND table_name = 'Project'
|
||||
) THEN
|
||||
ALTER TABLE "Project" ADD CONSTRAINT "Project_programId_fkey"
|
||||
FOREIGN KEY ("programId") REFERENCES "Program"("id") ON DELETE CASCADE;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Step 7: Make roundId nullable (allow projects without round assignment)
|
||||
-- Safe: DROP NOT NULL is idempotent if already nullable
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'Project' AND column_name = 'roundId' AND is_nullable = 'NO'
|
||||
) THEN
|
||||
ALTER TABLE "Project" ALTER COLUMN "roundId" DROP NOT NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Step 8: Update round FK to SET NULL on delete (instead of CASCADE)
|
||||
-- Projects should remain in the database if their round is deleted
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.table_constraints
|
||||
WHERE constraint_name = 'Project_roundId_fkey' AND table_name = 'Project'
|
||||
) THEN
|
||||
ALTER TABLE "Project" DROP CONSTRAINT "Project_roundId_fkey";
|
||||
END IF;
|
||||
ALTER TABLE "Project" ADD CONSTRAINT "Project_roundId_fkey"
|
||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE SET NULL;
|
||||
END $$;
|
||||
|
||||
-- Step 9: Add performance indexes
|
||||
CREATE INDEX IF NOT EXISTS "Project_programId_idx" ON "Project"("programId");
|
||||
CREATE INDEX IF NOT EXISTS "Project_programId_roundId_idx" ON "Project"("programId", "roundId");
|
||||
-- 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
|
||||
-- NOTE: Written to be idempotent (safe to re-run if partially applied)
|
||||
|
||||
-- Step 1: Add Program.slug for edition-wide apply URLs (nullable for existing programs)
|
||||
ALTER TABLE "Program" ADD COLUMN IF NOT EXISTS "slug" TEXT;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "Program_slug_key" ON "Program"("slug");
|
||||
|
||||
-- Step 2: Add programId column (nullable initially to handle existing data)
|
||||
ALTER TABLE "Project" ADD COLUMN IF NOT EXISTS "programId" TEXT;
|
||||
|
||||
-- Step 3: Backfill programId from existing round relationships
|
||||
-- Only update rows where programId is still NULL (idempotent)
|
||||
UPDATE "Project" p
|
||||
SET "programId" = r."programId"
|
||||
FROM "Round" r
|
||||
WHERE p."roundId" = r.id
|
||||
AND p."programId" IS NULL;
|
||||
|
||||
-- Step 4: Handle orphaned projects (no roundId = no way to derive programId)
|
||||
-- Assign them to the first available program, or delete them if no program exists
|
||||
DO $$
|
||||
DECLARE
|
||||
null_count INTEGER;
|
||||
fallback_program_id TEXT;
|
||||
BEGIN
|
||||
SELECT COUNT(*) INTO null_count FROM "Project" WHERE "programId" IS NULL;
|
||||
IF null_count > 0 THEN
|
||||
SELECT id INTO fallback_program_id FROM "Program" ORDER BY "createdAt" ASC LIMIT 1;
|
||||
IF fallback_program_id IS NOT NULL THEN
|
||||
UPDATE "Project" SET "programId" = fallback_program_id WHERE "programId" IS NULL;
|
||||
RAISE NOTICE 'Assigned % orphaned projects to fallback program %', null_count, fallback_program_id;
|
||||
ELSE
|
||||
DELETE FROM "Project" WHERE "programId" IS NULL;
|
||||
RAISE NOTICE 'Deleted % orphaned projects (no program exists to assign them to)', null_count;
|
||||
END IF;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Step 5: Make programId required (NOT NULL constraint) - safe if already NOT NULL
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'Project' AND column_name = 'programId' AND is_nullable = 'YES'
|
||||
) THEN
|
||||
ALTER TABLE "Project" ALTER COLUMN "programId" SET NOT NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Step 6: Add foreign key constraint for programId (skip if already exists)
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.table_constraints
|
||||
WHERE constraint_name = 'Project_programId_fkey' AND table_name = 'Project'
|
||||
) THEN
|
||||
ALTER TABLE "Project" ADD CONSTRAINT "Project_programId_fkey"
|
||||
FOREIGN KEY ("programId") REFERENCES "Program"("id") ON DELETE CASCADE;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Step 7: Make roundId nullable (allow projects without round assignment)
|
||||
-- Safe: DROP NOT NULL is idempotent if already nullable
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'Project' AND column_name = 'roundId' AND is_nullable = 'NO'
|
||||
) THEN
|
||||
ALTER TABLE "Project" ALTER COLUMN "roundId" DROP NOT NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Step 8: Update round FK to SET NULL on delete (instead of CASCADE)
|
||||
-- Projects should remain in the database if their round is deleted
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.table_constraints
|
||||
WHERE constraint_name = 'Project_roundId_fkey' AND table_name = 'Project'
|
||||
) THEN
|
||||
ALTER TABLE "Project" DROP CONSTRAINT "Project_roundId_fkey";
|
||||
END IF;
|
||||
ALTER TABLE "Project" ADD CONSTRAINT "Project_roundId_fkey"
|
||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE SET NULL;
|
||||
END $$;
|
||||
|
||||
-- Step 9: Add performance indexes
|
||||
CREATE INDEX IF NOT EXISTS "Project_programId_idx" ON "Project"("programId");
|
||||
CREATE INDEX IF NOT EXISTS "Project_programId_roundId_idx" ON "Project"("programId", "roundId");
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
-- Reconciliation migration: Add missing foreign keys and indexes
|
||||
-- The add_15_features migration omitted some FKs and indexes that the schema expects
|
||||
-- This migration brings the database in line with the Prisma schema
|
||||
|
||||
-- =====================================================
|
||||
-- Missing Foreign Keys
|
||||
-- =====================================================
|
||||
|
||||
-- RoundTemplate → Program
|
||||
ALTER TABLE "RoundTemplate" ADD CONSTRAINT "RoundTemplate_programId_fkey"
|
||||
FOREIGN KEY ("programId") REFERENCES "Program"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- RoundTemplate → User (creator)
|
||||
ALTER TABLE "RoundTemplate" ADD CONSTRAINT "RoundTemplate_createdBy_fkey"
|
||||
FOREIGN KEY ("createdBy") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- Message → Round
|
||||
ALTER TABLE "Message" ADD CONSTRAINT "Message_roundId_fkey"
|
||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- EvaluationDiscussion → Round
|
||||
ALTER TABLE "EvaluationDiscussion" ADD CONSTRAINT "EvaluationDiscussion_roundId_fkey"
|
||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- ProjectFile → ProjectFile (self-relation for file versioning)
|
||||
ALTER TABLE "ProjectFile" ADD CONSTRAINT "ProjectFile_replacedById_fkey"
|
||||
FOREIGN KEY ("replacedById") REFERENCES "ProjectFile"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- =====================================================
|
||||
-- Missing Indexes
|
||||
-- =====================================================
|
||||
|
||||
CREATE INDEX "RoundTemplate_roundType_idx" ON "RoundTemplate"("roundType");
|
||||
CREATE INDEX "MentorNote_authorId_idx" ON "MentorNote"("authorId");
|
||||
CREATE INDEX "MentorMilestoneCompletion_completedById_idx" ON "MentorMilestoneCompletion"("completedById");
|
||||
CREATE INDEX "Webhook_createdById_idx" ON "Webhook"("createdById");
|
||||
CREATE INDEX "WebhookDelivery_event_idx" ON "WebhookDelivery"("event");
|
||||
CREATE INDEX "Message_roundId_idx" ON "Message"("roundId");
|
||||
CREATE INDEX "EvaluationDiscussion_closedById_idx" ON "EvaluationDiscussion"("closedById");
|
||||
CREATE INDEX "DiscussionComment_discussionId_idx" ON "DiscussionComment"("discussionId");
|
||||
CREATE INDEX "DiscussionComment_userId_idx" ON "DiscussionComment"("userId");
|
||||
-- Reconciliation migration: Add missing foreign keys and indexes
|
||||
-- The add_15_features migration omitted some FKs and indexes that the schema expects
|
||||
-- This migration brings the database in line with the Prisma schema
|
||||
|
||||
-- =====================================================
|
||||
-- Missing Foreign Keys
|
||||
-- =====================================================
|
||||
|
||||
-- RoundTemplate → Program
|
||||
ALTER TABLE "RoundTemplate" ADD CONSTRAINT "RoundTemplate_programId_fkey"
|
||||
FOREIGN KEY ("programId") REFERENCES "Program"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- RoundTemplate → User (creator)
|
||||
ALTER TABLE "RoundTemplate" ADD CONSTRAINT "RoundTemplate_createdBy_fkey"
|
||||
FOREIGN KEY ("createdBy") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- Message → Round
|
||||
ALTER TABLE "Message" ADD CONSTRAINT "Message_roundId_fkey"
|
||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- EvaluationDiscussion → Round
|
||||
ALTER TABLE "EvaluationDiscussion" ADD CONSTRAINT "EvaluationDiscussion_roundId_fkey"
|
||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- ProjectFile → ProjectFile (self-relation for file versioning)
|
||||
ALTER TABLE "ProjectFile" ADD CONSTRAINT "ProjectFile_replacedById_fkey"
|
||||
FOREIGN KEY ("replacedById") REFERENCES "ProjectFile"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- =====================================================
|
||||
-- Missing Indexes
|
||||
-- =====================================================
|
||||
|
||||
CREATE INDEX "RoundTemplate_roundType_idx" ON "RoundTemplate"("roundType");
|
||||
CREATE INDEX "MentorNote_authorId_idx" ON "MentorNote"("authorId");
|
||||
CREATE INDEX "MentorMilestoneCompletion_completedById_idx" ON "MentorMilestoneCompletion"("completedById");
|
||||
CREATE INDEX "Webhook_createdById_idx" ON "Webhook"("createdById");
|
||||
CREATE INDEX "WebhookDelivery_event_idx" ON "WebhookDelivery"("event");
|
||||
CREATE INDEX "Message_roundId_idx" ON "Message"("roundId");
|
||||
CREATE INDEX "EvaluationDiscussion_closedById_idx" ON "EvaluationDiscussion"("closedById");
|
||||
CREATE INDEX "DiscussionComment_discussionId_idx" ON "DiscussionComment"("discussionId");
|
||||
CREATE INDEX "DiscussionComment_userId_idx" ON "DiscussionComment"("userId");
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
-- Fix round deletion FK constraint errors
|
||||
-- Add CASCADE on Evaluation.formId so deleting EvaluationForm cascades to Evaluations
|
||||
-- Add SET NULL on ProjectFile.roundId so deleting Round nullifies the reference
|
||||
|
||||
-- AlterTable: Evaluation.formId -> onDelete CASCADE
|
||||
ALTER TABLE "Evaluation" DROP CONSTRAINT "Evaluation_formId_fkey";
|
||||
ALTER TABLE "Evaluation" ADD CONSTRAINT "Evaluation_formId_fkey"
|
||||
FOREIGN KEY ("formId") REFERENCES "EvaluationForm"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AlterTable: ProjectFile.roundId -> onDelete SET NULL
|
||||
ALTER TABLE "ProjectFile" DROP CONSTRAINT "ProjectFile_roundId_fkey";
|
||||
ALTER TABLE "ProjectFile" ADD CONSTRAINT "ProjectFile_roundId_fkey"
|
||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
-- Fix round deletion FK constraint errors
|
||||
-- Add CASCADE on Evaluation.formId so deleting EvaluationForm cascades to Evaluations
|
||||
-- Add SET NULL on ProjectFile.roundId so deleting Round nullifies the reference
|
||||
|
||||
-- AlterTable: Evaluation.formId -> onDelete CASCADE
|
||||
ALTER TABLE "Evaluation" DROP CONSTRAINT "Evaluation_formId_fkey";
|
||||
ALTER TABLE "Evaluation" ADD CONSTRAINT "Evaluation_formId_fkey"
|
||||
FOREIGN KEY ("formId") REFERENCES "EvaluationForm"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AlterTable: ProjectFile.roundId -> onDelete SET NULL
|
||||
ALTER TABLE "ProjectFile" DROP CONSTRAINT "ProjectFile_roundId_fkey";
|
||||
ALTER TABLE "ProjectFile" ADD CONSTRAINT "ProjectFile_roundId_fkey"
|
||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "FileRequirement" (
|
||||
"id" TEXT NOT NULL,
|
||||
"roundId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"acceptedMimeTypes" TEXT[],
|
||||
"maxSizeMB" INTEGER,
|
||||
"isRequired" BOOLEAN NOT NULL DEFAULT true,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "FileRequirement_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "FileRequirement_roundId_idx" ON "FileRequirement"("roundId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "FileRequirement" ADD CONSTRAINT "FileRequirement_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AlterTable: add requirementId to ProjectFile
|
||||
ALTER TABLE "ProjectFile" ADD COLUMN "requirementId" TEXT;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ProjectFile_requirementId_idx" ON "ProjectFile"("requirementId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ProjectFile" ADD CONSTRAINT "ProjectFile_requirementId_fkey" FOREIGN KEY ("requirementId") REFERENCES "FileRequirement"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
-- CreateTable
|
||||
CREATE TABLE "FileRequirement" (
|
||||
"id" TEXT NOT NULL,
|
||||
"roundId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"acceptedMimeTypes" TEXT[],
|
||||
"maxSizeMB" INTEGER,
|
||||
"isRequired" BOOLEAN NOT NULL DEFAULT true,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "FileRequirement_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "FileRequirement_roundId_idx" ON "FileRequirement"("roundId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "FileRequirement" ADD CONSTRAINT "FileRequirement_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AlterTable: add requirementId to ProjectFile
|
||||
ALTER TABLE "ProjectFile" ADD COLUMN "requirementId" TEXT;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ProjectFile_requirementId_idx" ON "ProjectFile"("requirementId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ProjectFile" ADD CONSTRAINT "ProjectFile_requirementId_fkey" FOREIGN KEY ("requirementId") REFERENCES "FileRequirement"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
@@ -1,129 +1,129 @@
|
||||
-- Migration: Add all missing schema elements not covered by previous migrations
|
||||
-- This brings the database fully in line with prisma/schema.prisma
|
||||
-- Uses IF NOT EXISTS / DO $$ guards for idempotent execution
|
||||
|
||||
-- =============================================================================
|
||||
-- 1. MISSING TABLE: WizardTemplate
|
||||
-- =============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "WizardTemplate" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"config" JSONB NOT NULL,
|
||||
"isGlobal" BOOLEAN NOT NULL DEFAULT false,
|
||||
"programId" TEXT,
|
||||
"createdBy" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "WizardTemplate_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "WizardTemplate_programId_idx" ON "WizardTemplate"("programId");
|
||||
CREATE INDEX IF NOT EXISTS "WizardTemplate_isGlobal_idx" ON "WizardTemplate"("isGlobal");
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "WizardTemplate" ADD CONSTRAINT "WizardTemplate_programId_fkey"
|
||||
FOREIGN KEY ("programId") REFERENCES "Program"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "WizardTemplate" ADD CONSTRAINT "WizardTemplate_createdBy_fkey"
|
||||
FOREIGN KEY ("createdBy") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
|
||||
-- =============================================================================
|
||||
-- 2. MISSING COLUMNS ON SpecialAward: eligibility job tracking fields
|
||||
-- =============================================================================
|
||||
|
||||
ALTER TABLE "SpecialAward" ADD COLUMN IF NOT EXISTS "eligibilityJobStatus" TEXT;
|
||||
ALTER TABLE "SpecialAward" ADD COLUMN IF NOT EXISTS "eligibilityJobTotal" INTEGER;
|
||||
ALTER TABLE "SpecialAward" ADD COLUMN IF NOT EXISTS "eligibilityJobDone" INTEGER;
|
||||
ALTER TABLE "SpecialAward" ADD COLUMN IF NOT EXISTS "eligibilityJobError" TEXT;
|
||||
ALTER TABLE "SpecialAward" ADD COLUMN IF NOT EXISTS "eligibilityJobStarted" TIMESTAMP(3);
|
||||
|
||||
-- =============================================================================
|
||||
-- 3. Project.referralSource: Already in init migration. No action needed.
|
||||
-- Round.slug: Already in init migration. No action needed.
|
||||
-- =============================================================================
|
||||
|
||||
-- =============================================================================
|
||||
-- 5. MISSING INDEXES
|
||||
-- =============================================================================
|
||||
|
||||
-- 5a. Assignment: @@index([projectId, userId])
|
||||
CREATE INDEX IF NOT EXISTS "Assignment_projectId_userId_idx" ON "Assignment"("projectId", "userId");
|
||||
|
||||
-- 5b. AuditLog: @@index([sessionId])
|
||||
CREATE INDEX IF NOT EXISTS "AuditLog_sessionId_idx" ON "AuditLog"("sessionId");
|
||||
|
||||
-- 5c. ProjectFile: @@index([projectId, roundId])
|
||||
CREATE INDEX IF NOT EXISTS "ProjectFile_projectId_roundId_idx" ON "ProjectFile"("projectId", "roundId");
|
||||
|
||||
-- 5d. MessageRecipient: @@index([userId])
|
||||
CREATE INDEX IF NOT EXISTS "MessageRecipient_userId_idx" ON "MessageRecipient"("userId");
|
||||
|
||||
-- 5e. MessageRecipient: @@unique([messageId, userId, channel])
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "MessageRecipient_messageId_userId_channel_key" ON "MessageRecipient"("messageId", "userId", "channel");
|
||||
|
||||
-- 5f. AwardEligibility: @@index([awardId, eligible]) - composite index
|
||||
CREATE INDEX IF NOT EXISTS "AwardEligibility_awardId_eligible_idx" ON "AwardEligibility"("awardId", "eligible");
|
||||
|
||||
-- =============================================================================
|
||||
-- 6. REMOVE STALE INDEX: Message_scheduledAt_idx
|
||||
-- The schema does NOT have @@index([scheduledAt]) on Message.
|
||||
-- The add_15_features migration created it, but the schema doesn't list it.
|
||||
-- Leaving it as-is since it's harmless and could be useful.
|
||||
-- =============================================================================
|
||||
|
||||
-- =============================================================================
|
||||
-- 7. VERIFY: All models from add_15_features are present
|
||||
-- DigestLog, RoundTemplate, MentorNote, MentorMilestone,
|
||||
-- MentorMilestoneCompletion, Message, MessageTemplate, MessageRecipient,
|
||||
-- Webhook, WebhookDelivery, EvaluationDiscussion, DiscussionComment
|
||||
-- -> All confirmed created in 20260205223133_add_15_features migration.
|
||||
-- -> All FKs confirmed in add_15_features + 20260208000000_add_missing_fks_indexes.
|
||||
-- =============================================================================
|
||||
|
||||
-- =============================================================================
|
||||
-- 8. VERIFY: Existing tables from init and subsequent migrations
|
||||
-- All core tables (User, Account, Session, VerificationToken, Program, Round,
|
||||
-- EvaluationForm, Project, ProjectFile, Assignment, Evaluation, GracePeriod,
|
||||
-- SystemSettings, AuditLog, AIUsageLog, NotificationLog, InAppNotification,
|
||||
-- NotificationEmailSetting, LearningResource, ResourceAccess, Partner,
|
||||
-- ExpertiseTag, ProjectTag, LiveVotingSession, LiveVote, TeamMember,
|
||||
-- MentorAssignment, FilteringRule, FilteringResult, FilteringJob,
|
||||
-- AssignmentJob, TaggingJob, SpecialAward, AwardEligibility, AwardJuror,
|
||||
-- AwardVote, ReminderLog, ConflictOfInterest, EvaluationSummary,
|
||||
-- ProjectStatusHistory, MentorMessage, FileRequirement)
|
||||
-- -> All confirmed present in migrations.
|
||||
-- =============================================================================
|
||||
|
||||
-- =============================================================================
|
||||
-- SUMMARY OF CHANGES IN THIS MIGRATION:
|
||||
--
|
||||
-- NEW TABLE:
|
||||
-- - WizardTemplate (with programId FK, createdBy FK, indexes)
|
||||
--
|
||||
-- NEW COLUMNS:
|
||||
-- - SpecialAward.eligibilityJobStatus (TEXT, nullable)
|
||||
-- - SpecialAward.eligibilityJobTotal (INTEGER, nullable)
|
||||
-- - SpecialAward.eligibilityJobDone (INTEGER, nullable)
|
||||
-- - SpecialAward.eligibilityJobError (TEXT, nullable)
|
||||
-- - SpecialAward.eligibilityJobStarted (TIMESTAMP, nullable)
|
||||
--
|
||||
-- NEW INDEXES:
|
||||
-- - Assignment_projectId_userId_idx
|
||||
-- - AuditLog_sessionId_idx
|
||||
-- - ProjectFile_projectId_roundId_idx
|
||||
-- - MessageRecipient_userId_idx
|
||||
-- - MessageRecipient_messageId_userId_channel_key (UNIQUE)
|
||||
-- - AwardEligibility_awardId_eligible_idx
|
||||
-- - WizardTemplate_programId_idx
|
||||
-- - WizardTemplate_isGlobal_idx
|
||||
--
|
||||
-- NEW FOREIGN KEYS:
|
||||
-- - WizardTemplate_programId_fkey -> Program(id) ON DELETE CASCADE
|
||||
-- - WizardTemplate_createdBy_fkey -> User(id) ON DELETE RESTRICT
|
||||
-- =============================================================================
|
||||
-- Migration: Add all missing schema elements not covered by previous migrations
|
||||
-- This brings the database fully in line with prisma/schema.prisma
|
||||
-- Uses IF NOT EXISTS / DO $$ guards for idempotent execution
|
||||
|
||||
-- =============================================================================
|
||||
-- 1. MISSING TABLE: WizardTemplate
|
||||
-- =============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "WizardTemplate" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"config" JSONB NOT NULL,
|
||||
"isGlobal" BOOLEAN NOT NULL DEFAULT false,
|
||||
"programId" TEXT,
|
||||
"createdBy" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "WizardTemplate_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "WizardTemplate_programId_idx" ON "WizardTemplate"("programId");
|
||||
CREATE INDEX IF NOT EXISTS "WizardTemplate_isGlobal_idx" ON "WizardTemplate"("isGlobal");
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "WizardTemplate" ADD CONSTRAINT "WizardTemplate_programId_fkey"
|
||||
FOREIGN KEY ("programId") REFERENCES "Program"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "WizardTemplate" ADD CONSTRAINT "WizardTemplate_createdBy_fkey"
|
||||
FOREIGN KEY ("createdBy") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
|
||||
-- =============================================================================
|
||||
-- 2. MISSING COLUMNS ON SpecialAward: eligibility job tracking fields
|
||||
-- =============================================================================
|
||||
|
||||
ALTER TABLE "SpecialAward" ADD COLUMN IF NOT EXISTS "eligibilityJobStatus" TEXT;
|
||||
ALTER TABLE "SpecialAward" ADD COLUMN IF NOT EXISTS "eligibilityJobTotal" INTEGER;
|
||||
ALTER TABLE "SpecialAward" ADD COLUMN IF NOT EXISTS "eligibilityJobDone" INTEGER;
|
||||
ALTER TABLE "SpecialAward" ADD COLUMN IF NOT EXISTS "eligibilityJobError" TEXT;
|
||||
ALTER TABLE "SpecialAward" ADD COLUMN IF NOT EXISTS "eligibilityJobStarted" TIMESTAMP(3);
|
||||
|
||||
-- =============================================================================
|
||||
-- 3. Project.referralSource: Already in init migration. No action needed.
|
||||
-- Round.slug: Already in init migration. No action needed.
|
||||
-- =============================================================================
|
||||
|
||||
-- =============================================================================
|
||||
-- 5. MISSING INDEXES
|
||||
-- =============================================================================
|
||||
|
||||
-- 5a. Assignment: @@index([projectId, userId])
|
||||
CREATE INDEX IF NOT EXISTS "Assignment_projectId_userId_idx" ON "Assignment"("projectId", "userId");
|
||||
|
||||
-- 5b. AuditLog: @@index([sessionId])
|
||||
CREATE INDEX IF NOT EXISTS "AuditLog_sessionId_idx" ON "AuditLog"("sessionId");
|
||||
|
||||
-- 5c. ProjectFile: @@index([projectId, roundId])
|
||||
CREATE INDEX IF NOT EXISTS "ProjectFile_projectId_roundId_idx" ON "ProjectFile"("projectId", "roundId");
|
||||
|
||||
-- 5d. MessageRecipient: @@index([userId])
|
||||
CREATE INDEX IF NOT EXISTS "MessageRecipient_userId_idx" ON "MessageRecipient"("userId");
|
||||
|
||||
-- 5e. MessageRecipient: @@unique([messageId, userId, channel])
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "MessageRecipient_messageId_userId_channel_key" ON "MessageRecipient"("messageId", "userId", "channel");
|
||||
|
||||
-- 5f. AwardEligibility: @@index([awardId, eligible]) - composite index
|
||||
CREATE INDEX IF NOT EXISTS "AwardEligibility_awardId_eligible_idx" ON "AwardEligibility"("awardId", "eligible");
|
||||
|
||||
-- =============================================================================
|
||||
-- 6. REMOVE STALE INDEX: Message_scheduledAt_idx
|
||||
-- The schema does NOT have @@index([scheduledAt]) on Message.
|
||||
-- The add_15_features migration created it, but the schema doesn't list it.
|
||||
-- Leaving it as-is since it's harmless and could be useful.
|
||||
-- =============================================================================
|
||||
|
||||
-- =============================================================================
|
||||
-- 7. VERIFY: All models from add_15_features are present
|
||||
-- DigestLog, RoundTemplate, MentorNote, MentorMilestone,
|
||||
-- MentorMilestoneCompletion, Message, MessageTemplate, MessageRecipient,
|
||||
-- Webhook, WebhookDelivery, EvaluationDiscussion, DiscussionComment
|
||||
-- -> All confirmed created in 20260205223133_add_15_features migration.
|
||||
-- -> All FKs confirmed in add_15_features + 20260208000000_add_missing_fks_indexes.
|
||||
-- =============================================================================
|
||||
|
||||
-- =============================================================================
|
||||
-- 8. VERIFY: Existing tables from init and subsequent migrations
|
||||
-- All core tables (User, Account, Session, VerificationToken, Program, Round,
|
||||
-- EvaluationForm, Project, ProjectFile, Assignment, Evaluation, GracePeriod,
|
||||
-- SystemSettings, AuditLog, AIUsageLog, NotificationLog, InAppNotification,
|
||||
-- NotificationEmailSetting, LearningResource, ResourceAccess, Partner,
|
||||
-- ExpertiseTag, ProjectTag, LiveVotingSession, LiveVote, TeamMember,
|
||||
-- MentorAssignment, FilteringRule, FilteringResult, FilteringJob,
|
||||
-- AssignmentJob, TaggingJob, SpecialAward, AwardEligibility, AwardJuror,
|
||||
-- AwardVote, ReminderLog, ConflictOfInterest, EvaluationSummary,
|
||||
-- ProjectStatusHistory, MentorMessage, FileRequirement)
|
||||
-- -> All confirmed present in migrations.
|
||||
-- =============================================================================
|
||||
|
||||
-- =============================================================================
|
||||
-- SUMMARY OF CHANGES IN THIS MIGRATION:
|
||||
--
|
||||
-- NEW TABLE:
|
||||
-- - WizardTemplate (with programId FK, createdBy FK, indexes)
|
||||
--
|
||||
-- NEW COLUMNS:
|
||||
-- - SpecialAward.eligibilityJobStatus (TEXT, nullable)
|
||||
-- - SpecialAward.eligibilityJobTotal (INTEGER, nullable)
|
||||
-- - SpecialAward.eligibilityJobDone (INTEGER, nullable)
|
||||
-- - SpecialAward.eligibilityJobError (TEXT, nullable)
|
||||
-- - SpecialAward.eligibilityJobStarted (TIMESTAMP, nullable)
|
||||
--
|
||||
-- NEW INDEXES:
|
||||
-- - Assignment_projectId_userId_idx
|
||||
-- - AuditLog_sessionId_idx
|
||||
-- - ProjectFile_projectId_roundId_idx
|
||||
-- - MessageRecipient_userId_idx
|
||||
-- - MessageRecipient_messageId_userId_channel_key (UNIQUE)
|
||||
-- - AwardEligibility_awardId_eligible_idx
|
||||
-- - WizardTemplate_programId_idx
|
||||
-- - WizardTemplate_isGlobal_idx
|
||||
--
|
||||
-- NEW FOREIGN KEYS:
|
||||
-- - WizardTemplate_programId_fkey -> Program(id) ON DELETE CASCADE
|
||||
-- - WizardTemplate_createdBy_fkey -> User(id) ON DELETE RESTRICT
|
||||
-- =============================================================================
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AwardVote_awardId_userId_idx" ON "AwardVote"("awardId", "userId");
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AwardVote_awardId_userId_idx" ON "AwardVote"("awardId", "userId");
|
||||
|
||||
@@ -1,99 +1,99 @@
|
||||
-- Migration: Add live voting enhancements (criteria voting, audience voting, AudienceVoter)
|
||||
-- Brings LiveVotingSession, LiveVote, and new AudienceVoter model in sync with schema.prisma
|
||||
-- Uses IF NOT EXISTS / DO $$ guards for idempotent execution
|
||||
|
||||
-- =============================================================================
|
||||
-- 1. LiveVotingSession: Add criteria-based & audience voting columns
|
||||
-- =============================================================================
|
||||
|
||||
ALTER TABLE "LiveVotingSession" ADD COLUMN IF NOT EXISTS "votingMode" TEXT NOT NULL DEFAULT 'simple';
|
||||
ALTER TABLE "LiveVotingSession" ADD COLUMN IF NOT EXISTS "criteriaJson" JSONB;
|
||||
ALTER TABLE "LiveVotingSession" ADD COLUMN IF NOT EXISTS "audienceVotingMode" TEXT NOT NULL DEFAULT 'disabled';
|
||||
ALTER TABLE "LiveVotingSession" ADD COLUMN IF NOT EXISTS "audienceMaxFavorites" INTEGER NOT NULL DEFAULT 3;
|
||||
ALTER TABLE "LiveVotingSession" ADD COLUMN IF NOT EXISTS "audienceRequireId" BOOLEAN NOT NULL DEFAULT false;
|
||||
ALTER TABLE "LiveVotingSession" ADD COLUMN IF NOT EXISTS "audienceVotingDuration" INTEGER;
|
||||
|
||||
-- =============================================================================
|
||||
-- 2. LiveVote: Add criteria scores, audience voter link, make userId nullable
|
||||
-- =============================================================================
|
||||
|
||||
ALTER TABLE "LiveVote" ADD COLUMN IF NOT EXISTS "criterionScoresJson" JSONB;
|
||||
ALTER TABLE "LiveVote" ADD COLUMN IF NOT EXISTS "audienceVoterId" TEXT;
|
||||
|
||||
-- Make userId nullable (was NOT NULL in init migration)
|
||||
ALTER TABLE "LiveVote" ALTER COLUMN "userId" DROP NOT NULL;
|
||||
|
||||
-- =============================================================================
|
||||
-- 3. AudienceVoter: New table for audience participation
|
||||
-- =============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "AudienceVoter" (
|
||||
"id" TEXT NOT NULL,
|
||||
"sessionId" TEXT NOT NULL,
|
||||
"token" TEXT NOT NULL,
|
||||
"identifier" TEXT,
|
||||
"identifierType" TEXT,
|
||||
"ipAddress" TEXT,
|
||||
"userAgent" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "AudienceVoter_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- Unique constraint on token
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "AudienceVoter" ADD CONSTRAINT "AudienceVoter_token_key" UNIQUE ("token");
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
|
||||
-- Indexes
|
||||
CREATE INDEX IF NOT EXISTS "AudienceVoter_sessionId_idx" ON "AudienceVoter"("sessionId");
|
||||
CREATE INDEX IF NOT EXISTS "AudienceVoter_token_idx" ON "AudienceVoter"("token");
|
||||
|
||||
-- Foreign key: AudienceVoter.sessionId -> LiveVotingSession.id
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "AudienceVoter" ADD CONSTRAINT "AudienceVoter_sessionId_fkey"
|
||||
FOREIGN KEY ("sessionId") REFERENCES "LiveVotingSession"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
|
||||
-- =============================================================================
|
||||
-- 4. LiveVote: Foreign key and indexes for audienceVoterId
|
||||
-- =============================================================================
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "LiveVote_audienceVoterId_idx" ON "LiveVote"("audienceVoterId");
|
||||
|
||||
-- Foreign key: LiveVote.audienceVoterId -> AudienceVoter.id
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "LiveVote" ADD CONSTRAINT "LiveVote_audienceVoterId_fkey"
|
||||
FOREIGN KEY ("audienceVoterId") REFERENCES "AudienceVoter"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
|
||||
-- Unique constraint: sessionId + projectId + audienceVoterId
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "LiveVote" ADD CONSTRAINT "LiveVote_sessionId_projectId_audienceVoterId_key"
|
||||
UNIQUE ("sessionId", "projectId", "audienceVoterId");
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
|
||||
-- =============================================================================
|
||||
-- SUMMARY:
|
||||
--
|
||||
-- LiveVotingSession new columns:
|
||||
-- - votingMode (TEXT, default 'simple')
|
||||
-- - criteriaJson (JSONB, nullable)
|
||||
-- - audienceVotingMode (TEXT, default 'disabled')
|
||||
-- - audienceMaxFavorites (INTEGER, default 3)
|
||||
-- - audienceRequireId (BOOLEAN, default false)
|
||||
-- - audienceVotingDuration (INTEGER, nullable)
|
||||
--
|
||||
-- LiveVote changes:
|
||||
-- - criterionScoresJson (JSONB, nullable) - new column
|
||||
-- - audienceVoterId (TEXT, nullable) - new column
|
||||
-- - userId changed from NOT NULL to nullable
|
||||
-- - New unique: (sessionId, projectId, audienceVoterId)
|
||||
-- - New index: audienceVoterId
|
||||
-- - New FK: audienceVoterId -> AudienceVoter(id)
|
||||
--
|
||||
-- New table: AudienceVoter
|
||||
-- - id, sessionId, token (unique), identifier, identifierType,
|
||||
-- ipAddress, userAgent, createdAt
|
||||
-- - FK: sessionId -> LiveVotingSession(id) CASCADE
|
||||
-- =============================================================================
|
||||
-- Migration: Add live voting enhancements (criteria voting, audience voting, AudienceVoter)
|
||||
-- Brings LiveVotingSession, LiveVote, and new AudienceVoter model in sync with schema.prisma
|
||||
-- Uses IF NOT EXISTS / DO $$ guards for idempotent execution
|
||||
|
||||
-- =============================================================================
|
||||
-- 1. LiveVotingSession: Add criteria-based & audience voting columns
|
||||
-- =============================================================================
|
||||
|
||||
ALTER TABLE "LiveVotingSession" ADD COLUMN IF NOT EXISTS "votingMode" TEXT NOT NULL DEFAULT 'simple';
|
||||
ALTER TABLE "LiveVotingSession" ADD COLUMN IF NOT EXISTS "criteriaJson" JSONB;
|
||||
ALTER TABLE "LiveVotingSession" ADD COLUMN IF NOT EXISTS "audienceVotingMode" TEXT NOT NULL DEFAULT 'disabled';
|
||||
ALTER TABLE "LiveVotingSession" ADD COLUMN IF NOT EXISTS "audienceMaxFavorites" INTEGER NOT NULL DEFAULT 3;
|
||||
ALTER TABLE "LiveVotingSession" ADD COLUMN IF NOT EXISTS "audienceRequireId" BOOLEAN NOT NULL DEFAULT false;
|
||||
ALTER TABLE "LiveVotingSession" ADD COLUMN IF NOT EXISTS "audienceVotingDuration" INTEGER;
|
||||
|
||||
-- =============================================================================
|
||||
-- 2. LiveVote: Add criteria scores, audience voter link, make userId nullable
|
||||
-- =============================================================================
|
||||
|
||||
ALTER TABLE "LiveVote" ADD COLUMN IF NOT EXISTS "criterionScoresJson" JSONB;
|
||||
ALTER TABLE "LiveVote" ADD COLUMN IF NOT EXISTS "audienceVoterId" TEXT;
|
||||
|
||||
-- Make userId nullable (was NOT NULL in init migration)
|
||||
ALTER TABLE "LiveVote" ALTER COLUMN "userId" DROP NOT NULL;
|
||||
|
||||
-- =============================================================================
|
||||
-- 3. AudienceVoter: New table for audience participation
|
||||
-- =============================================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "AudienceVoter" (
|
||||
"id" TEXT NOT NULL,
|
||||
"sessionId" TEXT NOT NULL,
|
||||
"token" TEXT NOT NULL,
|
||||
"identifier" TEXT,
|
||||
"identifierType" TEXT,
|
||||
"ipAddress" TEXT,
|
||||
"userAgent" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "AudienceVoter_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- Unique constraint on token
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "AudienceVoter" ADD CONSTRAINT "AudienceVoter_token_key" UNIQUE ("token");
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
|
||||
-- Indexes
|
||||
CREATE INDEX IF NOT EXISTS "AudienceVoter_sessionId_idx" ON "AudienceVoter"("sessionId");
|
||||
CREATE INDEX IF NOT EXISTS "AudienceVoter_token_idx" ON "AudienceVoter"("token");
|
||||
|
||||
-- Foreign key: AudienceVoter.sessionId -> LiveVotingSession.id
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "AudienceVoter" ADD CONSTRAINT "AudienceVoter_sessionId_fkey"
|
||||
FOREIGN KEY ("sessionId") REFERENCES "LiveVotingSession"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
|
||||
-- =============================================================================
|
||||
-- 4. LiveVote: Foreign key and indexes for audienceVoterId
|
||||
-- =============================================================================
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "LiveVote_audienceVoterId_idx" ON "LiveVote"("audienceVoterId");
|
||||
|
||||
-- Foreign key: LiveVote.audienceVoterId -> AudienceVoter.id
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "LiveVote" ADD CONSTRAINT "LiveVote_audienceVoterId_fkey"
|
||||
FOREIGN KEY ("audienceVoterId") REFERENCES "AudienceVoter"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
|
||||
-- Unique constraint: sessionId + projectId + audienceVoterId
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "LiveVote" ADD CONSTRAINT "LiveVote_sessionId_projectId_audienceVoterId_key"
|
||||
UNIQUE ("sessionId", "projectId", "audienceVoterId");
|
||||
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||
|
||||
-- =============================================================================
|
||||
-- SUMMARY:
|
||||
--
|
||||
-- LiveVotingSession new columns:
|
||||
-- - votingMode (TEXT, default 'simple')
|
||||
-- - criteriaJson (JSONB, nullable)
|
||||
-- - audienceVotingMode (TEXT, default 'disabled')
|
||||
-- - audienceMaxFavorites (INTEGER, default 3)
|
||||
-- - audienceRequireId (BOOLEAN, default false)
|
||||
-- - audienceVotingDuration (INTEGER, nullable)
|
||||
--
|
||||
-- LiveVote changes:
|
||||
-- - criterionScoresJson (JSONB, nullable) - new column
|
||||
-- - audienceVoterId (TEXT, nullable) - new column
|
||||
-- - userId changed from NOT NULL to nullable
|
||||
-- - New unique: (sessionId, projectId, audienceVoterId)
|
||||
-- - New index: audienceVoterId
|
||||
-- - New FK: audienceVoterId -> AudienceVoter(id)
|
||||
--
|
||||
-- New table: AudienceVoter
|
||||
-- - id, sessionId, token (unique), identifier, identifierType,
|
||||
-- ipAddress, userAgent, createdAt
|
||||
-- - FK: sessionId -> LiveVotingSession(id) CASCADE
|
||||
-- =============================================================================
|
||||
|
||||
@@ -0,0 +1,572 @@
|
||||
-- =============================================================================
|
||||
-- Phase 0+1: Add Competition/Round Architecture (additive — no breaking changes)
|
||||
-- =============================================================================
|
||||
-- New enums, new tables, new optional columns on existing tables.
|
||||
-- Old Pipeline/Track/Stage tables are untouched.
|
||||
|
||||
-- ─── New Enum Types ──────────────────────────────────────────────────────────
|
||||
|
||||
CREATE TYPE "CompetitionStatus" AS ENUM ('DRAFT', 'ACTIVE', 'CLOSED', 'ARCHIVED');
|
||||
CREATE TYPE "RoundType" AS ENUM ('INTAKE', 'FILTERING', 'EVALUATION', 'SUBMISSION', 'MENTORING', 'LIVE_FINAL', 'DELIBERATION');
|
||||
CREATE TYPE "RoundStatus" AS ENUM ('ROUND_DRAFT', 'ROUND_ACTIVE', 'ROUND_CLOSED', 'ROUND_ARCHIVED');
|
||||
CREATE TYPE "ProjectRoundStateValue" AS ENUM ('PENDING', 'IN_PROGRESS', 'PASSED', 'REJECTED', 'COMPLETED', 'WITHDRAWN');
|
||||
CREATE TYPE "AdvancementRuleType" AS ENUM ('AUTO_ADVANCE', 'SCORE_THRESHOLD', 'TOP_N', 'ADMIN_SELECTION', 'AI_RECOMMENDED');
|
||||
CREATE TYPE "CapMode" AS ENUM ('HARD', 'SOFT', 'NONE');
|
||||
CREATE TYPE "DeadlinePolicy" AS ENUM ('HARD_DEADLINE', 'FLAG', 'GRACE');
|
||||
CREATE TYPE "JuryGroupMemberRole" AS ENUM ('CHAIR', 'MEMBER', 'OBSERVER');
|
||||
CREATE TYPE "AssignmentIntentSource" AS ENUM ('INVITE', 'ADMIN', 'SYSTEM');
|
||||
CREATE TYPE "AssignmentIntentStatus" AS ENUM ('INTENT_PENDING', 'HONORED', 'OVERRIDDEN', 'EXPIRED', 'CANCELLED');
|
||||
CREATE TYPE "MentorMessageRole" AS ENUM ('MENTOR_ROLE', 'APPLICANT_ROLE', 'ADMIN_ROLE');
|
||||
CREATE TYPE "SubmissionPromotionSource" AS ENUM ('MENTOR_FILE', 'ADMIN_REPLACEMENT');
|
||||
CREATE TYPE "DeliberationMode" AS ENUM ('SINGLE_WINNER_VOTE', 'FULL_RANKING');
|
||||
CREATE TYPE "DeliberationStatus" AS ENUM ('DELIB_OPEN', 'VOTING', 'TALLYING', 'RUNOFF', 'DELIB_LOCKED');
|
||||
CREATE TYPE "TieBreakMethod" AS ENUM ('TIE_RUNOFF', 'TIE_ADMIN_DECIDES', 'SCORE_FALLBACK');
|
||||
CREATE TYPE "DeliberationParticipantStatus" AS ENUM ('REQUIRED', 'ABSENT_EXCUSED', 'REPLACED', 'REPLACEMENT_ACTIVE');
|
||||
CREATE TYPE "AwardEligibilityMode" AS ENUM ('SEPARATE_POOL', 'STAY_IN_MAIN');
|
||||
|
||||
-- Add FEATURE_FLAGS to SettingCategory enum
|
||||
ALTER TYPE "SettingCategory" ADD VALUE 'FEATURE_FLAGS';
|
||||
|
||||
-- ─── New Tables ──────────────────────────────────────────────────────────────
|
||||
|
||||
-- Competition (replaces Pipeline)
|
||||
CREATE TABLE "Competition" (
|
||||
"id" TEXT NOT NULL,
|
||||
"programId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"slug" TEXT NOT NULL,
|
||||
"status" "CompetitionStatus" NOT NULL DEFAULT 'DRAFT',
|
||||
"categoryMode" TEXT NOT NULL DEFAULT 'SHARED',
|
||||
"startupFinalistCount" INTEGER NOT NULL DEFAULT 3,
|
||||
"conceptFinalistCount" INTEGER NOT NULL DEFAULT 3,
|
||||
"notifyOnRoundAdvance" BOOLEAN NOT NULL DEFAULT true,
|
||||
"notifyOnDeadlineApproach" BOOLEAN NOT NULL DEFAULT true,
|
||||
"deadlineReminderDays" INTEGER[] DEFAULT ARRAY[7, 3, 1]::INTEGER[],
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Competition_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- Round (replaces Stage)
|
||||
CREATE TABLE "Round" (
|
||||
"id" TEXT NOT NULL,
|
||||
"competitionId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"slug" TEXT NOT NULL,
|
||||
"roundType" "RoundType" NOT NULL,
|
||||
"status" "RoundStatus" NOT NULL DEFAULT 'ROUND_DRAFT',
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"windowOpenAt" TIMESTAMP(3),
|
||||
"windowCloseAt" TIMESTAMP(3),
|
||||
"configJson" JSONB,
|
||||
"purposeKey" TEXT,
|
||||
"juryGroupId" TEXT,
|
||||
"submissionWindowId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Round_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- ProjectRoundState
|
||||
CREATE TABLE "ProjectRoundState" (
|
||||
"id" TEXT NOT NULL,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"roundId" TEXT NOT NULL,
|
||||
"state" "ProjectRoundStateValue" NOT NULL DEFAULT 'PENDING',
|
||||
"enteredAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"exitedAt" TIMESTAMP(3),
|
||||
"metadataJson" JSONB,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "ProjectRoundState_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AdvancementRule
|
||||
CREATE TABLE "AdvancementRule" (
|
||||
"id" TEXT NOT NULL,
|
||||
"roundId" TEXT NOT NULL,
|
||||
"targetRoundId" TEXT,
|
||||
"ruleType" "AdvancementRuleType" NOT NULL,
|
||||
"configJson" JSONB NOT NULL,
|
||||
"isDefault" BOOLEAN NOT NULL DEFAULT true,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "AdvancementRule_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- JuryGroup
|
||||
CREATE TABLE "JuryGroup" (
|
||||
"id" TEXT NOT NULL,
|
||||
"competitionId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"slug" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"defaultMaxAssignments" INTEGER NOT NULL DEFAULT 20,
|
||||
"defaultCapMode" "CapMode" NOT NULL DEFAULT 'SOFT',
|
||||
"softCapBuffer" INTEGER NOT NULL DEFAULT 2,
|
||||
"categoryQuotasEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"defaultCategoryQuotas" JSONB,
|
||||
"allowJurorCapAdjustment" BOOLEAN NOT NULL DEFAULT false,
|
||||
"allowJurorRatioAdjustment" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "JuryGroup_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- JuryGroupMember
|
||||
CREATE TABLE "JuryGroupMember" (
|
||||
"id" TEXT NOT NULL,
|
||||
"juryGroupId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"role" "JuryGroupMemberRole" NOT NULL DEFAULT 'MEMBER',
|
||||
"joinedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"maxAssignmentsOverride" INTEGER,
|
||||
"capModeOverride" "CapMode",
|
||||
"categoryQuotasOverride" JSONB,
|
||||
"preferredStartupRatio" DOUBLE PRECISION,
|
||||
"availabilityNotes" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "JuryGroupMember_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- SubmissionWindow
|
||||
CREATE TABLE "SubmissionWindow" (
|
||||
"id" TEXT NOT NULL,
|
||||
"competitionId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"slug" TEXT NOT NULL,
|
||||
"roundNumber" INTEGER NOT NULL,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"windowOpenAt" TIMESTAMP(3),
|
||||
"windowCloseAt" TIMESTAMP(3),
|
||||
"deadlinePolicy" "DeadlinePolicy" NOT NULL DEFAULT 'FLAG',
|
||||
"graceHours" INTEGER,
|
||||
"lockOnClose" BOOLEAN NOT NULL DEFAULT true,
|
||||
"isLocked" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "SubmissionWindow_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- SubmissionFileRequirement
|
||||
CREATE TABLE "SubmissionFileRequirement" (
|
||||
"id" TEXT NOT NULL,
|
||||
"submissionWindowId" TEXT NOT NULL,
|
||||
"label" TEXT NOT NULL,
|
||||
"slug" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"mimeTypes" TEXT[],
|
||||
"maxSizeMb" INTEGER,
|
||||
"required" BOOLEAN NOT NULL DEFAULT true,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "SubmissionFileRequirement_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- RoundSubmissionVisibility
|
||||
CREATE TABLE "RoundSubmissionVisibility" (
|
||||
"id" TEXT NOT NULL,
|
||||
"roundId" TEXT NOT NULL,
|
||||
"submissionWindowId" TEXT NOT NULL,
|
||||
"canView" BOOLEAN NOT NULL DEFAULT true,
|
||||
"displayLabel" TEXT,
|
||||
|
||||
CONSTRAINT "RoundSubmissionVisibility_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AssignmentIntent
|
||||
CREATE TABLE "AssignmentIntent" (
|
||||
"id" TEXT NOT NULL,
|
||||
"juryGroupMemberId" TEXT NOT NULL,
|
||||
"roundId" TEXT NOT NULL,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"source" "AssignmentIntentSource" NOT NULL,
|
||||
"status" "AssignmentIntentStatus" NOT NULL DEFAULT 'INTENT_PENDING',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "AssignmentIntent_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- AssignmentException
|
||||
CREATE TABLE "AssignmentException" (
|
||||
"id" TEXT NOT NULL,
|
||||
"assignmentId" TEXT NOT NULL,
|
||||
"reason" TEXT NOT NULL,
|
||||
"overCapBy" INTEGER NOT NULL,
|
||||
"approvedById" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "AssignmentException_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- MentorFile
|
||||
CREATE TABLE "MentorFile" (
|
||||
"id" TEXT NOT NULL,
|
||||
"mentorAssignmentId" TEXT NOT NULL,
|
||||
"uploadedByUserId" TEXT NOT NULL,
|
||||
"fileName" TEXT NOT NULL,
|
||||
"mimeType" TEXT NOT NULL,
|
||||
"size" INTEGER NOT NULL,
|
||||
"bucket" TEXT NOT NULL,
|
||||
"objectKey" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"isPromoted" BOOLEAN NOT NULL DEFAULT false,
|
||||
"promotedToFileId" TEXT,
|
||||
"promotedAt" TIMESTAMP(3),
|
||||
"promotedByUserId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "MentorFile_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- MentorFileComment
|
||||
CREATE TABLE "MentorFileComment" (
|
||||
"id" TEXT NOT NULL,
|
||||
"mentorFileId" TEXT NOT NULL,
|
||||
"authorId" TEXT NOT NULL,
|
||||
"content" TEXT NOT NULL,
|
||||
"parentCommentId" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "MentorFileComment_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- SubmissionPromotionEvent
|
||||
CREATE TABLE "SubmissionPromotionEvent" (
|
||||
"id" TEXT NOT NULL,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"roundId" TEXT NOT NULL,
|
||||
"slotKey" TEXT NOT NULL,
|
||||
"sourceType" "SubmissionPromotionSource" NOT NULL,
|
||||
"sourceFileId" TEXT,
|
||||
"promotedById" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "SubmissionPromotionEvent_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- DeliberationSession
|
||||
CREATE TABLE "DeliberationSession" (
|
||||
"id" TEXT NOT NULL,
|
||||
"competitionId" TEXT NOT NULL,
|
||||
"roundId" TEXT NOT NULL,
|
||||
"category" "CompetitionCategory" NOT NULL,
|
||||
"mode" "DeliberationMode" NOT NULL,
|
||||
"showCollectiveRankings" BOOLEAN NOT NULL DEFAULT false,
|
||||
"showPriorJuryData" BOOLEAN NOT NULL DEFAULT false,
|
||||
"status" "DeliberationStatus" NOT NULL,
|
||||
"tieBreakMethod" "TieBreakMethod" NOT NULL,
|
||||
"adminOverrideResult" JSONB,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "DeliberationSession_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- DeliberationVote
|
||||
CREATE TABLE "DeliberationVote" (
|
||||
"id" TEXT NOT NULL,
|
||||
"sessionId" TEXT NOT NULL,
|
||||
"juryMemberId" TEXT NOT NULL,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"rank" INTEGER,
|
||||
"isWinnerPick" BOOLEAN NOT NULL DEFAULT false,
|
||||
"runoffRound" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "DeliberationVote_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- DeliberationResult
|
||||
CREATE TABLE "DeliberationResult" (
|
||||
"id" TEXT NOT NULL,
|
||||
"sessionId" TEXT NOT NULL,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"finalRank" INTEGER NOT NULL,
|
||||
"voteCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"isAdminOverridden" BOOLEAN NOT NULL DEFAULT false,
|
||||
"overrideReason" TEXT,
|
||||
|
||||
CONSTRAINT "DeliberationResult_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- DeliberationParticipant
|
||||
CREATE TABLE "DeliberationParticipant" (
|
||||
"id" TEXT NOT NULL,
|
||||
"sessionId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"status" "DeliberationParticipantStatus" NOT NULL,
|
||||
"replacedById" TEXT,
|
||||
|
||||
CONSTRAINT "DeliberationParticipant_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- ResultLock
|
||||
CREATE TABLE "ResultLock" (
|
||||
"id" TEXT NOT NULL,
|
||||
"competitionId" TEXT NOT NULL,
|
||||
"roundId" TEXT NOT NULL,
|
||||
"category" "CompetitionCategory" NOT NULL,
|
||||
"lockedById" TEXT NOT NULL,
|
||||
"resultSnapshot" JSONB NOT NULL,
|
||||
"lockedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "ResultLock_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- ResultUnlockEvent
|
||||
CREATE TABLE "ResultUnlockEvent" (
|
||||
"id" TEXT NOT NULL,
|
||||
"resultLockId" TEXT NOT NULL,
|
||||
"unlockedById" TEXT NOT NULL,
|
||||
"reason" TEXT NOT NULL,
|
||||
"unlockedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "ResultUnlockEvent_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- ─── Add Columns to Existing Tables ──────────────────────────────────────────
|
||||
|
||||
-- Assignment: add juryGroupId
|
||||
ALTER TABLE "Assignment" ADD COLUMN "juryGroupId" TEXT;
|
||||
|
||||
-- SpecialAward: add competition/round architecture fields
|
||||
ALTER TABLE "SpecialAward" ADD COLUMN "competitionId" TEXT;
|
||||
ALTER TABLE "SpecialAward" ADD COLUMN "evaluationRoundId" TEXT;
|
||||
ALTER TABLE "SpecialAward" ADD COLUMN "juryGroupId" TEXT;
|
||||
ALTER TABLE "SpecialAward" ADD COLUMN "eligibilityMode" "AwardEligibilityMode" NOT NULL DEFAULT 'STAY_IN_MAIN';
|
||||
ALTER TABLE "SpecialAward" ADD COLUMN "decisionMode" TEXT;
|
||||
|
||||
-- MentorAssignment: add workspace fields
|
||||
ALTER TABLE "MentorAssignment" ADD COLUMN "workspaceEnabled" BOOLEAN NOT NULL DEFAULT false;
|
||||
ALTER TABLE "MentorAssignment" ADD COLUMN "workspaceOpenAt" TIMESTAMP(3);
|
||||
ALTER TABLE "MentorAssignment" ADD COLUMN "workspaceCloseAt" TIMESTAMP(3);
|
||||
|
||||
-- MentorMessage: add workspace fields
|
||||
ALTER TABLE "MentorMessage" ADD COLUMN "workspaceId" TEXT;
|
||||
ALTER TABLE "MentorMessage" ADD COLUMN "senderRole" "MentorMessageRole";
|
||||
|
||||
-- ProjectFile: add submission window link
|
||||
ALTER TABLE "ProjectFile" ADD COLUMN "submissionWindowId" TEXT;
|
||||
ALTER TABLE "ProjectFile" ADD COLUMN "submissionFileRequirementId" TEXT;
|
||||
|
||||
-- ─── Unique Constraints ──────────────────────────────────────────────────────
|
||||
|
||||
CREATE UNIQUE INDEX "Competition_slug_key" ON "Competition"("slug");
|
||||
CREATE UNIQUE INDEX "Round_competitionId_slug_key" ON "Round"("competitionId", "slug");
|
||||
CREATE UNIQUE INDEX "Round_competitionId_sortOrder_key" ON "Round"("competitionId", "sortOrder");
|
||||
CREATE UNIQUE INDEX "ProjectRoundState_projectId_roundId_key" ON "ProjectRoundState"("projectId", "roundId");
|
||||
CREATE UNIQUE INDEX "JuryGroup_competitionId_slug_key" ON "JuryGroup"("competitionId", "slug");
|
||||
CREATE UNIQUE INDEX "JuryGroupMember_juryGroupId_userId_key" ON "JuryGroupMember"("juryGroupId", "userId");
|
||||
CREATE UNIQUE INDEX "SubmissionWindow_competitionId_slug_key" ON "SubmissionWindow"("competitionId", "slug");
|
||||
CREATE UNIQUE INDEX "SubmissionWindow_competitionId_roundNumber_key" ON "SubmissionWindow"("competitionId", "roundNumber");
|
||||
CREATE UNIQUE INDEX "RoundSubmissionVisibility_roundId_submissionWindowId_key" ON "RoundSubmissionVisibility"("roundId", "submissionWindowId");
|
||||
CREATE UNIQUE INDEX "AssignmentIntent_juryGroupMemberId_roundId_projectId_key" ON "AssignmentIntent"("juryGroupMemberId", "roundId", "projectId");
|
||||
CREATE UNIQUE INDEX "MentorFile_promotedToFileId_key" ON "MentorFile"("promotedToFileId");
|
||||
CREATE UNIQUE INDEX "DeliberationVote_sessionId_juryMemberId_projectId_runoffRo_key" ON "DeliberationVote"("sessionId", "juryMemberId", "projectId", "runoffRound");
|
||||
CREATE UNIQUE INDEX "DeliberationResult_sessionId_projectId_key" ON "DeliberationResult"("sessionId", "projectId");
|
||||
CREATE UNIQUE INDEX "DeliberationParticipant_sessionId_userId_key" ON "DeliberationParticipant"("sessionId", "userId");
|
||||
CREATE UNIQUE INDEX "SubmissionFileRequirement_submissionWindowId_slug_key" ON "SubmissionFileRequirement"("submissionWindowId", "slug");
|
||||
CREATE UNIQUE INDEX "AdvancementRule_roundId_sortOrder_key" ON "AdvancementRule"("roundId", "sortOrder");
|
||||
|
||||
-- ─── Indexes ─────────────────────────────────────────────────────────────────
|
||||
|
||||
-- Competition
|
||||
CREATE INDEX "Competition_programId_idx" ON "Competition"("programId");
|
||||
CREATE INDEX "Competition_status_idx" ON "Competition"("status");
|
||||
|
||||
-- Round
|
||||
CREATE INDEX "Round_competitionId_idx" ON "Round"("competitionId");
|
||||
CREATE INDEX "Round_roundType_idx" ON "Round"("roundType");
|
||||
CREATE INDEX "Round_status_idx" ON "Round"("status");
|
||||
|
||||
-- ProjectRoundState
|
||||
CREATE INDEX "ProjectRoundState_projectId_idx" ON "ProjectRoundState"("projectId");
|
||||
CREATE INDEX "ProjectRoundState_roundId_idx" ON "ProjectRoundState"("roundId");
|
||||
CREATE INDEX "ProjectRoundState_state_idx" ON "ProjectRoundState"("state");
|
||||
|
||||
-- AdvancementRule
|
||||
CREATE INDEX "AdvancementRule_roundId_idx" ON "AdvancementRule"("roundId");
|
||||
|
||||
-- JuryGroup
|
||||
CREATE INDEX "JuryGroup_competitionId_idx" ON "JuryGroup"("competitionId");
|
||||
|
||||
-- JuryGroupMember
|
||||
CREATE INDEX "JuryGroupMember_juryGroupId_idx" ON "JuryGroupMember"("juryGroupId");
|
||||
CREATE INDEX "JuryGroupMember_userId_idx" ON "JuryGroupMember"("userId");
|
||||
|
||||
-- SubmissionWindow
|
||||
CREATE INDEX "SubmissionWindow_competitionId_idx" ON "SubmissionWindow"("competitionId");
|
||||
|
||||
-- SubmissionFileRequirement
|
||||
CREATE INDEX "SubmissionFileRequirement_submissionWindowId_idx" ON "SubmissionFileRequirement"("submissionWindowId");
|
||||
|
||||
-- RoundSubmissionVisibility
|
||||
CREATE INDEX "RoundSubmissionVisibility_roundId_idx" ON "RoundSubmissionVisibility"("roundId");
|
||||
|
||||
-- AssignmentIntent
|
||||
CREATE INDEX "AssignmentIntent_roundId_idx" ON "AssignmentIntent"("roundId");
|
||||
CREATE INDEX "AssignmentIntent_projectId_idx" ON "AssignmentIntent"("projectId");
|
||||
CREATE INDEX "AssignmentIntent_status_idx" ON "AssignmentIntent"("status");
|
||||
|
||||
-- AssignmentException
|
||||
CREATE INDEX "AssignmentException_assignmentId_idx" ON "AssignmentException"("assignmentId");
|
||||
CREATE INDEX "AssignmentException_approvedById_idx" ON "AssignmentException"("approvedById");
|
||||
|
||||
-- MentorFile
|
||||
CREATE INDEX "MentorFile_mentorAssignmentId_idx" ON "MentorFile"("mentorAssignmentId");
|
||||
CREATE INDEX "MentorFile_uploadedByUserId_idx" ON "MentorFile"("uploadedByUserId");
|
||||
|
||||
-- MentorFileComment
|
||||
CREATE INDEX "MentorFileComment_mentorFileId_idx" ON "MentorFileComment"("mentorFileId");
|
||||
CREATE INDEX "MentorFileComment_authorId_idx" ON "MentorFileComment"("authorId");
|
||||
CREATE INDEX "MentorFileComment_parentCommentId_idx" ON "MentorFileComment"("parentCommentId");
|
||||
|
||||
-- SubmissionPromotionEvent
|
||||
CREATE INDEX "SubmissionPromotionEvent_projectId_idx" ON "SubmissionPromotionEvent"("projectId");
|
||||
CREATE INDEX "SubmissionPromotionEvent_roundId_idx" ON "SubmissionPromotionEvent"("roundId");
|
||||
CREATE INDEX "SubmissionPromotionEvent_sourceFileId_idx" ON "SubmissionPromotionEvent"("sourceFileId");
|
||||
|
||||
-- DeliberationSession
|
||||
CREATE INDEX "DeliberationSession_competitionId_idx" ON "DeliberationSession"("competitionId");
|
||||
CREATE INDEX "DeliberationSession_roundId_idx" ON "DeliberationSession"("roundId");
|
||||
CREATE INDEX "DeliberationSession_status_idx" ON "DeliberationSession"("status");
|
||||
|
||||
-- DeliberationVote
|
||||
CREATE INDEX "DeliberationVote_sessionId_idx" ON "DeliberationVote"("sessionId");
|
||||
CREATE INDEX "DeliberationVote_juryMemberId_idx" ON "DeliberationVote"("juryMemberId");
|
||||
CREATE INDEX "DeliberationVote_projectId_idx" ON "DeliberationVote"("projectId");
|
||||
|
||||
-- DeliberationResult
|
||||
CREATE INDEX "DeliberationResult_sessionId_idx" ON "DeliberationResult"("sessionId");
|
||||
CREATE INDEX "DeliberationResult_projectId_idx" ON "DeliberationResult"("projectId");
|
||||
|
||||
-- DeliberationParticipant
|
||||
CREATE INDEX "DeliberationParticipant_sessionId_idx" ON "DeliberationParticipant"("sessionId");
|
||||
CREATE INDEX "DeliberationParticipant_userId_idx" ON "DeliberationParticipant"("userId");
|
||||
|
||||
-- ResultLock
|
||||
CREATE INDEX "ResultLock_competitionId_idx" ON "ResultLock"("competitionId");
|
||||
CREATE INDEX "ResultLock_roundId_idx" ON "ResultLock"("roundId");
|
||||
CREATE INDEX "ResultLock_category_idx" ON "ResultLock"("category");
|
||||
|
||||
-- ResultUnlockEvent
|
||||
CREATE INDEX "ResultUnlockEvent_resultLockId_idx" ON "ResultUnlockEvent"("resultLockId");
|
||||
CREATE INDEX "ResultUnlockEvent_unlockedById_idx" ON "ResultUnlockEvent"("unlockedById");
|
||||
|
||||
-- Indexes on modified existing tables
|
||||
CREATE INDEX "Assignment_juryGroupId_idx" ON "Assignment"("juryGroupId");
|
||||
CREATE INDEX "SpecialAward_competitionId_idx" ON "SpecialAward"("competitionId");
|
||||
CREATE INDEX "SpecialAward_evaluationRoundId_idx" ON "SpecialAward"("evaluationRoundId");
|
||||
CREATE INDEX "MentorMessage_workspaceId_idx" ON "MentorMessage"("workspaceId");
|
||||
CREATE INDEX "ProjectFile_submissionWindowId_idx" ON "ProjectFile"("submissionWindowId");
|
||||
CREATE INDEX "ProjectFile_submissionFileRequirementId_idx" ON "ProjectFile"("submissionFileRequirementId");
|
||||
|
||||
-- ─── Foreign Keys ────────────────────────────────────────────────────────────
|
||||
|
||||
-- Competition
|
||||
ALTER TABLE "Competition" ADD CONSTRAINT "Competition_programId_fkey" FOREIGN KEY ("programId") REFERENCES "Program"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- Round
|
||||
ALTER TABLE "Round" ADD CONSTRAINT "Round_competitionId_fkey" FOREIGN KEY ("competitionId") REFERENCES "Competition"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "Round" ADD CONSTRAINT "Round_juryGroupId_fkey" FOREIGN KEY ("juryGroupId") REFERENCES "JuryGroup"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "Round" ADD CONSTRAINT "Round_submissionWindowId_fkey" FOREIGN KEY ("submissionWindowId") REFERENCES "SubmissionWindow"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- ProjectRoundState
|
||||
ALTER TABLE "ProjectRoundState" ADD CONSTRAINT "ProjectRoundState_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "ProjectRoundState" ADD CONSTRAINT "ProjectRoundState_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AdvancementRule
|
||||
ALTER TABLE "AdvancementRule" ADD CONSTRAINT "AdvancementRule_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- JuryGroup
|
||||
ALTER TABLE "JuryGroup" ADD CONSTRAINT "JuryGroup_competitionId_fkey" FOREIGN KEY ("competitionId") REFERENCES "Competition"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- JuryGroupMember
|
||||
ALTER TABLE "JuryGroupMember" ADD CONSTRAINT "JuryGroupMember_juryGroupId_fkey" FOREIGN KEY ("juryGroupId") REFERENCES "JuryGroup"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "JuryGroupMember" ADD CONSTRAINT "JuryGroupMember_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- SubmissionWindow
|
||||
ALTER TABLE "SubmissionWindow" ADD CONSTRAINT "SubmissionWindow_competitionId_fkey" FOREIGN KEY ("competitionId") REFERENCES "Competition"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- SubmissionFileRequirement
|
||||
ALTER TABLE "SubmissionFileRequirement" ADD CONSTRAINT "SubmissionFileRequirement_submissionWindowId_fkey" FOREIGN KEY ("submissionWindowId") REFERENCES "SubmissionWindow"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- RoundSubmissionVisibility
|
||||
ALTER TABLE "RoundSubmissionVisibility" ADD CONSTRAINT "RoundSubmissionVisibility_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "RoundSubmissionVisibility" ADD CONSTRAINT "RoundSubmissionVisibility_submissionWindowId_fkey" FOREIGN KEY ("submissionWindowId") REFERENCES "SubmissionWindow"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AssignmentIntent
|
||||
ALTER TABLE "AssignmentIntent" ADD CONSTRAINT "AssignmentIntent_juryGroupMemberId_fkey" FOREIGN KEY ("juryGroupMemberId") REFERENCES "JuryGroupMember"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "AssignmentIntent" ADD CONSTRAINT "AssignmentIntent_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "AssignmentIntent" ADD CONSTRAINT "AssignmentIntent_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AssignmentException
|
||||
ALTER TABLE "AssignmentException" ADD CONSTRAINT "AssignmentException_assignmentId_fkey" FOREIGN KEY ("assignmentId") REFERENCES "Assignment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "AssignmentException" ADD CONSTRAINT "AssignmentException_approvedById_fkey" FOREIGN KEY ("approvedById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- MentorFile
|
||||
ALTER TABLE "MentorFile" ADD CONSTRAINT "MentorFile_mentorAssignmentId_fkey" FOREIGN KEY ("mentorAssignmentId") REFERENCES "MentorAssignment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "MentorFile" ADD CONSTRAINT "MentorFile_uploadedByUserId_fkey" FOREIGN KEY ("uploadedByUserId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
ALTER TABLE "MentorFile" ADD CONSTRAINT "MentorFile_promotedByUserId_fkey" FOREIGN KEY ("promotedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "MentorFile" ADD CONSTRAINT "MentorFile_promotedToFileId_fkey" FOREIGN KEY ("promotedToFileId") REFERENCES "ProjectFile"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- MentorFileComment
|
||||
ALTER TABLE "MentorFileComment" ADD CONSTRAINT "MentorFileComment_mentorFileId_fkey" FOREIGN KEY ("mentorFileId") REFERENCES "MentorFile"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "MentorFileComment" ADD CONSTRAINT "MentorFileComment_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
ALTER TABLE "MentorFileComment" ADD CONSTRAINT "MentorFileComment_parentCommentId_fkey" FOREIGN KEY ("parentCommentId") REFERENCES "MentorFileComment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- SubmissionPromotionEvent
|
||||
ALTER TABLE "SubmissionPromotionEvent" ADD CONSTRAINT "SubmissionPromotionEvent_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "SubmissionPromotionEvent" ADD CONSTRAINT "SubmissionPromotionEvent_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "SubmissionPromotionEvent" ADD CONSTRAINT "SubmissionPromotionEvent_sourceFileId_fkey" FOREIGN KEY ("sourceFileId") REFERENCES "MentorFile"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "SubmissionPromotionEvent" ADD CONSTRAINT "SubmissionPromotionEvent_promotedById_fkey" FOREIGN KEY ("promotedById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- DeliberationSession
|
||||
ALTER TABLE "DeliberationSession" ADD CONSTRAINT "DeliberationSession_competitionId_fkey" FOREIGN KEY ("competitionId") REFERENCES "Competition"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "DeliberationSession" ADD CONSTRAINT "DeliberationSession_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- DeliberationVote
|
||||
ALTER TABLE "DeliberationVote" ADD CONSTRAINT "DeliberationVote_sessionId_fkey" FOREIGN KEY ("sessionId") REFERENCES "DeliberationSession"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "DeliberationVote" ADD CONSTRAINT "DeliberationVote_juryMemberId_fkey" FOREIGN KEY ("juryMemberId") REFERENCES "JuryGroupMember"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "DeliberationVote" ADD CONSTRAINT "DeliberationVote_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- DeliberationResult
|
||||
ALTER TABLE "DeliberationResult" ADD CONSTRAINT "DeliberationResult_sessionId_fkey" FOREIGN KEY ("sessionId") REFERENCES "DeliberationSession"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "DeliberationResult" ADD CONSTRAINT "DeliberationResult_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- DeliberationParticipant
|
||||
ALTER TABLE "DeliberationParticipant" ADD CONSTRAINT "DeliberationParticipant_sessionId_fkey" FOREIGN KEY ("sessionId") REFERENCES "DeliberationSession"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "DeliberationParticipant" ADD CONSTRAINT "DeliberationParticipant_userId_fkey" FOREIGN KEY ("userId") REFERENCES "JuryGroupMember"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "DeliberationParticipant" ADD CONSTRAINT "DeliberationParticipant_replacedById_fkey" FOREIGN KEY ("replacedById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- ResultLock
|
||||
ALTER TABLE "ResultLock" ADD CONSTRAINT "ResultLock_competitionId_fkey" FOREIGN KEY ("competitionId") REFERENCES "Competition"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "ResultLock" ADD CONSTRAINT "ResultLock_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "ResultLock" ADD CONSTRAINT "ResultLock_lockedById_fkey" FOREIGN KEY ("lockedById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- ResultUnlockEvent
|
||||
ALTER TABLE "ResultUnlockEvent" ADD CONSTRAINT "ResultUnlockEvent_resultLockId_fkey" FOREIGN KEY ("resultLockId") REFERENCES "ResultLock"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "ResultUnlockEvent" ADD CONSTRAINT "ResultUnlockEvent_unlockedById_fkey" FOREIGN KEY ("unlockedById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- FKs on modified existing tables
|
||||
ALTER TABLE "Assignment" ADD CONSTRAINT "Assignment_juryGroupId_fkey" FOREIGN KEY ("juryGroupId") REFERENCES "JuryGroup"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "SpecialAward" ADD CONSTRAINT "SpecialAward_competitionId_fkey" FOREIGN KEY ("competitionId") REFERENCES "Competition"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "SpecialAward" ADD CONSTRAINT "SpecialAward_evaluationRoundId_fkey" FOREIGN KEY ("evaluationRoundId") REFERENCES "Round"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "SpecialAward" ADD CONSTRAINT "SpecialAward_juryGroupId_fkey" FOREIGN KEY ("juryGroupId") REFERENCES "JuryGroup"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "MentorMessage" ADD CONSTRAINT "MentorMessage_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "MentorAssignment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "ProjectFile" ADD CONSTRAINT "ProjectFile_submissionWindowId_fkey" FOREIGN KEY ("submissionWindowId") REFERENCES "SubmissionWindow"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
ALTER TABLE "ProjectFile" ADD CONSTRAINT "ProjectFile_submissionFileRequirementId_fkey" FOREIGN KEY ("submissionFileRequirementId") REFERENCES "SubmissionFileRequirement"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,3 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "JuryGroupMember" ADD COLUMN "selfServiceCap" INTEGER,
|
||||
ADD COLUMN "selfServiceRatio" DOUBLE PRECISION;
|
||||
300
prisma/migrations/20260215200000_phase7_fk_renames/migration.sql
Normal file
300
prisma/migrations/20260215200000_phase7_fk_renames/migration.sql
Normal file
@@ -0,0 +1,300 @@
|
||||
-- =============================================================================
|
||||
-- Phase 7/8 Migration Part 1: Rename stageId → roundId on 15 tables
|
||||
-- =============================================================================
|
||||
-- This migration renames stageId columns to roundId and updates FK constraints
|
||||
-- to point to the Round table instead of Stage table.
|
||||
|
||||
-- ─── 1. EvaluationForm ───────────────────────────────────────────────────────
|
||||
|
||||
-- Drop FK constraint
|
||||
ALTER TABLE "EvaluationForm" DROP CONSTRAINT IF EXISTS "EvaluationForm_stageId_fkey";
|
||||
|
||||
-- Drop indexes
|
||||
DROP INDEX IF EXISTS "EvaluationForm_stageId_version_key";
|
||||
DROP INDEX IF EXISTS "EvaluationForm_stageId_isActive_idx";
|
||||
|
||||
-- Rename column
|
||||
ALTER TABLE "EvaluationForm" RENAME COLUMN "stageId" TO "roundId";
|
||||
|
||||
-- Recreate indexes with new name
|
||||
CREATE UNIQUE INDEX "EvaluationForm_roundId_version_key" ON "EvaluationForm"("roundId", "version");
|
||||
CREATE INDEX "EvaluationForm_roundId_isActive_idx" ON "EvaluationForm"("roundId", "isActive");
|
||||
|
||||
-- Recreate FK pointing to Round
|
||||
ALTER TABLE "EvaluationForm" ADD CONSTRAINT "EvaluationForm_roundId_fkey"
|
||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- ─── 2. FileRequirement ──────────────────────────────────────────────────────
|
||||
|
||||
-- Drop FK constraint
|
||||
ALTER TABLE "FileRequirement" DROP CONSTRAINT IF EXISTS "FileRequirement_stageId_fkey";
|
||||
|
||||
-- Drop index
|
||||
DROP INDEX IF EXISTS "FileRequirement_stageId_idx";
|
||||
|
||||
-- Rename column
|
||||
ALTER TABLE "FileRequirement" RENAME COLUMN "stageId" TO "roundId";
|
||||
|
||||
-- Recreate index
|
||||
CREATE INDEX "FileRequirement_roundId_idx" ON "FileRequirement"("roundId");
|
||||
|
||||
-- Recreate FK pointing to Round
|
||||
ALTER TABLE "FileRequirement" ADD CONSTRAINT "FileRequirement_roundId_fkey"
|
||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- ─── 3. Assignment ───────────────────────────────────────────────────────────
|
||||
|
||||
-- Drop FK constraint
|
||||
ALTER TABLE "Assignment" DROP CONSTRAINT IF EXISTS "Assignment_stageId_fkey";
|
||||
|
||||
-- Drop indexes and unique constraint
|
||||
DROP INDEX IF EXISTS "Assignment_userId_projectId_stageId_key";
|
||||
DROP INDEX IF EXISTS "Assignment_stageId_idx";
|
||||
|
||||
-- Rename column
|
||||
ALTER TABLE "Assignment" RENAME COLUMN "stageId" TO "roundId";
|
||||
|
||||
-- Recreate unique constraint and index with new name
|
||||
CREATE UNIQUE INDEX "Assignment_userId_projectId_roundId_key" ON "Assignment"("userId", "projectId", "roundId");
|
||||
CREATE INDEX "Assignment_roundId_idx" ON "Assignment"("roundId");
|
||||
|
||||
-- Recreate FK pointing to Round
|
||||
ALTER TABLE "Assignment" ADD CONSTRAINT "Assignment_roundId_fkey"
|
||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- ─── 4. GracePeriod ──────────────────────────────────────────────────────────
|
||||
|
||||
-- Drop FK constraint
|
||||
ALTER TABLE "GracePeriod" DROP CONSTRAINT IF EXISTS "GracePeriod_stageId_fkey";
|
||||
|
||||
-- Drop indexes
|
||||
DROP INDEX IF EXISTS "GracePeriod_stageId_idx";
|
||||
DROP INDEX IF EXISTS "GracePeriod_stageId_userId_extendedUntil_idx";
|
||||
|
||||
-- Rename column
|
||||
ALTER TABLE "GracePeriod" RENAME COLUMN "stageId" TO "roundId";
|
||||
|
||||
-- Recreate indexes
|
||||
CREATE INDEX "GracePeriod_roundId_idx" ON "GracePeriod"("roundId");
|
||||
CREATE INDEX "GracePeriod_roundId_userId_extendedUntil_idx" ON "GracePeriod"("roundId", "userId", "extendedUntil");
|
||||
|
||||
-- Recreate FK pointing to Round
|
||||
ALTER TABLE "GracePeriod" ADD CONSTRAINT "GracePeriod_roundId_fkey"
|
||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- ─── 5. LiveVotingSession ────────────────────────────────────────────────────
|
||||
|
||||
-- Drop FK constraint
|
||||
ALTER TABLE "LiveVotingSession" DROP CONSTRAINT IF EXISTS "LiveVotingSession_stageId_fkey";
|
||||
|
||||
-- Drop unique index
|
||||
DROP INDEX IF EXISTS "LiveVotingSession_stageId_key";
|
||||
|
||||
-- Rename column
|
||||
ALTER TABLE "LiveVotingSession" RENAME COLUMN "stageId" TO "roundId";
|
||||
|
||||
-- Recreate unique index
|
||||
CREATE UNIQUE INDEX "LiveVotingSession_roundId_key" ON "LiveVotingSession"("roundId");
|
||||
|
||||
-- Recreate FK pointing to Round (nullable)
|
||||
ALTER TABLE "LiveVotingSession" ADD CONSTRAINT "LiveVotingSession_roundId_fkey"
|
||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- ─── 6. FilteringRule ────────────────────────────────────────────────────────
|
||||
|
||||
-- Drop FK constraint
|
||||
ALTER TABLE "FilteringRule" DROP CONSTRAINT IF EXISTS "FilteringRule_stageId_fkey";
|
||||
|
||||
-- Drop index
|
||||
DROP INDEX IF EXISTS "FilteringRule_stageId_idx";
|
||||
|
||||
-- Rename column
|
||||
ALTER TABLE "FilteringRule" RENAME COLUMN "stageId" TO "roundId";
|
||||
|
||||
-- Recreate index
|
||||
CREATE INDEX "FilteringRule_roundId_idx" ON "FilteringRule"("roundId");
|
||||
|
||||
-- Recreate FK pointing to Round
|
||||
ALTER TABLE "FilteringRule" ADD CONSTRAINT "FilteringRule_roundId_fkey"
|
||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- ─── 7. FilteringResult ──────────────────────────────────────────────────────
|
||||
|
||||
-- Drop FK constraint
|
||||
ALTER TABLE "FilteringResult" DROP CONSTRAINT IF EXISTS "FilteringResult_stageId_fkey";
|
||||
|
||||
-- Drop indexes and unique constraint
|
||||
DROP INDEX IF EXISTS "FilteringResult_stageId_projectId_key";
|
||||
DROP INDEX IF EXISTS "FilteringResult_stageId_idx";
|
||||
|
||||
-- Rename column
|
||||
ALTER TABLE "FilteringResult" RENAME COLUMN "stageId" TO "roundId";
|
||||
|
||||
-- Recreate unique constraint and index
|
||||
CREATE UNIQUE INDEX "FilteringResult_roundId_projectId_key" ON "FilteringResult"("roundId", "projectId");
|
||||
CREATE INDEX "FilteringResult_roundId_idx" ON "FilteringResult"("roundId");
|
||||
|
||||
-- Recreate FK pointing to Round
|
||||
ALTER TABLE "FilteringResult" ADD CONSTRAINT "FilteringResult_roundId_fkey"
|
||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- ─── 8. FilteringJob ─────────────────────────────────────────────────────────
|
||||
|
||||
-- Drop FK constraint
|
||||
ALTER TABLE "FilteringJob" DROP CONSTRAINT IF EXISTS "FilteringJob_stageId_fkey";
|
||||
|
||||
-- Drop index
|
||||
DROP INDEX IF EXISTS "FilteringJob_stageId_idx";
|
||||
|
||||
-- Rename column
|
||||
ALTER TABLE "FilteringJob" RENAME COLUMN "stageId" TO "roundId";
|
||||
|
||||
-- Recreate index
|
||||
CREATE INDEX "FilteringJob_roundId_idx" ON "FilteringJob"("roundId");
|
||||
|
||||
-- Recreate FK pointing to Round
|
||||
ALTER TABLE "FilteringJob" ADD CONSTRAINT "FilteringJob_roundId_fkey"
|
||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- ─── 9. AssignmentJob ────────────────────────────────────────────────────────
|
||||
|
||||
-- Drop FK constraint
|
||||
ALTER TABLE "AssignmentJob" DROP CONSTRAINT IF EXISTS "AssignmentJob_stageId_fkey";
|
||||
|
||||
-- Drop index
|
||||
DROP INDEX IF EXISTS "AssignmentJob_stageId_idx";
|
||||
|
||||
-- Rename column
|
||||
ALTER TABLE "AssignmentJob" RENAME COLUMN "stageId" TO "roundId";
|
||||
|
||||
-- Recreate index
|
||||
CREATE INDEX "AssignmentJob_roundId_idx" ON "AssignmentJob"("roundId");
|
||||
|
||||
-- Recreate FK pointing to Round
|
||||
ALTER TABLE "AssignmentJob" ADD CONSTRAINT "AssignmentJob_roundId_fkey"
|
||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- ─── 10. ReminderLog ─────────────────────────────────────────────────────────
|
||||
|
||||
-- Drop FK constraint
|
||||
ALTER TABLE "ReminderLog" DROP CONSTRAINT IF EXISTS "ReminderLog_stageId_fkey";
|
||||
|
||||
-- Drop indexes and unique constraint
|
||||
DROP INDEX IF EXISTS "ReminderLog_stageId_userId_type_key";
|
||||
DROP INDEX IF EXISTS "ReminderLog_stageId_idx";
|
||||
|
||||
-- Rename column
|
||||
ALTER TABLE "ReminderLog" RENAME COLUMN "stageId" TO "roundId";
|
||||
|
||||
-- Recreate unique constraint and index
|
||||
CREATE UNIQUE INDEX "ReminderLog_roundId_userId_type_key" ON "ReminderLog"("roundId", "userId", "type");
|
||||
CREATE INDEX "ReminderLog_roundId_idx" ON "ReminderLog"("roundId");
|
||||
|
||||
-- Recreate FK pointing to Round
|
||||
ALTER TABLE "ReminderLog" ADD CONSTRAINT "ReminderLog_roundId_fkey"
|
||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- ─── 11. EvaluationSummary ───────────────────────────────────────────────────
|
||||
|
||||
-- Drop FK constraint
|
||||
ALTER TABLE "EvaluationSummary" DROP CONSTRAINT IF EXISTS "EvaluationSummary_stageId_fkey";
|
||||
|
||||
-- Drop indexes and unique constraint
|
||||
DROP INDEX IF EXISTS "EvaluationSummary_projectId_stageId_key";
|
||||
DROP INDEX IF EXISTS "EvaluationSummary_stageId_idx";
|
||||
|
||||
-- Rename column
|
||||
ALTER TABLE "EvaluationSummary" RENAME COLUMN "stageId" TO "roundId";
|
||||
|
||||
-- Recreate unique constraint and index
|
||||
CREATE UNIQUE INDEX "EvaluationSummary_projectId_roundId_key" ON "EvaluationSummary"("projectId", "roundId");
|
||||
CREATE INDEX "EvaluationSummary_roundId_idx" ON "EvaluationSummary"("roundId");
|
||||
|
||||
-- Recreate FK pointing to Round
|
||||
ALTER TABLE "EvaluationSummary" ADD CONSTRAINT "EvaluationSummary_roundId_fkey"
|
||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- ─── 12. EvaluationDiscussion ────────────────────────────────────────────────
|
||||
|
||||
-- Drop FK constraint
|
||||
ALTER TABLE "EvaluationDiscussion" DROP CONSTRAINT IF EXISTS "EvaluationDiscussion_stageId_fkey";
|
||||
|
||||
-- Drop indexes and unique constraint
|
||||
DROP INDEX IF EXISTS "EvaluationDiscussion_projectId_stageId_key";
|
||||
DROP INDEX IF EXISTS "EvaluationDiscussion_stageId_idx";
|
||||
|
||||
-- Rename column
|
||||
ALTER TABLE "EvaluationDiscussion" RENAME COLUMN "stageId" TO "roundId";
|
||||
|
||||
-- Recreate unique constraint and index
|
||||
CREATE UNIQUE INDEX "EvaluationDiscussion_projectId_roundId_key" ON "EvaluationDiscussion"("projectId", "roundId");
|
||||
CREATE INDEX "EvaluationDiscussion_roundId_idx" ON "EvaluationDiscussion"("roundId");
|
||||
|
||||
-- Recreate FK pointing to Round
|
||||
ALTER TABLE "EvaluationDiscussion" ADD CONSTRAINT "EvaluationDiscussion_roundId_fkey"
|
||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- ─── 13. Message ─────────────────────────────────────────────────────────────
|
||||
|
||||
-- Drop FK constraint
|
||||
ALTER TABLE "Message" DROP CONSTRAINT IF EXISTS "Message_stageId_fkey";
|
||||
|
||||
-- Drop index
|
||||
DROP INDEX IF EXISTS "Message_stageId_idx";
|
||||
|
||||
-- Rename column (nullable, so SET NULL on delete)
|
||||
ALTER TABLE "Message" RENAME COLUMN "stageId" TO "roundId";
|
||||
|
||||
-- Recreate index
|
||||
CREATE INDEX "Message_roundId_idx" ON "Message"("roundId");
|
||||
|
||||
-- Recreate FK pointing to Round with SET NULL
|
||||
ALTER TABLE "Message" ADD CONSTRAINT "Message_roundId_fkey"
|
||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- ─── 14. Cohort ──────────────────────────────────────────────────────────────
|
||||
|
||||
-- Drop FK constraint
|
||||
ALTER TABLE "Cohort" DROP CONSTRAINT IF EXISTS "Cohort_stageId_fkey";
|
||||
|
||||
-- Drop indexes
|
||||
DROP INDEX IF EXISTS "Cohort_stageId_idx";
|
||||
|
||||
-- Rename column
|
||||
ALTER TABLE "Cohort" RENAME COLUMN "stageId" TO "roundId";
|
||||
|
||||
-- Recreate index
|
||||
CREATE INDEX "Cohort_roundId_idx" ON "Cohort"("roundId");
|
||||
|
||||
-- Recreate FK pointing to Round
|
||||
ALTER TABLE "Cohort" ADD CONSTRAINT "Cohort_roundId_fkey"
|
||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- ─── 15. LiveProgressCursor ──────────────────────────────────────────────────
|
||||
|
||||
-- Drop FK constraint
|
||||
ALTER TABLE "LiveProgressCursor" DROP CONSTRAINT IF EXISTS "LiveProgressCursor_stageId_fkey";
|
||||
|
||||
-- Drop unique index
|
||||
DROP INDEX IF EXISTS "LiveProgressCursor_stageId_key";
|
||||
|
||||
-- Rename column
|
||||
ALTER TABLE "LiveProgressCursor" RENAME COLUMN "stageId" TO "roundId";
|
||||
|
||||
-- Recreate unique index
|
||||
CREATE UNIQUE INDEX "LiveProgressCursor_roundId_key" ON "LiveProgressCursor"("roundId");
|
||||
|
||||
-- Recreate FK pointing to Round
|
||||
ALTER TABLE "LiveProgressCursor" ADD CONSTRAINT "LiveProgressCursor_roundId_fkey"
|
||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- ─── 16. SpecialAward: Drop trackId column ───────────────────────────────────
|
||||
|
||||
-- Drop FK constraint
|
||||
ALTER TABLE "SpecialAward" DROP CONSTRAINT IF EXISTS "SpecialAward_trackId_fkey";
|
||||
|
||||
-- Drop unique index
|
||||
DROP INDEX IF EXISTS "SpecialAward_trackId_key";
|
||||
|
||||
-- Drop column
|
||||
ALTER TABLE "SpecialAward" DROP COLUMN IF EXISTS "trackId";
|
||||
@@ -0,0 +1,31 @@
|
||||
-- =============================================================================
|
||||
-- Phase 7/8 Migration Part 2: Drop legacy Pipeline/Track/Stage tables and enums
|
||||
-- =============================================================================
|
||||
-- This migration removes the old stage-based architecture tables and enums.
|
||||
-- All data has been migrated to Competition/Round architecture.
|
||||
|
||||
-- ─── Drop Tables in FK-safe order ────────────────────────────────────────────
|
||||
|
||||
-- Drop ProjectStageState (references Track and Stage)
|
||||
DROP TABLE IF EXISTS "ProjectStageState" CASCADE;
|
||||
|
||||
-- Drop StageTransition (references Stage)
|
||||
DROP TABLE IF EXISTS "StageTransition" CASCADE;
|
||||
|
||||
-- Drop Stage (references Track)
|
||||
DROP TABLE IF EXISTS "Stage" CASCADE;
|
||||
|
||||
-- Drop Track (references Pipeline)
|
||||
DROP TABLE IF EXISTS "Track" CASCADE;
|
||||
|
||||
-- Drop Pipeline
|
||||
DROP TABLE IF EXISTS "Pipeline" CASCADE;
|
||||
|
||||
-- ─── Drop Enums ──────────────────────────────────────────────────────────────
|
||||
|
||||
DROP TYPE IF EXISTS "StageType";
|
||||
DROP TYPE IF EXISTS "TrackKind";
|
||||
DROP TYPE IF EXISTS "RoutingMode";
|
||||
DROP TYPE IF EXISTS "StageStatus";
|
||||
DROP TYPE IF EXISTS "ProjectStageStateValue";
|
||||
DROP TYPE IF EXISTS "DecisionMode";
|
||||
1049
prisma/schema.prisma
1049
prisma/schema.prisma
File diff suppressed because it is too large
Load Diff
761
prisma/seed.ts
761
prisma/seed.ts
@@ -7,16 +7,18 @@ import {
|
||||
SettingCategory,
|
||||
CompetitionCategory,
|
||||
OceanIssue,
|
||||
StageType,
|
||||
TrackKind,
|
||||
RoutingMode,
|
||||
DecisionMode,
|
||||
StageStatus,
|
||||
ProjectStageStateValue,
|
||||
ProjectStatus,
|
||||
SubmissionSource,
|
||||
// Competition architecture enums
|
||||
CompetitionStatus,
|
||||
RoundType,
|
||||
RoundStatus,
|
||||
CapMode,
|
||||
JuryGroupMemberRole,
|
||||
AdvancementRuleType,
|
||||
} from '@prisma/client'
|
||||
import bcrypt from 'bcryptjs'
|
||||
import { defaultRoundConfig } from '../src/types/competition-configs'
|
||||
import { readFileSync } from 'fs'
|
||||
import { parse } from 'csv-parse/sync'
|
||||
import { join, dirname } from 'path'
|
||||
@@ -424,368 +426,10 @@ async function main() {
|
||||
})
|
||||
console.log(` ✓ Program: ${program.name} ${program.year}`)
|
||||
|
||||
// ==========================================================================
|
||||
// 7. Pipeline
|
||||
// ==========================================================================
|
||||
console.log('\n🔗 Creating pipeline...')
|
||||
|
||||
const pipeline = await prisma.pipeline.upsert({
|
||||
where: { slug: 'mopc-2026' },
|
||||
update: {
|
||||
name: 'MOPC 2026 Main Pipeline',
|
||||
status: 'ACTIVE',
|
||||
},
|
||||
create: {
|
||||
programId: program.id,
|
||||
name: 'MOPC 2026 Main Pipeline',
|
||||
slug: 'mopc-2026',
|
||||
status: 'ACTIVE',
|
||||
settingsJson: {
|
||||
description: 'Main pipeline for MOPC 2026 competition',
|
||||
allowParallelTracks: true,
|
||||
autoAdvanceOnClose: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
console.log(` ✓ Pipeline: ${pipeline.name}`)
|
||||
// Legacy Pipeline/Track/Stage system removed - Competition/Round architecture now in use
|
||||
|
||||
// ==========================================================================
|
||||
// 8. Tracks (4)
|
||||
// ==========================================================================
|
||||
console.log('\n🛤️ Creating tracks...')
|
||||
|
||||
const mainTrack = await prisma.track.upsert({
|
||||
where: { pipelineId_slug: { pipelineId: pipeline.id, slug: 'main' } },
|
||||
update: { name: 'Main Competition' },
|
||||
create: {
|
||||
pipelineId: pipeline.id,
|
||||
name: 'Main Competition',
|
||||
slug: 'main',
|
||||
kind: TrackKind.MAIN,
|
||||
sortOrder: 0,
|
||||
settingsJson: { description: 'Primary competition track for all applicants' },
|
||||
},
|
||||
})
|
||||
|
||||
const innovationTrack = await prisma.track.upsert({
|
||||
where: { pipelineId_slug: { pipelineId: pipeline.id, slug: 'innovation-award' } },
|
||||
update: { name: 'Ocean Innovation Award' },
|
||||
create: {
|
||||
pipelineId: pipeline.id,
|
||||
name: 'Ocean Innovation Award',
|
||||
slug: 'innovation-award',
|
||||
kind: TrackKind.AWARD,
|
||||
routingMode: RoutingMode.SHARED,
|
||||
decisionMode: DecisionMode.JURY_VOTE,
|
||||
sortOrder: 1,
|
||||
settingsJson: { description: 'Award for most innovative ocean technology' },
|
||||
},
|
||||
})
|
||||
|
||||
const impactTrack = await prisma.track.upsert({
|
||||
where: { pipelineId_slug: { pipelineId: pipeline.id, slug: 'impact-award' } },
|
||||
update: { name: 'Ocean Impact Award' },
|
||||
create: {
|
||||
pipelineId: pipeline.id,
|
||||
name: 'Ocean Impact Award',
|
||||
slug: 'impact-award',
|
||||
kind: TrackKind.AWARD,
|
||||
routingMode: RoutingMode.EXCLUSIVE,
|
||||
decisionMode: DecisionMode.AWARD_MASTER_DECISION,
|
||||
sortOrder: 2,
|
||||
settingsJson: { description: 'Award for highest community impact on ocean health' },
|
||||
},
|
||||
})
|
||||
|
||||
const peoplesTrack = await prisma.track.upsert({
|
||||
where: { pipelineId_slug: { pipelineId: pipeline.id, slug: 'peoples-choice' } },
|
||||
update: { name: "People's Choice" },
|
||||
create: {
|
||||
pipelineId: pipeline.id,
|
||||
name: "People's Choice",
|
||||
slug: 'peoples-choice',
|
||||
kind: TrackKind.SHOWCASE,
|
||||
routingMode: RoutingMode.SHARED,
|
||||
sortOrder: 3,
|
||||
settingsJson: { description: 'Public audience voting for fan favorite' },
|
||||
},
|
||||
})
|
||||
|
||||
console.log(` ✓ Main Competition (MAIN)`)
|
||||
console.log(` ✓ Ocean Innovation Award (AWARD, SHARED)`)
|
||||
console.log(` ✓ Ocean Impact Award (AWARD, EXCLUSIVE)`)
|
||||
console.log(` ✓ People's Choice (SHOWCASE, SHARED)`)
|
||||
|
||||
// ==========================================================================
|
||||
// 9. Stages
|
||||
// ==========================================================================
|
||||
console.log('\n📊 Creating stages...')
|
||||
|
||||
// --- Main track stages ---
|
||||
const mainStages = await Promise.all([
|
||||
prisma.stage.upsert({
|
||||
where: { trackId_slug: { trackId: mainTrack.id, slug: 'intake' } },
|
||||
update: {},
|
||||
create: {
|
||||
trackId: mainTrack.id,
|
||||
stageType: StageType.INTAKE,
|
||||
name: 'Application Intake',
|
||||
slug: 'intake',
|
||||
status: StageStatus.STAGE_CLOSED,
|
||||
sortOrder: 0,
|
||||
configJson: {
|
||||
fileRequirements: [
|
||||
{ name: 'Executive Summary', type: 'PDF', maxSizeMB: 50, required: true },
|
||||
{ name: 'Video Pitch', type: 'VIDEO', maxSizeMB: 500, required: false },
|
||||
],
|
||||
deadline: '2026-01-31T23:59:00Z',
|
||||
maxSubmissions: 1,
|
||||
},
|
||||
},
|
||||
}),
|
||||
prisma.stage.upsert({
|
||||
where: { trackId_slug: { trackId: mainTrack.id, slug: 'screening' } },
|
||||
update: {},
|
||||
create: {
|
||||
trackId: mainTrack.id,
|
||||
stageType: StageType.FILTER,
|
||||
name: 'AI Screening',
|
||||
slug: 'screening',
|
||||
status: StageStatus.STAGE_ACTIVE,
|
||||
sortOrder: 1,
|
||||
configJson: {
|
||||
deterministic: {
|
||||
rules: [
|
||||
{ field: 'competitionCategory', operator: 'is_not_null', label: 'Has category' },
|
||||
{ field: 'description', operator: 'min_length', value: 50, label: 'Description >= 50 chars' },
|
||||
],
|
||||
},
|
||||
ai: { rubricVersion: '2026-v1', model: 'gpt-4o' },
|
||||
confidenceBands: {
|
||||
high: { threshold: 0.8, action: 'auto_pass' },
|
||||
medium: { threshold: 0.5, action: 'manual_review' },
|
||||
low: { threshold: 0, action: 'auto_reject' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
prisma.stage.upsert({
|
||||
where: { trackId_slug: { trackId: mainTrack.id, slug: 'evaluation' } },
|
||||
update: {},
|
||||
create: {
|
||||
trackId: mainTrack.id,
|
||||
stageType: StageType.EVALUATION,
|
||||
name: 'Expert Evaluation',
|
||||
slug: 'evaluation',
|
||||
status: StageStatus.STAGE_DRAFT,
|
||||
sortOrder: 2,
|
||||
configJson: {
|
||||
criteriaVersion: '2026-v1',
|
||||
assignmentStrategy: 'smart',
|
||||
requiredReviews: 3,
|
||||
minAssignmentsPerJuror: 5,
|
||||
maxAssignmentsPerJuror: 20,
|
||||
},
|
||||
},
|
||||
}),
|
||||
prisma.stage.upsert({
|
||||
where: { trackId_slug: { trackId: mainTrack.id, slug: 'selection' } },
|
||||
update: {},
|
||||
create: {
|
||||
trackId: mainTrack.id,
|
||||
stageType: StageType.SELECTION,
|
||||
name: 'Semi-Final Selection',
|
||||
slug: 'selection',
|
||||
status: StageStatus.STAGE_DRAFT,
|
||||
sortOrder: 3,
|
||||
configJson: {
|
||||
rankingSource: 'evaluation_scores',
|
||||
finalistTarget: 6,
|
||||
selectionMethod: 'top_n_with_admin_override',
|
||||
},
|
||||
},
|
||||
}),
|
||||
prisma.stage.upsert({
|
||||
where: { trackId_slug: { trackId: mainTrack.id, slug: 'grand-final' } },
|
||||
update: {},
|
||||
create: {
|
||||
trackId: mainTrack.id,
|
||||
stageType: StageType.LIVE_FINAL,
|
||||
name: 'Grand Final',
|
||||
slug: 'grand-final',
|
||||
status: StageStatus.STAGE_DRAFT,
|
||||
sortOrder: 4,
|
||||
configJson: {
|
||||
sessionMode: 'cohort',
|
||||
votingEnabled: true,
|
||||
audienceVoting: true,
|
||||
audienceVoteWeight: 0.2,
|
||||
presentationDurationMinutes: 10,
|
||||
qaDurationMinutes: 5,
|
||||
},
|
||||
},
|
||||
}),
|
||||
prisma.stage.upsert({
|
||||
where: { trackId_slug: { trackId: mainTrack.id, slug: 'results' } },
|
||||
update: {},
|
||||
create: {
|
||||
trackId: mainTrack.id,
|
||||
stageType: StageType.RESULTS,
|
||||
name: 'Results & Awards',
|
||||
slug: 'results',
|
||||
status: StageStatus.STAGE_DRAFT,
|
||||
sortOrder: 5,
|
||||
configJson: {
|
||||
rankingWeights: { juryScore: 0.8, audienceScore: 0.2 },
|
||||
publicationPolicy: 'after_ceremony',
|
||||
announcementDate: '2026-06-15',
|
||||
},
|
||||
},
|
||||
}),
|
||||
])
|
||||
|
||||
// --- Innovation Award track stages ---
|
||||
const innovationStages = await Promise.all([
|
||||
prisma.stage.upsert({
|
||||
where: { trackId_slug: { trackId: innovationTrack.id, slug: 'innovation-review' } },
|
||||
update: {},
|
||||
create: {
|
||||
trackId: innovationTrack.id,
|
||||
stageType: StageType.EVALUATION,
|
||||
name: 'Innovation Jury Review',
|
||||
slug: 'innovation-review',
|
||||
status: StageStatus.STAGE_DRAFT,
|
||||
sortOrder: 0,
|
||||
configJson: {
|
||||
criteriaVersion: 'innovation-2026-v1',
|
||||
assignmentStrategy: 'manual',
|
||||
requiredReviews: 2,
|
||||
},
|
||||
},
|
||||
}),
|
||||
prisma.stage.upsert({
|
||||
where: { trackId_slug: { trackId: innovationTrack.id, slug: 'innovation-results' } },
|
||||
update: {},
|
||||
create: {
|
||||
trackId: innovationTrack.id,
|
||||
stageType: StageType.RESULTS,
|
||||
name: 'Innovation Results',
|
||||
slug: 'innovation-results',
|
||||
status: StageStatus.STAGE_DRAFT,
|
||||
sortOrder: 1,
|
||||
configJson: { publicationPolicy: 'after_ceremony' },
|
||||
},
|
||||
}),
|
||||
])
|
||||
|
||||
// --- Impact Award track stages ---
|
||||
const impactStages = await Promise.all([
|
||||
prisma.stage.upsert({
|
||||
where: { trackId_slug: { trackId: impactTrack.id, slug: 'impact-review' } },
|
||||
update: {},
|
||||
create: {
|
||||
trackId: impactTrack.id,
|
||||
stageType: StageType.EVALUATION,
|
||||
name: 'Impact Assessment',
|
||||
slug: 'impact-review',
|
||||
status: StageStatus.STAGE_DRAFT,
|
||||
sortOrder: 0,
|
||||
configJson: {
|
||||
criteriaVersion: 'impact-2026-v1',
|
||||
assignmentStrategy: 'award_master',
|
||||
requiredReviews: 1,
|
||||
},
|
||||
},
|
||||
}),
|
||||
prisma.stage.upsert({
|
||||
where: { trackId_slug: { trackId: impactTrack.id, slug: 'impact-results' } },
|
||||
update: {},
|
||||
create: {
|
||||
trackId: impactTrack.id,
|
||||
stageType: StageType.RESULTS,
|
||||
name: 'Impact Results',
|
||||
slug: 'impact-results',
|
||||
status: StageStatus.STAGE_DRAFT,
|
||||
sortOrder: 1,
|
||||
configJson: { publicationPolicy: 'after_ceremony' },
|
||||
},
|
||||
}),
|
||||
])
|
||||
|
||||
// --- People's Choice track stages ---
|
||||
const peoplesStages = await Promise.all([
|
||||
prisma.stage.upsert({
|
||||
where: { trackId_slug: { trackId: peoplesTrack.id, slug: 'public-vote' } },
|
||||
update: {},
|
||||
create: {
|
||||
trackId: peoplesTrack.id,
|
||||
stageType: StageType.LIVE_FINAL,
|
||||
name: 'Public Voting',
|
||||
slug: 'public-vote',
|
||||
status: StageStatus.STAGE_DRAFT,
|
||||
sortOrder: 0,
|
||||
configJson: {
|
||||
votingMode: 'favorites',
|
||||
maxFavorites: 3,
|
||||
requireIdentification: false,
|
||||
votingDurationMinutes: 30,
|
||||
},
|
||||
},
|
||||
}),
|
||||
prisma.stage.upsert({
|
||||
where: { trackId_slug: { trackId: peoplesTrack.id, slug: 'peoples-results' } },
|
||||
update: {},
|
||||
create: {
|
||||
trackId: peoplesTrack.id,
|
||||
stageType: StageType.RESULTS,
|
||||
name: "People's Choice Results",
|
||||
slug: 'peoples-results',
|
||||
status: StageStatus.STAGE_DRAFT,
|
||||
sortOrder: 1,
|
||||
configJson: { publicationPolicy: 'after_ceremony' },
|
||||
},
|
||||
}),
|
||||
])
|
||||
|
||||
const allStages = [...mainStages, ...innovationStages, ...impactStages, ...peoplesStages]
|
||||
console.log(` ✓ Created ${allStages.length} stages across 4 tracks`)
|
||||
|
||||
// ==========================================================================
|
||||
// 10. Stage Transitions (linear within each track)
|
||||
// ==========================================================================
|
||||
console.log('\n🔀 Creating stage transitions...')
|
||||
|
||||
const trackStageGroups = [
|
||||
{ name: 'Main', stages: mainStages },
|
||||
{ name: 'Innovation', stages: innovationStages },
|
||||
{ name: 'Impact', stages: impactStages },
|
||||
{ name: "People's", stages: peoplesStages },
|
||||
]
|
||||
|
||||
let transitionCount = 0
|
||||
for (const group of trackStageGroups) {
|
||||
for (let i = 0; i < group.stages.length - 1; i++) {
|
||||
await prisma.stageTransition.upsert({
|
||||
where: {
|
||||
fromStageId_toStageId: {
|
||||
fromStageId: group.stages[i].id,
|
||||
toStageId: group.stages[i + 1].id,
|
||||
},
|
||||
},
|
||||
update: {},
|
||||
create: {
|
||||
fromStageId: group.stages[i].id,
|
||||
toStageId: group.stages[i + 1].id,
|
||||
isDefault: true,
|
||||
},
|
||||
})
|
||||
transitionCount++
|
||||
}
|
||||
}
|
||||
console.log(` ✓ Created ${transitionCount} transitions`)
|
||||
|
||||
// ==========================================================================
|
||||
// 11. Parse CSV & Create Applicants + Projects
|
||||
// 7. Parse CSV & Create Applicants + Projects
|
||||
// ==========================================================================
|
||||
console.log('\n📄 Checking for existing projects...')
|
||||
|
||||
@@ -820,9 +464,6 @@ async function main() {
|
||||
// Create applicant users and projects
|
||||
console.log('\n🚀 Creating applicant users and projects...')
|
||||
|
||||
const intakeStage = mainStages[0] // INTAKE - CLOSED
|
||||
const filterStage = mainStages[1] // FILTER - ACTIVE
|
||||
|
||||
let skippedNoEmail = 0
|
||||
for (let rowIdx = 0; rowIdx < validRecords.length; rowIdx++) {
|
||||
const row = validRecords[rowIdx]
|
||||
@@ -871,7 +512,7 @@ async function main() {
|
||||
})
|
||||
|
||||
// Create project
|
||||
const project = await prisma.project.create({
|
||||
await prisma.project.create({
|
||||
data: {
|
||||
programId: program.id,
|
||||
title: projectName || `Project by ${name}`,
|
||||
@@ -896,108 +537,325 @@ async function main() {
|
||||
},
|
||||
})
|
||||
|
||||
// Create ProjectStageState: INTAKE stage = PASSED (intake closed)
|
||||
await prisma.projectStageState.create({
|
||||
data: {
|
||||
projectId: project.id,
|
||||
trackId: mainTrack.id,
|
||||
stageId: intakeStage.id,
|
||||
state: ProjectStageStateValue.PASSED,
|
||||
enteredAt: new Date('2026-01-15'),
|
||||
exitedAt: new Date('2026-01-31'),
|
||||
},
|
||||
})
|
||||
|
||||
// Create ProjectStageState: FILTER stage = PENDING (current active stage)
|
||||
await prisma.projectStageState.create({
|
||||
data: {
|
||||
projectId: project.id,
|
||||
trackId: mainTrack.id,
|
||||
stageId: filterStage.id,
|
||||
state: ProjectStageStateValue.PENDING,
|
||||
enteredAt: new Date('2026-02-01'),
|
||||
},
|
||||
})
|
||||
|
||||
projectCount++
|
||||
if (projectCount % 50 === 0) {
|
||||
console.log(` ... ${projectCount} projects created`)
|
||||
}
|
||||
}
|
||||
|
||||
console.log(` ✓ Created ${projectCount} projects with stage states`)
|
||||
console.log(` ✓ Created ${projectCount} projects`)
|
||||
if (skippedNoEmail > 0) {
|
||||
console.log(` ⚠ Skipped ${skippedNoEmail} rows with no valid email`)
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
// 12. Evaluation Form (for Expert Evaluation stage)
|
||||
// ==========================================================================
|
||||
console.log('\n📝 Creating evaluation form...')
|
||||
// Legacy evaluation forms and special awards removed - Competition/Round architecture now in use
|
||||
|
||||
const evaluationStage = mainStages[2] // EVALUATION stage
|
||||
await prisma.evaluationForm.upsert({
|
||||
where: { stageId_version: { stageId: evaluationStage.id, version: 1 } },
|
||||
// ==========================================================================
|
||||
// 8. Competition Architecture
|
||||
// ==========================================================================
|
||||
console.log('\n🏗️ Creating competition architecture...')
|
||||
|
||||
const competition = await prisma.competition.upsert({
|
||||
where: { slug: 'mopc-2026' },
|
||||
update: {},
|
||||
create: {
|
||||
stageId: evaluationStage.id,
|
||||
version: 1,
|
||||
isActive: true,
|
||||
criteriaJson: [
|
||||
{ id: 'need_clarity', label: 'Need Clarity', description: 'How clearly is the problem/need articulated?', scale: '1-5', weight: 20, type: 'numeric', required: true },
|
||||
{ id: 'solution_relevance', label: 'Solution Relevance', description: 'How relevant and innovative is the proposed solution?', scale: '1-5', weight: 25, type: 'numeric', required: true },
|
||||
{ id: 'ocean_impact', label: 'Ocean Impact', description: 'What is the potential positive impact on ocean conservation?', scale: '1-5', weight: 25, type: 'numeric', required: true },
|
||||
{ id: 'feasibility', label: 'Feasibility & Scalability', description: 'How feasible and scalable is the project?', scale: '1-5', weight: 20, type: 'numeric', required: true },
|
||||
{ id: 'team_strength', label: 'Team Strength', description: 'How strong and capable is the team?', scale: '1-5', weight: 10, type: 'numeric', required: true },
|
||||
],
|
||||
scalesJson: {
|
||||
'1-5': { min: 1, max: 5, labels: { 1: 'Poor', 2: 'Below Average', 3: 'Average', 4: 'Good', 5: 'Excellent' } },
|
||||
programId: program.id,
|
||||
name: 'MOPC 2026',
|
||||
slug: 'mopc-2026',
|
||||
status: CompetitionStatus.ACTIVE,
|
||||
categoryMode: 'SHARED',
|
||||
startupFinalistCount: 3,
|
||||
conceptFinalistCount: 3,
|
||||
notifyOnRoundAdvance: true,
|
||||
notifyOnDeadlineApproach: true,
|
||||
deadlineReminderDays: [7, 3, 1],
|
||||
},
|
||||
})
|
||||
console.log(` ✓ Competition: ${competition.name}`)
|
||||
|
||||
// --- Jury Groups ---
|
||||
const juryGroup1 = await prisma.juryGroup.upsert({
|
||||
where: { competitionId_slug: { competitionId: competition.id, slug: 'screening-jury' } },
|
||||
update: {},
|
||||
create: {
|
||||
competitionId: competition.id,
|
||||
name: 'Screening Jury',
|
||||
slug: 'screening-jury',
|
||||
sortOrder: 0,
|
||||
defaultMaxAssignments: 30,
|
||||
defaultCapMode: CapMode.SOFT,
|
||||
softCapBuffer: 5,
|
||||
categoryQuotasEnabled: false,
|
||||
},
|
||||
})
|
||||
|
||||
const juryGroup2 = await prisma.juryGroup.upsert({
|
||||
where: { competitionId_slug: { competitionId: competition.id, slug: 'expert-jury' } },
|
||||
update: {},
|
||||
create: {
|
||||
competitionId: competition.id,
|
||||
name: 'Expert Jury',
|
||||
slug: 'expert-jury',
|
||||
sortOrder: 1,
|
||||
defaultMaxAssignments: 20,
|
||||
defaultCapMode: CapMode.SOFT,
|
||||
softCapBuffer: 2,
|
||||
categoryQuotasEnabled: true,
|
||||
defaultCategoryQuotas: {
|
||||
STARTUP: { min: 5, max: 15 },
|
||||
BUSINESS_CONCEPT: { min: 3, max: 10 },
|
||||
},
|
||||
},
|
||||
})
|
||||
console.log(' ✓ Evaluation form created (5 criteria)')
|
||||
|
||||
// ==========================================================================
|
||||
// 13. Special Awards
|
||||
// ==========================================================================
|
||||
console.log('\n🏆 Creating special awards...')
|
||||
|
||||
await prisma.specialAward.upsert({
|
||||
where: { trackId: innovationTrack.id },
|
||||
const juryGroup3 = await prisma.juryGroup.upsert({
|
||||
where: { competitionId_slug: { competitionId: competition.id, slug: 'finals-jury' } },
|
||||
update: {},
|
||||
create: {
|
||||
programId: program.id,
|
||||
name: 'Ocean Innovation Award',
|
||||
description: 'Recognizes the most innovative technology solution for ocean protection',
|
||||
status: 'DRAFT',
|
||||
trackId: innovationTrack.id,
|
||||
scoringMode: 'PICK_WINNER',
|
||||
useAiEligibility: true,
|
||||
criteriaText: 'Projects demonstrating breakthrough technological innovation for ocean conservation',
|
||||
competitionId: competition.id,
|
||||
name: 'Finals Jury',
|
||||
slug: 'finals-jury',
|
||||
sortOrder: 2,
|
||||
defaultMaxAssignments: 10,
|
||||
defaultCapMode: CapMode.HARD,
|
||||
softCapBuffer: 0,
|
||||
categoryQuotasEnabled: false,
|
||||
},
|
||||
})
|
||||
|
||||
await prisma.specialAward.upsert({
|
||||
where: { trackId: impactTrack.id },
|
||||
console.log(' ✓ Jury Groups: Screening, Expert, Finals')
|
||||
|
||||
// --- Add jury members to groups ---
|
||||
// Split 8 jurors: 4 in screening, 6 in expert (some overlap), all 8 in finals
|
||||
const juryGroupAssignments = [
|
||||
{ groupId: juryGroup1.id, userIds: juryUserIds.slice(0, 4), role: JuryGroupMemberRole.MEMBER },
|
||||
{ groupId: juryGroup2.id, userIds: juryUserIds.slice(0, 6), role: JuryGroupMemberRole.MEMBER },
|
||||
{ groupId: juryGroup3.id, userIds: juryUserIds, role: JuryGroupMemberRole.MEMBER },
|
||||
]
|
||||
|
||||
let memberCount = 0
|
||||
for (const assignment of juryGroupAssignments) {
|
||||
for (let i = 0; i < assignment.userIds.length; i++) {
|
||||
const userId = assignment.userIds[i]
|
||||
await prisma.juryGroupMember.upsert({
|
||||
where: {
|
||||
juryGroupId_userId: { juryGroupId: assignment.groupId, userId },
|
||||
},
|
||||
update: {},
|
||||
create: {
|
||||
juryGroupId: assignment.groupId,
|
||||
userId,
|
||||
role: i === 0 ? JuryGroupMemberRole.CHAIR : assignment.role,
|
||||
},
|
||||
})
|
||||
memberCount++
|
||||
}
|
||||
}
|
||||
console.log(` ✓ ${memberCount} jury group memberships created`)
|
||||
|
||||
// --- Demo self-service preferences ---
|
||||
// Enable self-service on the Expert Panel and set preferences for first 2 members
|
||||
await prisma.juryGroup.update({
|
||||
where: { id: juryGroup2.id },
|
||||
data: { allowJurorCapAdjustment: true, allowJurorRatioAdjustment: true },
|
||||
})
|
||||
|
||||
// Juror 0 sets a lower cap and prefers startups
|
||||
const selfServiceMember1 = await prisma.juryGroupMember.findUnique({
|
||||
where: { juryGroupId_userId: { juryGroupId: juryGroup2.id, userId: juryUserIds[0] } },
|
||||
})
|
||||
if (selfServiceMember1) {
|
||||
await prisma.juryGroupMember.update({
|
||||
where: { id: selfServiceMember1.id },
|
||||
data: { selfServiceCap: 12, selfServiceRatio: 0.7 },
|
||||
})
|
||||
}
|
||||
|
||||
// Juror 1 sets a moderate ratio preference
|
||||
const selfServiceMember2 = await prisma.juryGroupMember.findUnique({
|
||||
where: { juryGroupId_userId: { juryGroupId: juryGroup2.id, userId: juryUserIds[1] } },
|
||||
})
|
||||
if (selfServiceMember2) {
|
||||
await prisma.juryGroupMember.update({
|
||||
where: { id: selfServiceMember2.id },
|
||||
data: { selfServiceRatio: 0.4 },
|
||||
})
|
||||
}
|
||||
console.log(' ✓ Self-service preferences: 2 jurors in Expert Panel')
|
||||
|
||||
// --- Submission Windows ---
|
||||
const submissionWindow1 = await prisma.submissionWindow.upsert({
|
||||
where: { competitionId_slug: { competitionId: competition.id, slug: 'r1-application-docs' } },
|
||||
update: {},
|
||||
create: {
|
||||
programId: program.id,
|
||||
name: 'Ocean Impact Award',
|
||||
description: 'Recognizes the project with highest community and environmental impact',
|
||||
status: 'DRAFT',
|
||||
trackId: impactTrack.id,
|
||||
scoringMode: 'PICK_WINNER',
|
||||
useAiEligibility: false,
|
||||
criteriaText: 'Projects with measurable, significant impact on ocean health and coastal communities',
|
||||
competitionId: competition.id,
|
||||
name: 'R1 Application Documents',
|
||||
slug: 'r1-application-docs',
|
||||
roundNumber: 1,
|
||||
sortOrder: 0,
|
||||
windowOpenAt: new Date('2026-01-01'),
|
||||
windowCloseAt: new Date('2026-01-31'),
|
||||
isLocked: true,
|
||||
},
|
||||
})
|
||||
|
||||
console.log(' ✓ Ocean Innovation Award → innovation-award track')
|
||||
console.log(' ✓ Ocean Impact Award → impact-award track')
|
||||
const submissionWindow2 = await prisma.submissionWindow.upsert({
|
||||
where: { competitionId_slug: { competitionId: competition.id, slug: 'r4-semifinal-docs' } },
|
||||
update: {},
|
||||
create: {
|
||||
competitionId: competition.id,
|
||||
name: 'R4 Semi-Finalist Documents',
|
||||
slug: 'r4-semifinal-docs',
|
||||
roundNumber: 4,
|
||||
sortOrder: 1,
|
||||
windowOpenAt: new Date('2026-04-01'),
|
||||
windowCloseAt: new Date('2026-04-30'),
|
||||
isLocked: false,
|
||||
},
|
||||
})
|
||||
|
||||
console.log(' ✓ Submission Windows: R1 Application, R4 Semi-finalist')
|
||||
|
||||
// --- File Requirements ---
|
||||
await prisma.submissionFileRequirement.upsert({
|
||||
where: { submissionWindowId_slug: { submissionWindowId: submissionWindow1.id, slug: 'executive-summary' } },
|
||||
update: {},
|
||||
create: {
|
||||
submissionWindowId: submissionWindow1.id,
|
||||
label: 'Executive Summary',
|
||||
slug: 'executive-summary',
|
||||
description: 'PDF document summarizing the project',
|
||||
mimeTypes: ['application/pdf'],
|
||||
maxSizeMb: 50,
|
||||
required: true,
|
||||
sortOrder: 0,
|
||||
},
|
||||
})
|
||||
|
||||
await prisma.submissionFileRequirement.upsert({
|
||||
where: { submissionWindowId_slug: { submissionWindowId: submissionWindow1.id, slug: 'video-pitch' } },
|
||||
update: {},
|
||||
create: {
|
||||
submissionWindowId: submissionWindow1.id,
|
||||
label: 'Video Pitch',
|
||||
slug: 'video-pitch',
|
||||
description: 'Short video pitching the project (max 5 minutes)',
|
||||
mimeTypes: ['video/mp4', 'video/quicktime'],
|
||||
maxSizeMb: 500,
|
||||
required: false,
|
||||
sortOrder: 1,
|
||||
},
|
||||
})
|
||||
|
||||
await prisma.submissionFileRequirement.upsert({
|
||||
where: { submissionWindowId_slug: { submissionWindowId: submissionWindow2.id, slug: 'updated-business-plan' } },
|
||||
update: {},
|
||||
create: {
|
||||
submissionWindowId: submissionWindow2.id,
|
||||
label: 'Updated Business Plan',
|
||||
slug: 'updated-business-plan',
|
||||
description: 'Updated business plan with financials',
|
||||
mimeTypes: ['application/pdf'],
|
||||
maxSizeMb: 50,
|
||||
required: true,
|
||||
sortOrder: 0,
|
||||
},
|
||||
})
|
||||
|
||||
console.log(' ✓ File Requirements: Exec Summary, Video Pitch, Business Plan')
|
||||
|
||||
// --- Rounds (8-round Monaco flow) ---
|
||||
const roundDefs = [
|
||||
{ name: 'R1 - Application Intake', slug: 'r1-intake', roundType: RoundType.INTAKE, sortOrder: 0, status: RoundStatus.ROUND_CLOSED, juryGroupId: null, submissionWindowId: submissionWindow1.id },
|
||||
{ name: 'R2 - AI Screening', slug: 'r2-screening', roundType: RoundType.FILTERING, sortOrder: 1, status: RoundStatus.ROUND_ACTIVE, juryGroupId: juryGroup1.id, submissionWindowId: null },
|
||||
{ name: 'R3 - Expert Evaluation', slug: 'r3-evaluation', roundType: RoundType.EVALUATION, sortOrder: 2, status: RoundStatus.ROUND_DRAFT, juryGroupId: juryGroup2.id, submissionWindowId: null },
|
||||
{ name: 'R4 - Document Submission', slug: 'r4-submission', roundType: RoundType.SUBMISSION, sortOrder: 3, status: RoundStatus.ROUND_DRAFT, juryGroupId: null, submissionWindowId: submissionWindow2.id },
|
||||
{ name: 'R5 - Semi-Final Evaluation', slug: 'r5-semi-eval', roundType: RoundType.EVALUATION, sortOrder: 4, status: RoundStatus.ROUND_DRAFT, juryGroupId: juryGroup2.id, submissionWindowId: null },
|
||||
{ name: 'R6 - Mentoring', slug: 'r6-mentoring', roundType: RoundType.MENTORING, sortOrder: 5, status: RoundStatus.ROUND_DRAFT, juryGroupId: null, submissionWindowId: null },
|
||||
{ name: 'R7 - Grand Final', slug: 'r7-grand-final', roundType: RoundType.LIVE_FINAL, sortOrder: 6, status: RoundStatus.ROUND_DRAFT, juryGroupId: juryGroup3.id, submissionWindowId: null },
|
||||
{ name: 'R8 - Deliberation', slug: 'r8-deliberation', roundType: RoundType.DELIBERATION, sortOrder: 7, status: RoundStatus.ROUND_DRAFT, juryGroupId: juryGroup3.id, submissionWindowId: null },
|
||||
]
|
||||
|
||||
const rounds = []
|
||||
for (const def of roundDefs) {
|
||||
const config = defaultRoundConfig(def.roundType)
|
||||
const round = await prisma.round.upsert({
|
||||
where: { competitionId_slug: { competitionId: competition.id, slug: def.slug } },
|
||||
update: {},
|
||||
create: {
|
||||
competitionId: competition.id,
|
||||
name: def.name,
|
||||
slug: def.slug,
|
||||
roundType: def.roundType,
|
||||
status: def.status,
|
||||
sortOrder: def.sortOrder,
|
||||
configJson: config as object,
|
||||
juryGroupId: def.juryGroupId,
|
||||
submissionWindowId: def.submissionWindowId,
|
||||
},
|
||||
})
|
||||
rounds.push(round)
|
||||
}
|
||||
console.log(` ✓ ${rounds.length} rounds created (R1-R8)`)
|
||||
|
||||
// --- Advancement Rules (auto-advance between rounds) ---
|
||||
for (let i = 0; i < rounds.length - 1; i++) {
|
||||
await prisma.advancementRule.upsert({
|
||||
where: {
|
||||
roundId_sortOrder: { roundId: rounds[i].id, sortOrder: 0 },
|
||||
},
|
||||
update: {},
|
||||
create: {
|
||||
roundId: rounds[i].id,
|
||||
ruleType: AdvancementRuleType.AUTO_ADVANCE,
|
||||
sortOrder: 0,
|
||||
targetRoundId: rounds[i + 1].id,
|
||||
configJson: {},
|
||||
},
|
||||
})
|
||||
}
|
||||
console.log(` ✓ ${rounds.length - 1} advancement rules created`)
|
||||
|
||||
// --- Round-Submission Visibility (which rounds can see which submission windows) ---
|
||||
// R2 and R3 can see R1 docs, R5 can see R4 docs
|
||||
const visibilityLinks = [
|
||||
{ roundId: rounds[1].id, submissionWindowId: submissionWindow1.id }, // R2 sees R1 docs
|
||||
{ roundId: rounds[2].id, submissionWindowId: submissionWindow1.id }, // R3 sees R1 docs
|
||||
{ roundId: rounds[4].id, submissionWindowId: submissionWindow1.id }, // R5 sees R1 docs
|
||||
{ roundId: rounds[4].id, submissionWindowId: submissionWindow2.id }, // R5 sees R4 docs
|
||||
]
|
||||
|
||||
for (const link of visibilityLinks) {
|
||||
await prisma.roundSubmissionVisibility.upsert({
|
||||
where: {
|
||||
roundId_submissionWindowId: {
|
||||
roundId: link.roundId,
|
||||
submissionWindowId: link.submissionWindowId,
|
||||
},
|
||||
},
|
||||
update: {},
|
||||
create: link,
|
||||
})
|
||||
}
|
||||
console.log(` ✓ ${visibilityLinks.length} submission visibility links created`)
|
||||
|
||||
// --- Feature flag: enable competition model ---
|
||||
await prisma.systemSettings.upsert({
|
||||
where: { key: 'feature.useCompetitionModel' },
|
||||
update: { value: 'true' },
|
||||
create: {
|
||||
key: 'feature.useCompetitionModel',
|
||||
value: 'true',
|
||||
type: SettingType.BOOLEAN,
|
||||
category: SettingCategory.FEATURE_FLAGS,
|
||||
description: 'Use Competition/Round model (legacy Pipeline system removed)',
|
||||
},
|
||||
})
|
||||
console.log(' ✓ Feature flag: feature.useCompetitionModel = true')
|
||||
|
||||
// ==========================================================================
|
||||
// 14. Notification Email Settings
|
||||
// 9. Notification Email Settings
|
||||
// ==========================================================================
|
||||
console.log('\n🔔 Creating notification email settings...')
|
||||
|
||||
@@ -1052,27 +910,12 @@ async function main() {
|
||||
console.log(` ✓ Created ${notificationSettings.length} notification email settings`)
|
||||
|
||||
// ==========================================================================
|
||||
// 16. Summary
|
||||
// 10. Summary
|
||||
// ==========================================================================
|
||||
console.log('\n' + '='.repeat(60))
|
||||
console.log('✅ SEEDING COMPLETE')
|
||||
console.log('='.repeat(60))
|
||||
console.log(`
|
||||
Program: ${program.name} ${program.year}
|
||||
Pipeline: ${pipeline.name} (${pipeline.slug})
|
||||
Tracks: 4 (Main, Innovation Award, Impact Award, People's Choice)
|
||||
Stages: ${allStages.length} total
|
||||
Transitions: ${transitionCount}
|
||||
Projects: ${projectCount} (from CSV)
|
||||
Users: ${3 + juryMembers.length + mentors.length + observers.length + projectCount} total
|
||||
- Admin/Staff: 3
|
||||
- Jury: ${juryMembers.length}
|
||||
- Mentors: ${mentors.length}
|
||||
- Observers: ${observers.length}
|
||||
- Applicants: ${projectCount}
|
||||
|
||||
Login: matt@monaco-opc.com / 195260Mp!
|
||||
`)
|
||||
console.log(`\n Program: ${program.name} ${program.year}\n\n Competition: ${competition.name} (${competition.slug})\n Rounds: ${rounds.length} (R1-R8)\n Jury Groups: 3 (Screening, Expert, Finals)\n Sub. Windows: 2 (R1 Application, R4 Semi-finalist)\n\n Projects: ${projectCount} (from CSV)\n Users: ${3 + juryMembers.length + mentors.length + observers.length + projectCount} total\n - Admin/Staff: 3\n - Jury: ${juryMembers.length}\n - Mentors: ${mentors.length}\n - Observers: ${observers.length}\n - Applicants: ${projectCount}\n\n Login: matt@monaco-opc.com / 195260Mp!\n `)
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user