Compare commits
8 Commits
86fa542371
...
65a22e6f19
| Author | SHA1 | Date |
|---|---|---|
|
|
65a22e6f19 | |
|
|
989db4dc14 | |
|
|
5e0c8b2dfe | |
|
|
85a0fa5016 | |
|
|
c707899179 | |
|
|
4d40afec6e | |
|
|
effc078918 | |
|
|
763b2ef0f5 |
|
|
@ -16,105 +16,143 @@
|
||||||
-- the enum.
|
-- the enum.
|
||||||
|
|
||||||
|
|
||||||
ALTER TYPE "SettingCategory" ADD VALUE 'DIGEST';
|
DO $$ BEGIN ALTER TYPE "SettingCategory" ADD VALUE 'DIGEST'; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
ALTER TYPE "SettingCategory" ADD VALUE 'ANALYTICS';
|
DO $$ BEGIN ALTER TYPE "SettingCategory" ADD VALUE 'ANALYTICS'; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
ALTER TYPE "SettingCategory" ADD VALUE 'AUDIT_CONFIG';
|
DO $$ BEGIN ALTER TYPE "SettingCategory" ADD VALUE 'AUDIT_CONFIG'; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
ALTER TYPE "SettingCategory" ADD VALUE 'INTEGRATIONS';
|
DO $$ BEGIN ALTER TYPE "SettingCategory" ADD VALUE 'INTEGRATIONS'; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
ALTER TYPE "SettingCategory" ADD VALUE 'LOCALIZATION';
|
DO $$ BEGIN ALTER TYPE "SettingCategory" ADD VALUE 'LOCALIZATION'; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
ALTER TYPE "SettingCategory" ADD VALUE 'COMMUNICATION';
|
DO $$ BEGIN ALTER TYPE "SettingCategory" ADD VALUE 'COMMUNICATION'; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- DropForeignKey
|
-- DropForeignKey
|
||||||
ALTER TABLE "ApplicationForm" DROP CONSTRAINT "ApplicationForm_programId_fkey";
|
ALTER TABLE "ApplicationForm" DROP CONSTRAINT IF EXISTS "ApplicationForm_programId_fkey";
|
||||||
|
|
||||||
-- DropForeignKey
|
-- DropForeignKey
|
||||||
ALTER TABLE "ApplicationForm" DROP CONSTRAINT "ApplicationForm_roundId_fkey";
|
ALTER TABLE "ApplicationForm" DROP CONSTRAINT IF EXISTS "ApplicationForm_roundId_fkey";
|
||||||
|
|
||||||
-- DropForeignKey
|
-- DropForeignKey
|
||||||
ALTER TABLE "ApplicationFormField" DROP CONSTRAINT "ApplicationFormField_formId_fkey";
|
ALTER TABLE "ApplicationFormField" DROP CONSTRAINT IF EXISTS "ApplicationFormField_formId_fkey";
|
||||||
|
|
||||||
-- DropForeignKey
|
-- DropForeignKey
|
||||||
ALTER TABLE "ApplicationFormField" DROP CONSTRAINT "ApplicationFormField_stepId_fkey";
|
ALTER TABLE "ApplicationFormField" DROP CONSTRAINT IF EXISTS "ApplicationFormField_stepId_fkey";
|
||||||
|
|
||||||
-- DropForeignKey
|
-- DropForeignKey
|
||||||
ALTER TABLE "ApplicationFormSubmission" DROP CONSTRAINT "ApplicationFormSubmission_formId_fkey";
|
ALTER TABLE "ApplicationFormSubmission" DROP CONSTRAINT IF EXISTS "ApplicationFormSubmission_formId_fkey";
|
||||||
|
|
||||||
-- DropForeignKey
|
-- DropForeignKey
|
||||||
ALTER TABLE "OnboardingStep" DROP CONSTRAINT "OnboardingStep_formId_fkey";
|
ALTER TABLE "OnboardingStep" DROP CONSTRAINT IF EXISTS "OnboardingStep_formId_fkey";
|
||||||
|
|
||||||
-- DropForeignKey
|
-- DropForeignKey
|
||||||
ALTER TABLE "SubmissionFile" DROP CONSTRAINT "SubmissionFile_submissionId_fkey";
|
ALTER TABLE "SubmissionFile" DROP CONSTRAINT IF EXISTS "SubmissionFile_submissionId_fkey";
|
||||||
|
|
||||||
-- DropIndex
|
-- DropIndex
|
||||||
DROP INDEX "User_email_idx";
|
DROP INDEX IF EXISTS "User_email_idx";
|
||||||
|
|
||||||
-- AlterTable
|
-- AlterTable
|
||||||
ALTER TABLE "AssignmentJob" ALTER COLUMN "updatedAt" DROP DEFAULT;
|
DO $$ BEGIN ALTER TABLE "AssignmentJob" ALTER COLUMN "updatedAt" DROP DEFAULT; EXCEPTION WHEN others THEN NULL; END $$;
|
||||||
|
|
||||||
-- AlterTable
|
-- AlterTable
|
||||||
ALTER TABLE "AuditLog" ADD COLUMN "previousDataJson" JSONB,
|
DO $$ BEGIN
|
||||||
ADD COLUMN "sessionId" TEXT;
|
ALTER TABLE "AuditLog" ADD COLUMN "previousDataJson" JSONB;
|
||||||
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "AuditLog" ADD COLUMN "sessionId" TEXT;
|
||||||
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
|
||||||
-- AlterTable
|
-- AlterTable
|
||||||
ALTER TABLE "FilteringJob" ALTER COLUMN "updatedAt" DROP DEFAULT;
|
DO $$ BEGIN ALTER TABLE "FilteringJob" ALTER COLUMN "updatedAt" DROP DEFAULT; EXCEPTION WHEN others THEN NULL; END $$;
|
||||||
|
|
||||||
-- AlterTable
|
-- AlterTable
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "LiveVote" ADD COLUMN "isAudienceVote" BOOLEAN NOT NULL DEFAULT false;
|
ALTER TABLE "LiveVote" ADD COLUMN "isAudienceVote" BOOLEAN NOT NULL DEFAULT false;
|
||||||
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
|
||||||
-- AlterTable
|
-- AlterTable
|
||||||
ALTER TABLE "LiveVotingSession" ADD COLUMN "allowAudienceVotes" BOOLEAN NOT NULL DEFAULT false,
|
DO $$ BEGIN
|
||||||
ADD COLUMN "audienceVoteWeight" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
ALTER TABLE "LiveVotingSession" ADD COLUMN "allowAudienceVotes" BOOLEAN NOT NULL DEFAULT false;
|
||||||
ADD COLUMN "presentationSettingsJson" JSONB,
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
ADD COLUMN "tieBreakerMethod" TEXT NOT NULL DEFAULT 'admin_decides';
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "LiveVotingSession" ADD COLUMN "audienceVoteWeight" DOUBLE PRECISION NOT NULL DEFAULT 0;
|
||||||
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "LiveVotingSession" ADD COLUMN "presentationSettingsJson" JSONB;
|
||||||
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "LiveVotingSession" ADD COLUMN "tieBreakerMethod" TEXT NOT NULL DEFAULT 'admin_decides';
|
||||||
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
|
||||||
-- AlterTable
|
-- AlterTable
|
||||||
ALTER TABLE "MentorAssignment" ADD COLUMN "completionStatus" TEXT NOT NULL DEFAULT 'in_progress',
|
DO $$ BEGIN
|
||||||
ADD COLUMN "lastViewedAt" TIMESTAMP(3);
|
ALTER TABLE "MentorAssignment" ADD COLUMN "completionStatus" TEXT NOT NULL DEFAULT 'in_progress';
|
||||||
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "MentorAssignment" ADD COLUMN "lastViewedAt" TIMESTAMP(3);
|
||||||
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
|
||||||
-- AlterTable
|
-- AlterTable
|
||||||
ALTER TABLE "NotificationEmailSetting" ALTER COLUMN "updatedAt" DROP DEFAULT;
|
DO $$ BEGIN ALTER TABLE "NotificationEmailSetting" ALTER COLUMN "updatedAt" DROP DEFAULT; EXCEPTION WHEN others THEN NULL; END $$;
|
||||||
|
|
||||||
-- AlterTable
|
-- AlterTable
|
||||||
ALTER TABLE "Project" ADD COLUMN "draftDataJson" JSONB,
|
DO $$ BEGIN
|
||||||
ADD COLUMN "draftExpiresAt" TIMESTAMP(3),
|
ALTER TABLE "Project" ADD COLUMN "draftDataJson" JSONB;
|
||||||
ADD COLUMN "isDraft" BOOLEAN NOT NULL DEFAULT false;
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Project" ADD COLUMN "draftExpiresAt" TIMESTAMP(3);
|
||||||
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "Project" ADD COLUMN "isDraft" BOOLEAN NOT NULL DEFAULT false;
|
||||||
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
|
||||||
-- AlterTable
|
-- AlterTable
|
||||||
ALTER TABLE "ProjectFile" ADD COLUMN "isLate" BOOLEAN NOT NULL DEFAULT false,
|
DO $$ BEGIN
|
||||||
ADD COLUMN "replacedById" TEXT,
|
ALTER TABLE "ProjectFile" ADD COLUMN "isLate" BOOLEAN NOT NULL DEFAULT false;
|
||||||
ADD COLUMN "roundId" TEXT,
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
ADD COLUMN "version" INTEGER NOT NULL DEFAULT 1;
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "ProjectFile" ADD COLUMN "replacedById" TEXT;
|
||||||
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "ProjectFile" ADD COLUMN "roundId" TEXT;
|
||||||
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "ProjectFile" ADD COLUMN "version" INTEGER NOT NULL DEFAULT 1;
|
||||||
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
|
||||||
-- AlterTable
|
-- AlterTable
|
||||||
ALTER TABLE "TaggingJob" ALTER COLUMN "updatedAt" DROP DEFAULT;
|
DO $$ BEGIN ALTER TABLE "TaggingJob" ALTER COLUMN "updatedAt" DROP DEFAULT; EXCEPTION WHEN others THEN NULL; END $$;
|
||||||
|
|
||||||
-- AlterTable
|
-- AlterTable
|
||||||
ALTER TABLE "User" ADD COLUMN "availabilityJson" JSONB,
|
DO $$ BEGIN
|
||||||
ADD COLUMN "digestFrequency" TEXT NOT NULL DEFAULT 'none',
|
ALTER TABLE "User" ADD COLUMN "availabilityJson" JSONB;
|
||||||
ADD COLUMN "preferredWorkload" INTEGER;
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "User" ADD COLUMN "digestFrequency" TEXT NOT NULL DEFAULT 'none';
|
||||||
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "User" ADD COLUMN "preferredWorkload" INTEGER;
|
||||||
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
|
||||||
-- DropTable
|
-- DropTable
|
||||||
DROP TABLE "ApplicationForm";
|
DROP TABLE IF EXISTS "ApplicationForm";
|
||||||
|
|
||||||
-- DropTable
|
-- DropTable
|
||||||
DROP TABLE "ApplicationFormField";
|
DROP TABLE IF EXISTS "ApplicationFormField";
|
||||||
|
|
||||||
-- DropTable
|
-- DropTable
|
||||||
DROP TABLE "ApplicationFormSubmission";
|
DROP TABLE IF EXISTS "ApplicationFormSubmission";
|
||||||
|
|
||||||
-- DropTable
|
-- DropTable
|
||||||
DROP TABLE "OnboardingStep";
|
DROP TABLE IF EXISTS "OnboardingStep";
|
||||||
|
|
||||||
-- DropTable
|
-- DropTable
|
||||||
DROP TABLE "SubmissionFile";
|
DROP TABLE IF EXISTS "SubmissionFile";
|
||||||
|
|
||||||
-- DropEnum
|
-- DropEnum
|
||||||
DROP TYPE "FormFieldType";
|
DROP TYPE IF EXISTS "FormFieldType";
|
||||||
|
|
||||||
-- DropEnum
|
-- DropEnum
|
||||||
DROP TYPE "SpecialFieldType";
|
DROP TYPE IF EXISTS "SpecialFieldType";
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "ReminderLog" (
|
CREATE TABLE IF NOT EXISTS "ReminderLog" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"roundId" TEXT NOT NULL,
|
"roundId" TEXT NOT NULL,
|
||||||
"userId" TEXT NOT NULL,
|
"userId" TEXT NOT NULL,
|
||||||
|
|
@ -125,7 +163,7 @@ CREATE TABLE "ReminderLog" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "ConflictOfInterest" (
|
CREATE TABLE IF NOT EXISTS "ConflictOfInterest" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"assignmentId" TEXT NOT NULL,
|
"assignmentId" TEXT NOT NULL,
|
||||||
"userId" TEXT NOT NULL,
|
"userId" TEXT NOT NULL,
|
||||||
|
|
@ -143,7 +181,7 @@ CREATE TABLE "ConflictOfInterest" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "EvaluationSummary" (
|
CREATE TABLE IF NOT EXISTS "EvaluationSummary" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"projectId" TEXT NOT NULL,
|
"projectId" TEXT NOT NULL,
|
||||||
"roundId" TEXT NOT NULL,
|
"roundId" TEXT NOT NULL,
|
||||||
|
|
@ -157,7 +195,7 @@ CREATE TABLE "EvaluationSummary" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "ProjectStatusHistory" (
|
CREATE TABLE IF NOT EXISTS "ProjectStatusHistory" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"projectId" TEXT NOT NULL,
|
"projectId" TEXT NOT NULL,
|
||||||
"status" "ProjectStatus" NOT NULL,
|
"status" "ProjectStatus" NOT NULL,
|
||||||
|
|
@ -168,7 +206,7 @@ CREATE TABLE "ProjectStatusHistory" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "MentorMessage" (
|
CREATE TABLE IF NOT EXISTS "MentorMessage" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"projectId" TEXT NOT NULL,
|
"projectId" TEXT NOT NULL,
|
||||||
"senderId" TEXT NOT NULL,
|
"senderId" TEXT NOT NULL,
|
||||||
|
|
@ -180,7 +218,7 @@ CREATE TABLE "MentorMessage" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "DigestLog" (
|
CREATE TABLE IF NOT EXISTS "DigestLog" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"userId" TEXT NOT NULL,
|
"userId" TEXT NOT NULL,
|
||||||
"digestType" TEXT NOT NULL,
|
"digestType" TEXT NOT NULL,
|
||||||
|
|
@ -191,7 +229,7 @@ CREATE TABLE "DigestLog" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "RoundTemplate" (
|
CREATE TABLE IF NOT EXISTS "RoundTemplate" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"name" TEXT NOT NULL,
|
"name" TEXT NOT NULL,
|
||||||
"description" TEXT,
|
"description" TEXT,
|
||||||
|
|
@ -208,7 +246,7 @@ CREATE TABLE "RoundTemplate" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "MentorNote" (
|
CREATE TABLE IF NOT EXISTS "MentorNote" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"mentorAssignmentId" TEXT NOT NULL,
|
"mentorAssignmentId" TEXT NOT NULL,
|
||||||
"authorId" TEXT NOT NULL,
|
"authorId" TEXT NOT NULL,
|
||||||
|
|
@ -221,7 +259,7 @@ CREATE TABLE "MentorNote" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "MentorMilestone" (
|
CREATE TABLE IF NOT EXISTS "MentorMilestone" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"programId" TEXT NOT NULL,
|
"programId" TEXT NOT NULL,
|
||||||
"name" TEXT NOT NULL,
|
"name" TEXT NOT NULL,
|
||||||
|
|
@ -236,7 +274,7 @@ CREATE TABLE "MentorMilestone" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "MentorMilestoneCompletion" (
|
CREATE TABLE IF NOT EXISTS "MentorMilestoneCompletion" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"milestoneId" TEXT NOT NULL,
|
"milestoneId" TEXT NOT NULL,
|
||||||
"mentorAssignmentId" TEXT NOT NULL,
|
"mentorAssignmentId" TEXT NOT NULL,
|
||||||
|
|
@ -247,7 +285,7 @@ CREATE TABLE "MentorMilestoneCompletion" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "Message" (
|
CREATE TABLE IF NOT EXISTS "Message" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"senderId" TEXT NOT NULL,
|
"senderId" TEXT NOT NULL,
|
||||||
"recipientType" TEXT NOT NULL,
|
"recipientType" TEXT NOT NULL,
|
||||||
|
|
@ -266,7 +304,7 @@ CREATE TABLE "Message" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "MessageTemplate" (
|
CREATE TABLE IF NOT EXISTS "MessageTemplate" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"name" TEXT NOT NULL,
|
"name" TEXT NOT NULL,
|
||||||
"category" TEXT NOT NULL,
|
"category" TEXT NOT NULL,
|
||||||
|
|
@ -282,7 +320,7 @@ CREATE TABLE "MessageTemplate" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "MessageRecipient" (
|
CREATE TABLE IF NOT EXISTS "MessageRecipient" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"messageId" TEXT NOT NULL,
|
"messageId" TEXT NOT NULL,
|
||||||
"userId" TEXT NOT NULL,
|
"userId" TEXT NOT NULL,
|
||||||
|
|
@ -295,7 +333,7 @@ CREATE TABLE "MessageRecipient" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "Webhook" (
|
CREATE TABLE IF NOT EXISTS "Webhook" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"name" TEXT NOT NULL,
|
"name" TEXT NOT NULL,
|
||||||
"url" TEXT NOT NULL,
|
"url" TEXT NOT NULL,
|
||||||
|
|
@ -312,7 +350,7 @@ CREATE TABLE "Webhook" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "WebhookDelivery" (
|
CREATE TABLE IF NOT EXISTS "WebhookDelivery" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"webhookId" TEXT NOT NULL,
|
"webhookId" TEXT NOT NULL,
|
||||||
"event" TEXT NOT NULL,
|
"event" TEXT NOT NULL,
|
||||||
|
|
@ -328,7 +366,7 @@ CREATE TABLE "WebhookDelivery" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "EvaluationDiscussion" (
|
CREATE TABLE IF NOT EXISTS "EvaluationDiscussion" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"projectId" TEXT NOT NULL,
|
"projectId" TEXT NOT NULL,
|
||||||
"roundId" TEXT NOT NULL,
|
"roundId" TEXT NOT NULL,
|
||||||
|
|
@ -341,7 +379,7 @@ CREATE TABLE "EvaluationDiscussion" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "DiscussionComment" (
|
CREATE TABLE IF NOT EXISTS "DiscussionComment" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"discussionId" TEXT NOT NULL,
|
"discussionId" TEXT NOT NULL,
|
||||||
"userId" TEXT NOT NULL,
|
"userId" TEXT NOT NULL,
|
||||||
|
|
@ -352,199 +390,257 @@ CREATE TABLE "DiscussionComment" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "ReminderLog_roundId_idx" ON "ReminderLog"("roundId");
|
CREATE INDEX IF NOT EXISTS "ReminderLog_roundId_idx" ON "ReminderLog"("roundId");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "ReminderLog_roundId_userId_type_key" ON "ReminderLog"("roundId", "userId", "type");
|
CREATE UNIQUE INDEX IF NOT EXISTS "ReminderLog_roundId_userId_type_key" ON "ReminderLog"("roundId", "userId", "type");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "ConflictOfInterest_assignmentId_key" ON "ConflictOfInterest"("assignmentId");
|
CREATE UNIQUE INDEX IF NOT EXISTS "ConflictOfInterest_assignmentId_key" ON "ConflictOfInterest"("assignmentId");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "ConflictOfInterest_userId_idx" ON "ConflictOfInterest"("userId");
|
CREATE INDEX IF NOT EXISTS "ConflictOfInterest_userId_idx" ON "ConflictOfInterest"("userId");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "ConflictOfInterest_roundId_hasConflict_idx" ON "ConflictOfInterest"("roundId", "hasConflict");
|
CREATE INDEX IF NOT EXISTS "ConflictOfInterest_roundId_hasConflict_idx" ON "ConflictOfInterest"("roundId", "hasConflict");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "EvaluationSummary_roundId_idx" ON "EvaluationSummary"("roundId");
|
CREATE INDEX IF NOT EXISTS "EvaluationSummary_roundId_idx" ON "EvaluationSummary"("roundId");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "EvaluationSummary_projectId_roundId_key" ON "EvaluationSummary"("projectId", "roundId");
|
CREATE UNIQUE INDEX IF NOT EXISTS "EvaluationSummary_projectId_roundId_key" ON "EvaluationSummary"("projectId", "roundId");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "ProjectStatusHistory_projectId_changedAt_idx" ON "ProjectStatusHistory"("projectId", "changedAt");
|
CREATE INDEX IF NOT EXISTS "ProjectStatusHistory_projectId_changedAt_idx" ON "ProjectStatusHistory"("projectId", "changedAt");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "MentorMessage_projectId_createdAt_idx" ON "MentorMessage"("projectId", "createdAt");
|
CREATE INDEX IF NOT EXISTS "MentorMessage_projectId_createdAt_idx" ON "MentorMessage"("projectId", "createdAt");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "DigestLog_userId_idx" ON "DigestLog"("userId");
|
CREATE INDEX IF NOT EXISTS "DigestLog_userId_idx" ON "DigestLog"("userId");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "DigestLog_sentAt_idx" ON "DigestLog"("sentAt");
|
CREATE INDEX IF NOT EXISTS "DigestLog_sentAt_idx" ON "DigestLog"("sentAt");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "RoundTemplate_programId_idx" ON "RoundTemplate"("programId");
|
CREATE INDEX IF NOT EXISTS "RoundTemplate_programId_idx" ON "RoundTemplate"("programId");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "MentorNote_mentorAssignmentId_idx" ON "MentorNote"("mentorAssignmentId");
|
CREATE INDEX IF NOT EXISTS "MentorNote_mentorAssignmentId_idx" ON "MentorNote"("mentorAssignmentId");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "MentorMilestone_programId_idx" ON "MentorMilestone"("programId");
|
CREATE INDEX IF NOT EXISTS "MentorMilestone_programId_idx" ON "MentorMilestone"("programId");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "MentorMilestone_sortOrder_idx" ON "MentorMilestone"("sortOrder");
|
CREATE INDEX IF NOT EXISTS "MentorMilestone_sortOrder_idx" ON "MentorMilestone"("sortOrder");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "MentorMilestoneCompletion_mentorAssignmentId_idx" ON "MentorMilestoneCompletion"("mentorAssignmentId");
|
CREATE INDEX IF NOT EXISTS "MentorMilestoneCompletion_mentorAssignmentId_idx" ON "MentorMilestoneCompletion"("mentorAssignmentId");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "MentorMilestoneCompletion_milestoneId_mentorAssignmentId_key" ON "MentorMilestoneCompletion"("milestoneId", "mentorAssignmentId");
|
CREATE UNIQUE INDEX IF NOT EXISTS "MentorMilestoneCompletion_milestoneId_mentorAssignmentId_key" ON "MentorMilestoneCompletion"("milestoneId", "mentorAssignmentId");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "Message_senderId_idx" ON "Message"("senderId");
|
CREATE INDEX IF NOT EXISTS "Message_senderId_idx" ON "Message"("senderId");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "Message_sentAt_idx" ON "Message"("sentAt");
|
CREATE INDEX IF NOT EXISTS "Message_sentAt_idx" ON "Message"("sentAt");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "Message_scheduledAt_idx" ON "Message"("scheduledAt");
|
CREATE INDEX IF NOT EXISTS "Message_scheduledAt_idx" ON "Message"("scheduledAt");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "MessageTemplate_category_idx" ON "MessageTemplate"("category");
|
CREATE INDEX IF NOT EXISTS "MessageTemplate_category_idx" ON "MessageTemplate"("category");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "MessageTemplate_isActive_idx" ON "MessageTemplate"("isActive");
|
CREATE INDEX IF NOT EXISTS "MessageTemplate_isActive_idx" ON "MessageTemplate"("isActive");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "MessageRecipient_messageId_idx" ON "MessageRecipient"("messageId");
|
CREATE INDEX IF NOT EXISTS "MessageRecipient_messageId_idx" ON "MessageRecipient"("messageId");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "MessageRecipient_userId_isRead_idx" ON "MessageRecipient"("userId", "isRead");
|
CREATE INDEX IF NOT EXISTS "MessageRecipient_userId_isRead_idx" ON "MessageRecipient"("userId", "isRead");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "Webhook_isActive_idx" ON "Webhook"("isActive");
|
CREATE INDEX IF NOT EXISTS "Webhook_isActive_idx" ON "Webhook"("isActive");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "WebhookDelivery_webhookId_idx" ON "WebhookDelivery"("webhookId");
|
CREATE INDEX IF NOT EXISTS "WebhookDelivery_webhookId_idx" ON "WebhookDelivery"("webhookId");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "WebhookDelivery_status_idx" ON "WebhookDelivery"("status");
|
CREATE INDEX IF NOT EXISTS "WebhookDelivery_status_idx" ON "WebhookDelivery"("status");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "WebhookDelivery_createdAt_idx" ON "WebhookDelivery"("createdAt");
|
CREATE INDEX IF NOT EXISTS "WebhookDelivery_createdAt_idx" ON "WebhookDelivery"("createdAt");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "EvaluationDiscussion_roundId_idx" ON "EvaluationDiscussion"("roundId");
|
CREATE INDEX IF NOT EXISTS "EvaluationDiscussion_roundId_idx" ON "EvaluationDiscussion"("roundId");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "EvaluationDiscussion_status_idx" ON "EvaluationDiscussion"("status");
|
CREATE INDEX IF NOT EXISTS "EvaluationDiscussion_status_idx" ON "EvaluationDiscussion"("status");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "EvaluationDiscussion_projectId_roundId_key" ON "EvaluationDiscussion"("projectId", "roundId");
|
CREATE UNIQUE INDEX IF NOT EXISTS "EvaluationDiscussion_projectId_roundId_key" ON "EvaluationDiscussion"("projectId", "roundId");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "DiscussionComment_discussionId_createdAt_idx" ON "DiscussionComment"("discussionId", "createdAt");
|
CREATE INDEX IF NOT EXISTS "DiscussionComment_discussionId_createdAt_idx" ON "DiscussionComment"("discussionId", "createdAt");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "AuditLog_entityType_entityId_timestamp_idx" ON "AuditLog"("entityType", "entityId", "timestamp");
|
CREATE INDEX IF NOT EXISTS "AuditLog_entityType_entityId_timestamp_idx" ON "AuditLog"("entityType", "entityId", "timestamp");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "Evaluation_status_formId_idx" ON "Evaluation"("status", "formId");
|
CREATE INDEX IF NOT EXISTS "Evaluation_status_formId_idx" ON "Evaluation"("status", "formId");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "GracePeriod_roundId_userId_extendedUntil_idx" ON "GracePeriod"("roundId", "userId", "extendedUntil");
|
CREATE INDEX IF NOT EXISTS "GracePeriod_roundId_userId_extendedUntil_idx" ON "GracePeriod"("roundId", "userId", "extendedUntil");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "LiveVote_isAudienceVote_idx" ON "LiveVote"("isAudienceVote");
|
CREATE INDEX IF NOT EXISTS "LiveVote_isAudienceVote_idx" ON "LiveVote"("isAudienceVote");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "ProjectFile_roundId_idx" ON "ProjectFile"("roundId");
|
CREATE INDEX IF NOT EXISTS "ProjectFile_roundId_idx" ON "ProjectFile"("roundId");
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "ProjectFile" ADD CONSTRAINT "ProjectFile_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
ALTER TABLE "ProjectFile" ADD CONSTRAINT "ProjectFile_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "SpecialAward" ADD CONSTRAINT "SpecialAward_winnerOverriddenBy_fkey" FOREIGN KEY ("winnerOverriddenBy") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
ALTER TABLE "SpecialAward" ADD CONSTRAINT "SpecialAward_winnerOverriddenBy_fkey" FOREIGN KEY ("winnerOverriddenBy") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "ReminderLog" ADD CONSTRAINT "ReminderLog_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE "ReminderLog" ADD CONSTRAINT "ReminderLog_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "ReminderLog" ADD CONSTRAINT "ReminderLog_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE "ReminderLog" ADD CONSTRAINT "ReminderLog_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "ConflictOfInterest" ADD CONSTRAINT "ConflictOfInterest_assignmentId_fkey" FOREIGN KEY ("assignmentId") REFERENCES "Assignment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE "ConflictOfInterest" ADD CONSTRAINT "ConflictOfInterest_assignmentId_fkey" FOREIGN KEY ("assignmentId") REFERENCES "Assignment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "ConflictOfInterest" ADD CONSTRAINT "ConflictOfInterest_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
ALTER TABLE "ConflictOfInterest" ADD CONSTRAINT "ConflictOfInterest_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "ConflictOfInterest" ADD CONSTRAINT "ConflictOfInterest_reviewedById_fkey" FOREIGN KEY ("reviewedById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
ALTER TABLE "ConflictOfInterest" ADD CONSTRAINT "ConflictOfInterest_reviewedById_fkey" FOREIGN KEY ("reviewedById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "EvaluationSummary" ADD CONSTRAINT "EvaluationSummary_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE "EvaluationSummary" ADD CONSTRAINT "EvaluationSummary_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "EvaluationSummary" ADD CONSTRAINT "EvaluationSummary_generatedById_fkey" FOREIGN KEY ("generatedById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
ALTER TABLE "EvaluationSummary" ADD CONSTRAINT "EvaluationSummary_generatedById_fkey" FOREIGN KEY ("generatedById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "ProjectStatusHistory" ADD CONSTRAINT "ProjectStatusHistory_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE "ProjectStatusHistory" ADD CONSTRAINT "ProjectStatusHistory_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "MentorMessage" ADD CONSTRAINT "MentorMessage_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE "MentorMessage" ADD CONSTRAINT "MentorMessage_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "MentorMessage" ADD CONSTRAINT "MentorMessage_senderId_fkey" FOREIGN KEY ("senderId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
ALTER TABLE "MentorMessage" ADD CONSTRAINT "MentorMessage_senderId_fkey" FOREIGN KEY ("senderId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "DigestLog" ADD CONSTRAINT "DigestLog_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE "DigestLog" ADD CONSTRAINT "DigestLog_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "MentorNote" ADD CONSTRAINT "MentorNote_mentorAssignmentId_fkey" FOREIGN KEY ("mentorAssignmentId") REFERENCES "MentorAssignment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE "MentorNote" ADD CONSTRAINT "MentorNote_mentorAssignmentId_fkey" FOREIGN KEY ("mentorAssignmentId") REFERENCES "MentorAssignment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "MentorNote" ADD CONSTRAINT "MentorNote_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
ALTER TABLE "MentorNote" ADD CONSTRAINT "MentorNote_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "MentorMilestone" ADD CONSTRAINT "MentorMilestone_programId_fkey" FOREIGN KEY ("programId") REFERENCES "Program"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE "MentorMilestone" ADD CONSTRAINT "MentorMilestone_programId_fkey" FOREIGN KEY ("programId") REFERENCES "Program"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "MentorMilestoneCompletion" ADD CONSTRAINT "MentorMilestoneCompletion_milestoneId_fkey" FOREIGN KEY ("milestoneId") REFERENCES "MentorMilestone"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE "MentorMilestoneCompletion" ADD CONSTRAINT "MentorMilestoneCompletion_milestoneId_fkey" FOREIGN KEY ("milestoneId") REFERENCES "MentorMilestone"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "MentorMilestoneCompletion" ADD CONSTRAINT "MentorMilestoneCompletion_mentorAssignmentId_fkey" FOREIGN KEY ("mentorAssignmentId") REFERENCES "MentorAssignment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE "MentorMilestoneCompletion" ADD CONSTRAINT "MentorMilestoneCompletion_mentorAssignmentId_fkey" FOREIGN KEY ("mentorAssignmentId") REFERENCES "MentorAssignment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "MentorMilestoneCompletion" ADD CONSTRAINT "MentorMilestoneCompletion_completedById_fkey" FOREIGN KEY ("completedById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
ALTER TABLE "MentorMilestoneCompletion" ADD CONSTRAINT "MentorMilestoneCompletion_completedById_fkey" FOREIGN KEY ("completedById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "Message" ADD CONSTRAINT "Message_senderId_fkey" FOREIGN KEY ("senderId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
ALTER TABLE "Message" ADD CONSTRAINT "Message_senderId_fkey" FOREIGN KEY ("senderId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "Message" ADD CONSTRAINT "Message_templateId_fkey" FOREIGN KEY ("templateId") REFERENCES "MessageTemplate"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
ALTER TABLE "Message" ADD CONSTRAINT "Message_templateId_fkey" FOREIGN KEY ("templateId") REFERENCES "MessageTemplate"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "MessageRecipient" ADD CONSTRAINT "MessageRecipient_messageId_fkey" FOREIGN KEY ("messageId") REFERENCES "Message"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE "MessageRecipient" ADD CONSTRAINT "MessageRecipient_messageId_fkey" FOREIGN KEY ("messageId") REFERENCES "Message"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "MessageRecipient" ADD CONSTRAINT "MessageRecipient_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE "MessageRecipient" ADD CONSTRAINT "MessageRecipient_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "Webhook" ADD CONSTRAINT "Webhook_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
ALTER TABLE "Webhook" ADD CONSTRAINT "Webhook_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "WebhookDelivery" ADD CONSTRAINT "WebhookDelivery_webhookId_fkey" FOREIGN KEY ("webhookId") REFERENCES "Webhook"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE "WebhookDelivery" ADD CONSTRAINT "WebhookDelivery_webhookId_fkey" FOREIGN KEY ("webhookId") REFERENCES "Webhook"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "EvaluationDiscussion" ADD CONSTRAINT "EvaluationDiscussion_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE "EvaluationDiscussion" ADD CONSTRAINT "EvaluationDiscussion_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "EvaluationDiscussion" ADD CONSTRAINT "EvaluationDiscussion_closedById_fkey" FOREIGN KEY ("closedById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
ALTER TABLE "EvaluationDiscussion" ADD CONSTRAINT "EvaluationDiscussion_closedById_fkey" FOREIGN KEY ("closedById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "DiscussionComment" ADD CONSTRAINT "DiscussionComment_discussionId_fkey" FOREIGN KEY ("discussionId") REFERENCES "EvaluationDiscussion"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE "DiscussionComment" ADD CONSTRAINT "DiscussionComment_discussionId_fkey" FOREIGN KEY ("discussionId") REFERENCES "EvaluationDiscussion"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "DiscussionComment" ADD CONSTRAINT "DiscussionComment_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
ALTER TABLE "DiscussionComment" ADD CONSTRAINT "DiscussionComment_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
|
||||||
|
|
@ -6,36 +6,46 @@
|
||||||
-- Missing Foreign Keys
|
-- Missing Foreign Keys
|
||||||
-- =====================================================
|
-- =====================================================
|
||||||
|
|
||||||
-- RoundTemplate → Program
|
-- RoundTemplate -> Program
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "RoundTemplate" ADD CONSTRAINT "RoundTemplate_programId_fkey"
|
ALTER TABLE "RoundTemplate" ADD CONSTRAINT "RoundTemplate_programId_fkey"
|
||||||
FOREIGN KEY ("programId") REFERENCES "Program"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
FOREIGN KEY ("programId") REFERENCES "Program"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- RoundTemplate → User (creator)
|
-- RoundTemplate -> User (creator)
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "RoundTemplate" ADD CONSTRAINT "RoundTemplate_createdBy_fkey"
|
ALTER TABLE "RoundTemplate" ADD CONSTRAINT "RoundTemplate_createdBy_fkey"
|
||||||
FOREIGN KEY ("createdBy") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
FOREIGN KEY ("createdBy") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- Message → Round
|
-- Message -> Round
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "Message" ADD CONSTRAINT "Message_roundId_fkey"
|
ALTER TABLE "Message" ADD CONSTRAINT "Message_roundId_fkey"
|
||||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- EvaluationDiscussion → Round
|
-- EvaluationDiscussion -> Round
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "EvaluationDiscussion" ADD CONSTRAINT "EvaluationDiscussion_roundId_fkey"
|
ALTER TABLE "EvaluationDiscussion" ADD CONSTRAINT "EvaluationDiscussion_roundId_fkey"
|
||||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- ProjectFile → ProjectFile (self-relation for file versioning)
|
-- ProjectFile -> ProjectFile (self-relation for file versioning)
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "ProjectFile" ADD CONSTRAINT "ProjectFile_replacedById_fkey"
|
ALTER TABLE "ProjectFile" ADD CONSTRAINT "ProjectFile_replacedById_fkey"
|
||||||
FOREIGN KEY ("replacedById") REFERENCES "ProjectFile"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
FOREIGN KEY ("replacedById") REFERENCES "ProjectFile"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- =====================================================
|
-- =====================================================
|
||||||
-- Missing Indexes
|
-- Missing Indexes
|
||||||
-- =====================================================
|
-- =====================================================
|
||||||
|
|
||||||
CREATE INDEX "RoundTemplate_roundType_idx" ON "RoundTemplate"("roundType");
|
CREATE INDEX IF NOT EXISTS "RoundTemplate_roundType_idx" ON "RoundTemplate"("roundType");
|
||||||
CREATE INDEX "MentorNote_authorId_idx" ON "MentorNote"("authorId");
|
CREATE INDEX IF NOT EXISTS "MentorNote_authorId_idx" ON "MentorNote"("authorId");
|
||||||
CREATE INDEX "MentorMilestoneCompletion_completedById_idx" ON "MentorMilestoneCompletion"("completedById");
|
CREATE INDEX IF NOT EXISTS "MentorMilestoneCompletion_completedById_idx" ON "MentorMilestoneCompletion"("completedById");
|
||||||
CREATE INDEX "Webhook_createdById_idx" ON "Webhook"("createdById");
|
CREATE INDEX IF NOT EXISTS "Webhook_createdById_idx" ON "Webhook"("createdById");
|
||||||
CREATE INDEX "WebhookDelivery_event_idx" ON "WebhookDelivery"("event");
|
CREATE INDEX IF NOT EXISTS "WebhookDelivery_event_idx" ON "WebhookDelivery"("event");
|
||||||
CREATE INDEX "Message_roundId_idx" ON "Message"("roundId");
|
CREATE INDEX IF NOT EXISTS "Message_roundId_idx" ON "Message"("roundId");
|
||||||
CREATE INDEX "EvaluationDiscussion_closedById_idx" ON "EvaluationDiscussion"("closedById");
|
CREATE INDEX IF NOT EXISTS "EvaluationDiscussion_closedById_idx" ON "EvaluationDiscussion"("closedById");
|
||||||
CREATE INDEX "DiscussionComment_discussionId_idx" ON "DiscussionComment"("discussionId");
|
CREATE INDEX IF NOT EXISTS "DiscussionComment_discussionId_idx" ON "DiscussionComment"("discussionId");
|
||||||
CREATE INDEX "DiscussionComment_userId_idx" ON "DiscussionComment"("userId");
|
CREATE INDEX IF NOT EXISTS "DiscussionComment_userId_idx" ON "DiscussionComment"("userId");
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,15 @@
|
||||||
-- Add SET NULL on ProjectFile.roundId so deleting Round nullifies the reference
|
-- Add SET NULL on ProjectFile.roundId so deleting Round nullifies the reference
|
||||||
|
|
||||||
-- AlterTable: Evaluation.formId -> onDelete CASCADE
|
-- AlterTable: Evaluation.formId -> onDelete CASCADE
|
||||||
ALTER TABLE "Evaluation" DROP CONSTRAINT "Evaluation_formId_fkey";
|
ALTER TABLE "Evaluation" DROP CONSTRAINT IF EXISTS "Evaluation_formId_fkey";
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "Evaluation" ADD CONSTRAINT "Evaluation_formId_fkey"
|
ALTER TABLE "Evaluation" ADD CONSTRAINT "Evaluation_formId_fkey"
|
||||||
FOREIGN KEY ("formId") REFERENCES "EvaluationForm"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
FOREIGN KEY ("formId") REFERENCES "EvaluationForm"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AlterTable: ProjectFile.roundId -> onDelete SET NULL
|
-- AlterTable: ProjectFile.roundId -> onDelete SET NULL
|
||||||
ALTER TABLE "ProjectFile" DROP CONSTRAINT "ProjectFile_roundId_fkey";
|
ALTER TABLE "ProjectFile" DROP CONSTRAINT IF EXISTS "ProjectFile_roundId_fkey";
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "ProjectFile" ADD 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;
|
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "FileRequirement" (
|
CREATE TABLE IF NOT EXISTS "FileRequirement" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"roundId" TEXT NOT NULL,
|
"roundId" TEXT NOT NULL,
|
||||||
"name" TEXT NOT NULL,
|
"name" TEXT NOT NULL,
|
||||||
|
|
@ -15,16 +15,22 @@ CREATE TABLE "FileRequirement" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "FileRequirement_roundId_idx" ON "FileRequirement"("roundId");
|
CREATE INDEX IF NOT EXISTS "FileRequirement_roundId_idx" ON "FileRequirement"("roundId");
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "FileRequirement" ADD CONSTRAINT "FileRequirement_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE "FileRequirement" ADD CONSTRAINT "FileRequirement_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AlterTable: add requirementId to ProjectFile
|
-- AlterTable: add requirementId to ProjectFile
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "ProjectFile" ADD COLUMN "requirementId" TEXT;
|
ALTER TABLE "ProjectFile" ADD COLUMN "requirementId" TEXT;
|
||||||
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "ProjectFile_requirementId_idx" ON "ProjectFile"("requirementId");
|
CREATE INDEX IF NOT EXISTS "ProjectFile_requirementId_idx" ON "ProjectFile"("requirementId");
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "ProjectFile" ADD CONSTRAINT "ProjectFile_requirementId_fkey" FOREIGN KEY ("requirementId") REFERENCES "FileRequirement"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
ALTER TABLE "ProjectFile" ADD CONSTRAINT "ProjectFile_requirementId_fkey" FOREIGN KEY ("requirementId") REFERENCES "FileRequirement"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "AwardVote_awardId_userId_idx" ON "AwardVote"("awardId", "userId");
|
CREATE INDEX IF NOT EXISTS "AwardVote_awardId_userId_idx" ON "AwardVote"("awardId", "userId");
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,26 @@
|
||||||
-- Simplify RoutingMode enum: remove POST_MAIN, rename PARALLEL → SHARED
|
-- Simplify RoutingMode enum: remove POST_MAIN, rename PARALLEL -> SHARED
|
||||||
-- Drop RoutingRule table (routing is now handled via award assignment)
|
-- Drop RoutingRule table (routing is now handled via award assignment)
|
||||||
|
|
||||||
-- 1. Update existing PARALLEL values to SHARED, POST_MAIN to SHARED
|
-- 1. Update existing PARALLEL values to SHARED, POST_MAIN to SHARED
|
||||||
|
-- (safe to run even if no rows match)
|
||||||
UPDATE "Track" SET "routingMode" = 'PARALLEL' WHERE "routingMode" = 'POST_MAIN';
|
UPDATE "Track" SET "routingMode" = 'PARALLEL' WHERE "routingMode" = 'POST_MAIN';
|
||||||
|
|
||||||
-- 2. Rename PARALLEL → SHARED in the enum
|
-- 2. Rename PARALLEL -> SHARED in the enum (only if PARALLEL still exists)
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TYPE "RoutingMode" RENAME VALUE 'PARALLEL' TO 'SHARED';
|
ALTER TYPE "RoutingMode" RENAME VALUE 'PARALLEL' TO 'SHARED';
|
||||||
|
EXCEPTION WHEN invalid_parameter_value THEN NULL; WHEN others THEN NULL; END $$;
|
||||||
|
|
||||||
-- 3. Remove POST_MAIN from the enum
|
-- 3. Remove POST_MAIN from the enum
|
||||||
-- PostgreSQL doesn't support DROP VALUE directly, so we recreate the enum
|
-- PostgreSQL doesn't support DROP VALUE directly, so we recreate the enum
|
||||||
-- Since we already converted POST_MAIN values to PARALLEL (now SHARED), this is safe
|
-- Since we already converted POST_MAIN values to PARALLEL (now SHARED), this is safe
|
||||||
|
|
||||||
-- Create new enum without POST_MAIN
|
-- Only recreate if the old enum still has POST_MAIN (i.e., hasn't been done yet)
|
||||||
-- Actually, since we already renamed PARALLEL to SHARED and converted POST_MAIN rows,
|
DO $$ BEGIN
|
||||||
-- we just need to remove the POST_MAIN value. PostgreSQL 13+ doesn't support dropping
|
IF EXISTS (
|
||||||
-- enum values natively, but since all rows are already migrated, we can:
|
SELECT 1 FROM pg_enum
|
||||||
|
WHERE enumlabel = 'POST_MAIN'
|
||||||
|
AND enumtypid = (SELECT oid FROM pg_type WHERE typname = 'RoutingMode')
|
||||||
|
) THEN
|
||||||
CREATE TYPE "RoutingMode_new" AS ENUM ('SHARED', 'EXCLUSIVE');
|
CREATE TYPE "RoutingMode_new" AS ENUM ('SHARED', 'EXCLUSIVE');
|
||||||
|
|
||||||
ALTER TABLE "Track"
|
ALTER TABLE "Track"
|
||||||
|
|
@ -23,6 +29,8 @@ ALTER TABLE "Track"
|
||||||
|
|
||||||
DROP TYPE "RoutingMode";
|
DROP TYPE "RoutingMode";
|
||||||
ALTER TYPE "RoutingMode_new" RENAME TO "RoutingMode";
|
ALTER TYPE "RoutingMode_new" RENAME TO "RoutingMode";
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
-- 4. Drop the RoutingRule table (no longer needed)
|
-- 4. Drop the RoutingRule table (no longer needed)
|
||||||
DROP TABLE IF EXISTS "RoutingRule";
|
DROP TABLE IF EXISTS "RoutingRule";
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,36 @@
|
||||||
-- =============================================================================
|
-- =============================================================================
|
||||||
-- Phase 0+1: Add Competition/Round Architecture (additive — no breaking changes)
|
-- Phase 0+1: Add Competition/Round Architecture (additive -- no breaking changes)
|
||||||
-- =============================================================================
|
-- =============================================================================
|
||||||
-- New enums, new tables, new optional columns on existing tables.
|
-- New enums, new tables, new optional columns on existing tables.
|
||||||
-- Old Pipeline/Track/Stage tables are untouched.
|
-- Old Pipeline/Track/Stage tables are untouched.
|
||||||
|
|
||||||
-- ─── New Enum Types ──────────────────────────────────────────────────────────
|
-- --- New Enum Types ---
|
||||||
|
|
||||||
CREATE TYPE "CompetitionStatus" AS ENUM ('DRAFT', 'ACTIVE', 'CLOSED', 'ARCHIVED');
|
DO $$ BEGIN CREATE TYPE "CompetitionStatus" AS ENUM ('DRAFT', 'ACTIVE', 'CLOSED', 'ARCHIVED'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
CREATE TYPE "RoundType" AS ENUM ('INTAKE', 'FILTERING', 'EVALUATION', 'SUBMISSION', 'MENTORING', 'LIVE_FINAL', 'DELIBERATION');
|
DO $$ BEGIN CREATE TYPE "RoundType" AS ENUM ('INTAKE', 'FILTERING', 'EVALUATION', 'SUBMISSION', 'MENTORING', 'LIVE_FINAL', 'DELIBERATION'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
CREATE TYPE "RoundStatus" AS ENUM ('ROUND_DRAFT', 'ROUND_ACTIVE', 'ROUND_CLOSED', 'ROUND_ARCHIVED');
|
DO $$ BEGIN CREATE TYPE "RoundStatus" AS ENUM ('ROUND_DRAFT', 'ROUND_ACTIVE', 'ROUND_CLOSED', 'ROUND_ARCHIVED'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
CREATE TYPE "ProjectRoundStateValue" AS ENUM ('PENDING', 'IN_PROGRESS', 'PASSED', 'REJECTED', 'COMPLETED', 'WITHDRAWN');
|
DO $$ BEGIN CREATE TYPE "ProjectRoundStateValue" AS ENUM ('PENDING', 'IN_PROGRESS', 'PASSED', 'REJECTED', 'COMPLETED', 'WITHDRAWN'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
CREATE TYPE "AdvancementRuleType" AS ENUM ('AUTO_ADVANCE', 'SCORE_THRESHOLD', 'TOP_N', 'ADMIN_SELECTION', 'AI_RECOMMENDED');
|
DO $$ BEGIN CREATE TYPE "AdvancementRuleType" AS ENUM ('AUTO_ADVANCE', 'SCORE_THRESHOLD', 'TOP_N', 'ADMIN_SELECTION', 'AI_RECOMMENDED'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
CREATE TYPE "CapMode" AS ENUM ('HARD', 'SOFT', 'NONE');
|
DO $$ BEGIN CREATE TYPE "CapMode" AS ENUM ('HARD', 'SOFT', 'NONE'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
CREATE TYPE "DeadlinePolicy" AS ENUM ('HARD_DEADLINE', 'FLAG', 'GRACE');
|
DO $$ BEGIN CREATE TYPE "DeadlinePolicy" AS ENUM ('HARD_DEADLINE', 'FLAG', 'GRACE'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
CREATE TYPE "JuryGroupMemberRole" AS ENUM ('CHAIR', 'MEMBER', 'OBSERVER');
|
DO $$ BEGIN CREATE TYPE "JuryGroupMemberRole" AS ENUM ('CHAIR', 'MEMBER', 'OBSERVER'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
CREATE TYPE "AssignmentIntentSource" AS ENUM ('INVITE', 'ADMIN', 'SYSTEM');
|
DO $$ BEGIN CREATE TYPE "AssignmentIntentSource" AS ENUM ('INVITE', 'ADMIN', 'SYSTEM'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
CREATE TYPE "AssignmentIntentStatus" AS ENUM ('INTENT_PENDING', 'HONORED', 'OVERRIDDEN', 'EXPIRED', 'CANCELLED');
|
DO $$ BEGIN CREATE TYPE "AssignmentIntentStatus" AS ENUM ('INTENT_PENDING', 'HONORED', 'OVERRIDDEN', 'EXPIRED', 'CANCELLED'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
CREATE TYPE "MentorMessageRole" AS ENUM ('MENTOR_ROLE', 'APPLICANT_ROLE', 'ADMIN_ROLE');
|
DO $$ BEGIN CREATE TYPE "MentorMessageRole" AS ENUM ('MENTOR_ROLE', 'APPLICANT_ROLE', 'ADMIN_ROLE'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
CREATE TYPE "SubmissionPromotionSource" AS ENUM ('MENTOR_FILE', 'ADMIN_REPLACEMENT');
|
DO $$ BEGIN CREATE TYPE "SubmissionPromotionSource" AS ENUM ('MENTOR_FILE', 'ADMIN_REPLACEMENT'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
CREATE TYPE "DeliberationMode" AS ENUM ('SINGLE_WINNER_VOTE', 'FULL_RANKING');
|
DO $$ BEGIN CREATE TYPE "DeliberationMode" AS ENUM ('SINGLE_WINNER_VOTE', 'FULL_RANKING'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
CREATE TYPE "DeliberationStatus" AS ENUM ('DELIB_OPEN', 'VOTING', 'TALLYING', 'RUNOFF', 'DELIB_LOCKED');
|
DO $$ BEGIN CREATE TYPE "DeliberationStatus" AS ENUM ('DELIB_OPEN', 'VOTING', 'TALLYING', 'RUNOFF', 'DELIB_LOCKED'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
CREATE TYPE "TieBreakMethod" AS ENUM ('TIE_RUNOFF', 'TIE_ADMIN_DECIDES', 'SCORE_FALLBACK');
|
DO $$ BEGIN CREATE TYPE "TieBreakMethod" AS ENUM ('TIE_RUNOFF', 'TIE_ADMIN_DECIDES', 'SCORE_FALLBACK'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
CREATE TYPE "DeliberationParticipantStatus" AS ENUM ('REQUIRED', 'ABSENT_EXCUSED', 'REPLACED', 'REPLACEMENT_ACTIVE');
|
DO $$ BEGIN CREATE TYPE "DeliberationParticipantStatus" AS ENUM ('REQUIRED', 'ABSENT_EXCUSED', 'REPLACED', 'REPLACEMENT_ACTIVE'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
CREATE TYPE "AwardEligibilityMode" AS ENUM ('SEPARATE_POOL', 'STAY_IN_MAIN');
|
DO $$ BEGIN CREATE TYPE "AwardEligibilityMode" AS ENUM ('SEPARATE_POOL', 'STAY_IN_MAIN'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- Add FEATURE_FLAGS to SettingCategory enum
|
-- Add FEATURE_FLAGS to SettingCategory enum
|
||||||
ALTER TYPE "SettingCategory" ADD VALUE 'FEATURE_FLAGS';
|
DO $$ BEGIN ALTER TYPE "SettingCategory" ADD VALUE 'FEATURE_FLAGS'; EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- ─── New Tables ──────────────────────────────────────────────────────────────
|
-- --- New Tables ---
|
||||||
|
|
||||||
-- Competition (replaces Pipeline)
|
-- Competition (replaces Pipeline)
|
||||||
CREATE TABLE "Competition" (
|
CREATE TABLE IF NOT EXISTS "Competition" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"programId" TEXT NOT NULL,
|
"programId" TEXT NOT NULL,
|
||||||
"name" TEXT NOT NULL,
|
"name" TEXT NOT NULL,
|
||||||
|
|
@ -49,7 +49,7 @@ CREATE TABLE "Competition" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Round (replaces Stage)
|
-- Round (replaces Stage)
|
||||||
CREATE TABLE "Round" (
|
CREATE TABLE IF NOT EXISTS "Round" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"competitionId" TEXT NOT NULL,
|
"competitionId" TEXT NOT NULL,
|
||||||
"name" TEXT NOT NULL,
|
"name" TEXT NOT NULL,
|
||||||
|
|
@ -70,7 +70,7 @@ CREATE TABLE "Round" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- ProjectRoundState
|
-- ProjectRoundState
|
||||||
CREATE TABLE "ProjectRoundState" (
|
CREATE TABLE IF NOT EXISTS "ProjectRoundState" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"projectId" TEXT NOT NULL,
|
"projectId" TEXT NOT NULL,
|
||||||
"roundId" TEXT NOT NULL,
|
"roundId" TEXT NOT NULL,
|
||||||
|
|
@ -85,7 +85,7 @@ CREATE TABLE "ProjectRoundState" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- AdvancementRule
|
-- AdvancementRule
|
||||||
CREATE TABLE "AdvancementRule" (
|
CREATE TABLE IF NOT EXISTS "AdvancementRule" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"roundId" TEXT NOT NULL,
|
"roundId" TEXT NOT NULL,
|
||||||
"targetRoundId" TEXT,
|
"targetRoundId" TEXT,
|
||||||
|
|
@ -99,7 +99,7 @@ CREATE TABLE "AdvancementRule" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- JuryGroup
|
-- JuryGroup
|
||||||
CREATE TABLE "JuryGroup" (
|
CREATE TABLE IF NOT EXISTS "JuryGroup" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"competitionId" TEXT NOT NULL,
|
"competitionId" TEXT NOT NULL,
|
||||||
"name" TEXT NOT NULL,
|
"name" TEXT NOT NULL,
|
||||||
|
|
@ -120,7 +120,7 @@ CREATE TABLE "JuryGroup" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- JuryGroupMember
|
-- JuryGroupMember
|
||||||
CREATE TABLE "JuryGroupMember" (
|
CREATE TABLE IF NOT EXISTS "JuryGroupMember" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"juryGroupId" TEXT NOT NULL,
|
"juryGroupId" TEXT NOT NULL,
|
||||||
"userId" TEXT NOT NULL,
|
"userId" TEXT NOT NULL,
|
||||||
|
|
@ -138,7 +138,7 @@ CREATE TABLE "JuryGroupMember" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- SubmissionWindow
|
-- SubmissionWindow
|
||||||
CREATE TABLE "SubmissionWindow" (
|
CREATE TABLE IF NOT EXISTS "SubmissionWindow" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"competitionId" TEXT NOT NULL,
|
"competitionId" TEXT NOT NULL,
|
||||||
"name" TEXT NOT NULL,
|
"name" TEXT NOT NULL,
|
||||||
|
|
@ -158,7 +158,7 @@ CREATE TABLE "SubmissionWindow" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- SubmissionFileRequirement
|
-- SubmissionFileRequirement
|
||||||
CREATE TABLE "SubmissionFileRequirement" (
|
CREATE TABLE IF NOT EXISTS "SubmissionFileRequirement" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"submissionWindowId" TEXT NOT NULL,
|
"submissionWindowId" TEXT NOT NULL,
|
||||||
"label" TEXT NOT NULL,
|
"label" TEXT NOT NULL,
|
||||||
|
|
@ -175,7 +175,7 @@ CREATE TABLE "SubmissionFileRequirement" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- RoundSubmissionVisibility
|
-- RoundSubmissionVisibility
|
||||||
CREATE TABLE "RoundSubmissionVisibility" (
|
CREATE TABLE IF NOT EXISTS "RoundSubmissionVisibility" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"roundId" TEXT NOT NULL,
|
"roundId" TEXT NOT NULL,
|
||||||
"submissionWindowId" TEXT NOT NULL,
|
"submissionWindowId" TEXT NOT NULL,
|
||||||
|
|
@ -186,7 +186,7 @@ CREATE TABLE "RoundSubmissionVisibility" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- AssignmentIntent
|
-- AssignmentIntent
|
||||||
CREATE TABLE "AssignmentIntent" (
|
CREATE TABLE IF NOT EXISTS "AssignmentIntent" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"juryGroupMemberId" TEXT NOT NULL,
|
"juryGroupMemberId" TEXT NOT NULL,
|
||||||
"roundId" TEXT NOT NULL,
|
"roundId" TEXT NOT NULL,
|
||||||
|
|
@ -200,7 +200,7 @@ CREATE TABLE "AssignmentIntent" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- AssignmentException
|
-- AssignmentException
|
||||||
CREATE TABLE "AssignmentException" (
|
CREATE TABLE IF NOT EXISTS "AssignmentException" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"assignmentId" TEXT NOT NULL,
|
"assignmentId" TEXT NOT NULL,
|
||||||
"reason" TEXT NOT NULL,
|
"reason" TEXT NOT NULL,
|
||||||
|
|
@ -212,7 +212,7 @@ CREATE TABLE "AssignmentException" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- MentorFile
|
-- MentorFile
|
||||||
CREATE TABLE "MentorFile" (
|
CREATE TABLE IF NOT EXISTS "MentorFile" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"mentorAssignmentId" TEXT NOT NULL,
|
"mentorAssignmentId" TEXT NOT NULL,
|
||||||
"uploadedByUserId" TEXT NOT NULL,
|
"uploadedByUserId" TEXT NOT NULL,
|
||||||
|
|
@ -232,7 +232,7 @@ CREATE TABLE "MentorFile" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- MentorFileComment
|
-- MentorFileComment
|
||||||
CREATE TABLE "MentorFileComment" (
|
CREATE TABLE IF NOT EXISTS "MentorFileComment" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"mentorFileId" TEXT NOT NULL,
|
"mentorFileId" TEXT NOT NULL,
|
||||||
"authorId" TEXT NOT NULL,
|
"authorId" TEXT NOT NULL,
|
||||||
|
|
@ -245,7 +245,7 @@ CREATE TABLE "MentorFileComment" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- SubmissionPromotionEvent
|
-- SubmissionPromotionEvent
|
||||||
CREATE TABLE "SubmissionPromotionEvent" (
|
CREATE TABLE IF NOT EXISTS "SubmissionPromotionEvent" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"projectId" TEXT NOT NULL,
|
"projectId" TEXT NOT NULL,
|
||||||
"roundId" TEXT NOT NULL,
|
"roundId" TEXT NOT NULL,
|
||||||
|
|
@ -259,7 +259,7 @@ CREATE TABLE "SubmissionPromotionEvent" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- DeliberationSession
|
-- DeliberationSession
|
||||||
CREATE TABLE "DeliberationSession" (
|
CREATE TABLE IF NOT EXISTS "DeliberationSession" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"competitionId" TEXT NOT NULL,
|
"competitionId" TEXT NOT NULL,
|
||||||
"roundId" TEXT NOT NULL,
|
"roundId" TEXT NOT NULL,
|
||||||
|
|
@ -277,7 +277,7 @@ CREATE TABLE "DeliberationSession" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- DeliberationVote
|
-- DeliberationVote
|
||||||
CREATE TABLE "DeliberationVote" (
|
CREATE TABLE IF NOT EXISTS "DeliberationVote" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"sessionId" TEXT NOT NULL,
|
"sessionId" TEXT NOT NULL,
|
||||||
"juryMemberId" TEXT NOT NULL,
|
"juryMemberId" TEXT NOT NULL,
|
||||||
|
|
@ -291,7 +291,7 @@ CREATE TABLE "DeliberationVote" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- DeliberationResult
|
-- DeliberationResult
|
||||||
CREATE TABLE "DeliberationResult" (
|
CREATE TABLE IF NOT EXISTS "DeliberationResult" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"sessionId" TEXT NOT NULL,
|
"sessionId" TEXT NOT NULL,
|
||||||
"projectId" TEXT NOT NULL,
|
"projectId" TEXT NOT NULL,
|
||||||
|
|
@ -304,7 +304,7 @@ CREATE TABLE "DeliberationResult" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- DeliberationParticipant
|
-- DeliberationParticipant
|
||||||
CREATE TABLE "DeliberationParticipant" (
|
CREATE TABLE IF NOT EXISTS "DeliberationParticipant" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"sessionId" TEXT NOT NULL,
|
"sessionId" TEXT NOT NULL,
|
||||||
"userId" TEXT NOT NULL,
|
"userId" TEXT NOT NULL,
|
||||||
|
|
@ -315,7 +315,7 @@ CREATE TABLE "DeliberationParticipant" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- ResultLock
|
-- ResultLock
|
||||||
CREATE TABLE "ResultLock" (
|
CREATE TABLE IF NOT EXISTS "ResultLock" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"competitionId" TEXT NOT NULL,
|
"competitionId" TEXT NOT NULL,
|
||||||
"roundId" TEXT NOT NULL,
|
"roundId" TEXT NOT NULL,
|
||||||
|
|
@ -328,7 +328,7 @@ CREATE TABLE "ResultLock" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- ResultUnlockEvent
|
-- ResultUnlockEvent
|
||||||
CREATE TABLE "ResultUnlockEvent" (
|
CREATE TABLE IF NOT EXISTS "ResultUnlockEvent" (
|
||||||
"id" TEXT NOT NULL,
|
"id" TEXT NOT NULL,
|
||||||
"resultLockId" TEXT NOT NULL,
|
"resultLockId" TEXT NOT NULL,
|
||||||
"unlockedById" TEXT NOT NULL,
|
"unlockedById" TEXT NOT NULL,
|
||||||
|
|
@ -338,235 +338,365 @@ CREATE TABLE "ResultUnlockEvent" (
|
||||||
CONSTRAINT "ResultUnlockEvent_pkey" PRIMARY KEY ("id")
|
CONSTRAINT "ResultUnlockEvent_pkey" PRIMARY KEY ("id")
|
||||||
);
|
);
|
||||||
|
|
||||||
-- ─── Add Columns to Existing Tables ──────────────────────────────────────────
|
-- --- Add Columns to Existing Tables ---
|
||||||
|
|
||||||
-- Assignment: add juryGroupId
|
-- Assignment: add juryGroupId
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "Assignment" ADD COLUMN "juryGroupId" TEXT;
|
ALTER TABLE "Assignment" ADD COLUMN "juryGroupId" TEXT;
|
||||||
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
|
||||||
-- SpecialAward: add competition/round architecture fields
|
-- SpecialAward: add competition/round architecture fields
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "SpecialAward" ADD COLUMN "competitionId" TEXT;
|
ALTER TABLE "SpecialAward" ADD COLUMN "competitionId" TEXT;
|
||||||
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "SpecialAward" ADD COLUMN "evaluationRoundId" TEXT;
|
ALTER TABLE "SpecialAward" ADD COLUMN "evaluationRoundId" TEXT;
|
||||||
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "SpecialAward" ADD COLUMN "juryGroupId" TEXT;
|
ALTER TABLE "SpecialAward" ADD COLUMN "juryGroupId" TEXT;
|
||||||
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "SpecialAward" ADD COLUMN "eligibilityMode" "AwardEligibilityMode" NOT NULL DEFAULT 'STAY_IN_MAIN';
|
ALTER TABLE "SpecialAward" ADD COLUMN "eligibilityMode" "AwardEligibilityMode" NOT NULL DEFAULT 'STAY_IN_MAIN';
|
||||||
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "SpecialAward" ADD COLUMN "decisionMode" TEXT;
|
ALTER TABLE "SpecialAward" ADD COLUMN "decisionMode" TEXT;
|
||||||
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
|
||||||
-- MentorAssignment: add workspace fields
|
-- MentorAssignment: add workspace fields
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "MentorAssignment" ADD COLUMN "workspaceEnabled" BOOLEAN NOT NULL DEFAULT false;
|
ALTER TABLE "MentorAssignment" ADD COLUMN "workspaceEnabled" BOOLEAN NOT NULL DEFAULT false;
|
||||||
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "MentorAssignment" ADD COLUMN "workspaceOpenAt" TIMESTAMP(3);
|
ALTER TABLE "MentorAssignment" ADD COLUMN "workspaceOpenAt" TIMESTAMP(3);
|
||||||
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "MentorAssignment" ADD COLUMN "workspaceCloseAt" TIMESTAMP(3);
|
ALTER TABLE "MentorAssignment" ADD COLUMN "workspaceCloseAt" TIMESTAMP(3);
|
||||||
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
|
||||||
-- MentorMessage: add workspace fields
|
-- MentorMessage: add workspace fields
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "MentorMessage" ADD COLUMN "workspaceId" TEXT;
|
ALTER TABLE "MentorMessage" ADD COLUMN "workspaceId" TEXT;
|
||||||
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "MentorMessage" ADD COLUMN "senderRole" "MentorMessageRole";
|
ALTER TABLE "MentorMessage" ADD COLUMN "senderRole" "MentorMessageRole";
|
||||||
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
|
||||||
-- ProjectFile: add submission window link
|
-- ProjectFile: add submission window link
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "ProjectFile" ADD COLUMN "submissionWindowId" TEXT;
|
ALTER TABLE "ProjectFile" ADD COLUMN "submissionWindowId" TEXT;
|
||||||
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "ProjectFile" ADD COLUMN "submissionFileRequirementId" TEXT;
|
ALTER TABLE "ProjectFile" ADD COLUMN "submissionFileRequirementId" TEXT;
|
||||||
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
|
||||||
-- ─── Unique Constraints ──────────────────────────────────────────────────────
|
-- --- Unique Constraints ---
|
||||||
|
|
||||||
CREATE UNIQUE INDEX "Competition_slug_key" ON "Competition"("slug");
|
CREATE UNIQUE INDEX IF NOT EXISTS "Competition_slug_key" ON "Competition"("slug");
|
||||||
CREATE UNIQUE INDEX "Round_competitionId_slug_key" ON "Round"("competitionId", "slug");
|
CREATE UNIQUE INDEX IF NOT EXISTS "Round_competitionId_slug_key" ON "Round"("competitionId", "slug");
|
||||||
CREATE UNIQUE INDEX "Round_competitionId_sortOrder_key" ON "Round"("competitionId", "sortOrder");
|
CREATE UNIQUE INDEX IF NOT EXISTS "Round_competitionId_sortOrder_key" ON "Round"("competitionId", "sortOrder");
|
||||||
CREATE UNIQUE INDEX "ProjectRoundState_projectId_roundId_key" ON "ProjectRoundState"("projectId", "roundId");
|
CREATE UNIQUE INDEX IF NOT EXISTS "ProjectRoundState_projectId_roundId_key" ON "ProjectRoundState"("projectId", "roundId");
|
||||||
CREATE UNIQUE INDEX "JuryGroup_competitionId_slug_key" ON "JuryGroup"("competitionId", "slug");
|
CREATE UNIQUE INDEX IF NOT EXISTS "JuryGroup_competitionId_slug_key" ON "JuryGroup"("competitionId", "slug");
|
||||||
CREATE UNIQUE INDEX "JuryGroupMember_juryGroupId_userId_key" ON "JuryGroupMember"("juryGroupId", "userId");
|
CREATE UNIQUE INDEX IF NOT EXISTS "JuryGroupMember_juryGroupId_userId_key" ON "JuryGroupMember"("juryGroupId", "userId");
|
||||||
CREATE UNIQUE INDEX "SubmissionWindow_competitionId_slug_key" ON "SubmissionWindow"("competitionId", "slug");
|
CREATE UNIQUE INDEX IF NOT EXISTS "SubmissionWindow_competitionId_slug_key" ON "SubmissionWindow"("competitionId", "slug");
|
||||||
CREATE UNIQUE INDEX "SubmissionWindow_competitionId_roundNumber_key" ON "SubmissionWindow"("competitionId", "roundNumber");
|
CREATE UNIQUE INDEX IF NOT EXISTS "SubmissionWindow_competitionId_roundNumber_key" ON "SubmissionWindow"("competitionId", "roundNumber");
|
||||||
CREATE UNIQUE INDEX "RoundSubmissionVisibility_roundId_submissionWindowId_key" ON "RoundSubmissionVisibility"("roundId", "submissionWindowId");
|
CREATE UNIQUE INDEX IF NOT EXISTS "RoundSubmissionVisibility_roundId_submissionWindowId_key" ON "RoundSubmissionVisibility"("roundId", "submissionWindowId");
|
||||||
CREATE UNIQUE INDEX "AssignmentIntent_juryGroupMemberId_roundId_projectId_key" ON "AssignmentIntent"("juryGroupMemberId", "roundId", "projectId");
|
CREATE UNIQUE INDEX IF NOT EXISTS "AssignmentIntent_juryGroupMemberId_roundId_projectId_key" ON "AssignmentIntent"("juryGroupMemberId", "roundId", "projectId");
|
||||||
CREATE UNIQUE INDEX "MentorFile_promotedToFileId_key" ON "MentorFile"("promotedToFileId");
|
CREATE UNIQUE INDEX IF NOT EXISTS "MentorFile_promotedToFileId_key" ON "MentorFile"("promotedToFileId");
|
||||||
CREATE UNIQUE INDEX "DeliberationVote_sessionId_juryMemberId_projectId_runoffRo_key" ON "DeliberationVote"("sessionId", "juryMemberId", "projectId", "runoffRound");
|
CREATE UNIQUE INDEX IF NOT EXISTS "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 IF NOT EXISTS "DeliberationResult_sessionId_projectId_key" ON "DeliberationResult"("sessionId", "projectId");
|
||||||
CREATE UNIQUE INDEX "DeliberationParticipant_sessionId_userId_key" ON "DeliberationParticipant"("sessionId", "userId");
|
CREATE UNIQUE INDEX IF NOT EXISTS "DeliberationParticipant_sessionId_userId_key" ON "DeliberationParticipant"("sessionId", "userId");
|
||||||
CREATE UNIQUE INDEX "SubmissionFileRequirement_submissionWindowId_slug_key" ON "SubmissionFileRequirement"("submissionWindowId", "slug");
|
CREATE UNIQUE INDEX IF NOT EXISTS "SubmissionFileRequirement_submissionWindowId_slug_key" ON "SubmissionFileRequirement"("submissionWindowId", "slug");
|
||||||
CREATE UNIQUE INDEX "AdvancementRule_roundId_sortOrder_key" ON "AdvancementRule"("roundId", "sortOrder");
|
CREATE UNIQUE INDEX IF NOT EXISTS "AdvancementRule_roundId_sortOrder_key" ON "AdvancementRule"("roundId", "sortOrder");
|
||||||
|
|
||||||
-- ─── Indexes ─────────────────────────────────────────────────────────────────
|
-- --- Indexes ---
|
||||||
|
|
||||||
-- Competition
|
-- Competition
|
||||||
CREATE INDEX "Competition_programId_idx" ON "Competition"("programId");
|
CREATE INDEX IF NOT EXISTS "Competition_programId_idx" ON "Competition"("programId");
|
||||||
CREATE INDEX "Competition_status_idx" ON "Competition"("status");
|
CREATE INDEX IF NOT EXISTS "Competition_status_idx" ON "Competition"("status");
|
||||||
|
|
||||||
-- Round
|
-- Round
|
||||||
CREATE INDEX "Round_competitionId_idx" ON "Round"("competitionId");
|
CREATE INDEX IF NOT EXISTS "Round_competitionId_idx" ON "Round"("competitionId");
|
||||||
CREATE INDEX "Round_roundType_idx" ON "Round"("roundType");
|
CREATE INDEX IF NOT EXISTS "Round_roundType_idx" ON "Round"("roundType");
|
||||||
CREATE INDEX "Round_status_idx" ON "Round"("status");
|
CREATE INDEX IF NOT EXISTS "Round_status_idx" ON "Round"("status");
|
||||||
|
|
||||||
-- ProjectRoundState
|
-- ProjectRoundState
|
||||||
CREATE INDEX "ProjectRoundState_projectId_idx" ON "ProjectRoundState"("projectId");
|
CREATE INDEX IF NOT EXISTS "ProjectRoundState_projectId_idx" ON "ProjectRoundState"("projectId");
|
||||||
CREATE INDEX "ProjectRoundState_roundId_idx" ON "ProjectRoundState"("roundId");
|
CREATE INDEX IF NOT EXISTS "ProjectRoundState_roundId_idx" ON "ProjectRoundState"("roundId");
|
||||||
CREATE INDEX "ProjectRoundState_state_idx" ON "ProjectRoundState"("state");
|
CREATE INDEX IF NOT EXISTS "ProjectRoundState_state_idx" ON "ProjectRoundState"("state");
|
||||||
|
|
||||||
-- AdvancementRule
|
-- AdvancementRule
|
||||||
CREATE INDEX "AdvancementRule_roundId_idx" ON "AdvancementRule"("roundId");
|
CREATE INDEX IF NOT EXISTS "AdvancementRule_roundId_idx" ON "AdvancementRule"("roundId");
|
||||||
|
|
||||||
-- JuryGroup
|
-- JuryGroup
|
||||||
CREATE INDEX "JuryGroup_competitionId_idx" ON "JuryGroup"("competitionId");
|
CREATE INDEX IF NOT EXISTS "JuryGroup_competitionId_idx" ON "JuryGroup"("competitionId");
|
||||||
|
|
||||||
-- JuryGroupMember
|
-- JuryGroupMember
|
||||||
CREATE INDEX "JuryGroupMember_juryGroupId_idx" ON "JuryGroupMember"("juryGroupId");
|
CREATE INDEX IF NOT EXISTS "JuryGroupMember_juryGroupId_idx" ON "JuryGroupMember"("juryGroupId");
|
||||||
CREATE INDEX "JuryGroupMember_userId_idx" ON "JuryGroupMember"("userId");
|
CREATE INDEX IF NOT EXISTS "JuryGroupMember_userId_idx" ON "JuryGroupMember"("userId");
|
||||||
|
|
||||||
-- SubmissionWindow
|
-- SubmissionWindow
|
||||||
CREATE INDEX "SubmissionWindow_competitionId_idx" ON "SubmissionWindow"("competitionId");
|
CREATE INDEX IF NOT EXISTS "SubmissionWindow_competitionId_idx" ON "SubmissionWindow"("competitionId");
|
||||||
|
|
||||||
-- SubmissionFileRequirement
|
-- SubmissionFileRequirement
|
||||||
CREATE INDEX "SubmissionFileRequirement_submissionWindowId_idx" ON "SubmissionFileRequirement"("submissionWindowId");
|
CREATE INDEX IF NOT EXISTS "SubmissionFileRequirement_submissionWindowId_idx" ON "SubmissionFileRequirement"("submissionWindowId");
|
||||||
|
|
||||||
-- RoundSubmissionVisibility
|
-- RoundSubmissionVisibility
|
||||||
CREATE INDEX "RoundSubmissionVisibility_roundId_idx" ON "RoundSubmissionVisibility"("roundId");
|
CREATE INDEX IF NOT EXISTS "RoundSubmissionVisibility_roundId_idx" ON "RoundSubmissionVisibility"("roundId");
|
||||||
|
|
||||||
-- AssignmentIntent
|
-- AssignmentIntent
|
||||||
CREATE INDEX "AssignmentIntent_roundId_idx" ON "AssignmentIntent"("roundId");
|
CREATE INDEX IF NOT EXISTS "AssignmentIntent_roundId_idx" ON "AssignmentIntent"("roundId");
|
||||||
CREATE INDEX "AssignmentIntent_projectId_idx" ON "AssignmentIntent"("projectId");
|
CREATE INDEX IF NOT EXISTS "AssignmentIntent_projectId_idx" ON "AssignmentIntent"("projectId");
|
||||||
CREATE INDEX "AssignmentIntent_status_idx" ON "AssignmentIntent"("status");
|
CREATE INDEX IF NOT EXISTS "AssignmentIntent_status_idx" ON "AssignmentIntent"("status");
|
||||||
|
|
||||||
-- AssignmentException
|
-- AssignmentException
|
||||||
CREATE INDEX "AssignmentException_assignmentId_idx" ON "AssignmentException"("assignmentId");
|
CREATE INDEX IF NOT EXISTS "AssignmentException_assignmentId_idx" ON "AssignmentException"("assignmentId");
|
||||||
CREATE INDEX "AssignmentException_approvedById_idx" ON "AssignmentException"("approvedById");
|
CREATE INDEX IF NOT EXISTS "AssignmentException_approvedById_idx" ON "AssignmentException"("approvedById");
|
||||||
|
|
||||||
-- MentorFile
|
-- MentorFile
|
||||||
CREATE INDEX "MentorFile_mentorAssignmentId_idx" ON "MentorFile"("mentorAssignmentId");
|
CREATE INDEX IF NOT EXISTS "MentorFile_mentorAssignmentId_idx" ON "MentorFile"("mentorAssignmentId");
|
||||||
CREATE INDEX "MentorFile_uploadedByUserId_idx" ON "MentorFile"("uploadedByUserId");
|
CREATE INDEX IF NOT EXISTS "MentorFile_uploadedByUserId_idx" ON "MentorFile"("uploadedByUserId");
|
||||||
|
|
||||||
-- MentorFileComment
|
-- MentorFileComment
|
||||||
CREATE INDEX "MentorFileComment_mentorFileId_idx" ON "MentorFileComment"("mentorFileId");
|
CREATE INDEX IF NOT EXISTS "MentorFileComment_mentorFileId_idx" ON "MentorFileComment"("mentorFileId");
|
||||||
CREATE INDEX "MentorFileComment_authorId_idx" ON "MentorFileComment"("authorId");
|
CREATE INDEX IF NOT EXISTS "MentorFileComment_authorId_idx" ON "MentorFileComment"("authorId");
|
||||||
CREATE INDEX "MentorFileComment_parentCommentId_idx" ON "MentorFileComment"("parentCommentId");
|
CREATE INDEX IF NOT EXISTS "MentorFileComment_parentCommentId_idx" ON "MentorFileComment"("parentCommentId");
|
||||||
|
|
||||||
-- SubmissionPromotionEvent
|
-- SubmissionPromotionEvent
|
||||||
CREATE INDEX "SubmissionPromotionEvent_projectId_idx" ON "SubmissionPromotionEvent"("projectId");
|
CREATE INDEX IF NOT EXISTS "SubmissionPromotionEvent_projectId_idx" ON "SubmissionPromotionEvent"("projectId");
|
||||||
CREATE INDEX "SubmissionPromotionEvent_roundId_idx" ON "SubmissionPromotionEvent"("roundId");
|
CREATE INDEX IF NOT EXISTS "SubmissionPromotionEvent_roundId_idx" ON "SubmissionPromotionEvent"("roundId");
|
||||||
CREATE INDEX "SubmissionPromotionEvent_sourceFileId_idx" ON "SubmissionPromotionEvent"("sourceFileId");
|
CREATE INDEX IF NOT EXISTS "SubmissionPromotionEvent_sourceFileId_idx" ON "SubmissionPromotionEvent"("sourceFileId");
|
||||||
|
|
||||||
-- DeliberationSession
|
-- DeliberationSession
|
||||||
CREATE INDEX "DeliberationSession_competitionId_idx" ON "DeliberationSession"("competitionId");
|
CREATE INDEX IF NOT EXISTS "DeliberationSession_competitionId_idx" ON "DeliberationSession"("competitionId");
|
||||||
CREATE INDEX "DeliberationSession_roundId_idx" ON "DeliberationSession"("roundId");
|
CREATE INDEX IF NOT EXISTS "DeliberationSession_roundId_idx" ON "DeliberationSession"("roundId");
|
||||||
CREATE INDEX "DeliberationSession_status_idx" ON "DeliberationSession"("status");
|
CREATE INDEX IF NOT EXISTS "DeliberationSession_status_idx" ON "DeliberationSession"("status");
|
||||||
|
|
||||||
-- DeliberationVote
|
-- DeliberationVote
|
||||||
CREATE INDEX "DeliberationVote_sessionId_idx" ON "DeliberationVote"("sessionId");
|
CREATE INDEX IF NOT EXISTS "DeliberationVote_sessionId_idx" ON "DeliberationVote"("sessionId");
|
||||||
CREATE INDEX "DeliberationVote_juryMemberId_idx" ON "DeliberationVote"("juryMemberId");
|
CREATE INDEX IF NOT EXISTS "DeliberationVote_juryMemberId_idx" ON "DeliberationVote"("juryMemberId");
|
||||||
CREATE INDEX "DeliberationVote_projectId_idx" ON "DeliberationVote"("projectId");
|
CREATE INDEX IF NOT EXISTS "DeliberationVote_projectId_idx" ON "DeliberationVote"("projectId");
|
||||||
|
|
||||||
-- DeliberationResult
|
-- DeliberationResult
|
||||||
CREATE INDEX "DeliberationResult_sessionId_idx" ON "DeliberationResult"("sessionId");
|
CREATE INDEX IF NOT EXISTS "DeliberationResult_sessionId_idx" ON "DeliberationResult"("sessionId");
|
||||||
CREATE INDEX "DeliberationResult_projectId_idx" ON "DeliberationResult"("projectId");
|
CREATE INDEX IF NOT EXISTS "DeliberationResult_projectId_idx" ON "DeliberationResult"("projectId");
|
||||||
|
|
||||||
-- DeliberationParticipant
|
-- DeliberationParticipant
|
||||||
CREATE INDEX "DeliberationParticipant_sessionId_idx" ON "DeliberationParticipant"("sessionId");
|
CREATE INDEX IF NOT EXISTS "DeliberationParticipant_sessionId_idx" ON "DeliberationParticipant"("sessionId");
|
||||||
CREATE INDEX "DeliberationParticipant_userId_idx" ON "DeliberationParticipant"("userId");
|
CREATE INDEX IF NOT EXISTS "DeliberationParticipant_userId_idx" ON "DeliberationParticipant"("userId");
|
||||||
|
|
||||||
-- ResultLock
|
-- ResultLock
|
||||||
CREATE INDEX "ResultLock_competitionId_idx" ON "ResultLock"("competitionId");
|
CREATE INDEX IF NOT EXISTS "ResultLock_competitionId_idx" ON "ResultLock"("competitionId");
|
||||||
CREATE INDEX "ResultLock_roundId_idx" ON "ResultLock"("roundId");
|
CREATE INDEX IF NOT EXISTS "ResultLock_roundId_idx" ON "ResultLock"("roundId");
|
||||||
CREATE INDEX "ResultLock_category_idx" ON "ResultLock"("category");
|
CREATE INDEX IF NOT EXISTS "ResultLock_category_idx" ON "ResultLock"("category");
|
||||||
|
|
||||||
-- ResultUnlockEvent
|
-- ResultUnlockEvent
|
||||||
CREATE INDEX "ResultUnlockEvent_resultLockId_idx" ON "ResultUnlockEvent"("resultLockId");
|
CREATE INDEX IF NOT EXISTS "ResultUnlockEvent_resultLockId_idx" ON "ResultUnlockEvent"("resultLockId");
|
||||||
CREATE INDEX "ResultUnlockEvent_unlockedById_idx" ON "ResultUnlockEvent"("unlockedById");
|
CREATE INDEX IF NOT EXISTS "ResultUnlockEvent_unlockedById_idx" ON "ResultUnlockEvent"("unlockedById");
|
||||||
|
|
||||||
-- Indexes on modified existing tables
|
-- Indexes on modified existing tables
|
||||||
CREATE INDEX "Assignment_juryGroupId_idx" ON "Assignment"("juryGroupId");
|
CREATE INDEX IF NOT EXISTS "Assignment_juryGroupId_idx" ON "Assignment"("juryGroupId");
|
||||||
CREATE INDEX "SpecialAward_competitionId_idx" ON "SpecialAward"("competitionId");
|
CREATE INDEX IF NOT EXISTS "SpecialAward_competitionId_idx" ON "SpecialAward"("competitionId");
|
||||||
CREATE INDEX "SpecialAward_evaluationRoundId_idx" ON "SpecialAward"("evaluationRoundId");
|
CREATE INDEX IF NOT EXISTS "SpecialAward_evaluationRoundId_idx" ON "SpecialAward"("evaluationRoundId");
|
||||||
CREATE INDEX "MentorMessage_workspaceId_idx" ON "MentorMessage"("workspaceId");
|
CREATE INDEX IF NOT EXISTS "MentorMessage_workspaceId_idx" ON "MentorMessage"("workspaceId");
|
||||||
CREATE INDEX "ProjectFile_submissionWindowId_idx" ON "ProjectFile"("submissionWindowId");
|
CREATE INDEX IF NOT EXISTS "ProjectFile_submissionWindowId_idx" ON "ProjectFile"("submissionWindowId");
|
||||||
CREATE INDEX "ProjectFile_submissionFileRequirementId_idx" ON "ProjectFile"("submissionFileRequirementId");
|
CREATE INDEX IF NOT EXISTS "ProjectFile_submissionFileRequirementId_idx" ON "ProjectFile"("submissionFileRequirementId");
|
||||||
|
|
||||||
-- ─── Foreign Keys ────────────────────────────────────────────────────────────
|
-- --- Foreign Keys ---
|
||||||
|
|
||||||
-- Competition
|
-- Competition
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "Competition" ADD CONSTRAINT "Competition_programId_fkey" FOREIGN KEY ("programId") REFERENCES "Program"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE "Competition" ADD CONSTRAINT "Competition_programId_fkey" FOREIGN KEY ("programId") REFERENCES "Program"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- Round
|
-- Round
|
||||||
|
DO $$ BEGIN
|
||||||
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_competitionId_fkey" FOREIGN KEY ("competitionId") REFERENCES "Competition"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
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_juryGroupId_fkey" FOREIGN KEY ("juryGroupId") REFERENCES "JuryGroup"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "Round" ADD CONSTRAINT "Round_submissionWindowId_fkey" FOREIGN KEY ("submissionWindowId") REFERENCES "SubmissionWindow"("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;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- ProjectRoundState
|
-- ProjectRoundState
|
||||||
|
DO $$ BEGIN
|
||||||
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_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "ProjectRoundState" ADD CONSTRAINT "ProjectRoundState_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "Round"("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;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AdvancementRule
|
-- AdvancementRule
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "AdvancementRule" ADD CONSTRAINT "AdvancementRule_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE "AdvancementRule" ADD CONSTRAINT "AdvancementRule_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- JuryGroup
|
-- JuryGroup
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "JuryGroup" ADD CONSTRAINT "JuryGroup_competitionId_fkey" FOREIGN KEY ("competitionId") REFERENCES "Competition"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE "JuryGroup" ADD CONSTRAINT "JuryGroup_competitionId_fkey" FOREIGN KEY ("competitionId") REFERENCES "Competition"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- JuryGroupMember
|
-- JuryGroupMember
|
||||||
|
DO $$ BEGIN
|
||||||
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_juryGroupId_fkey" FOREIGN KEY ("juryGroupId") REFERENCES "JuryGroup"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "JuryGroupMember" ADD CONSTRAINT "JuryGroupMember_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("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;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- SubmissionWindow
|
-- SubmissionWindow
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "SubmissionWindow" ADD CONSTRAINT "SubmissionWindow_competitionId_fkey" FOREIGN KEY ("competitionId") REFERENCES "Competition"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE "SubmissionWindow" ADD CONSTRAINT "SubmissionWindow_competitionId_fkey" FOREIGN KEY ("competitionId") REFERENCES "Competition"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- SubmissionFileRequirement
|
-- SubmissionFileRequirement
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "SubmissionFileRequirement" ADD CONSTRAINT "SubmissionFileRequirement_submissionWindowId_fkey" FOREIGN KEY ("submissionWindowId") REFERENCES "SubmissionWindow"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE "SubmissionFileRequirement" ADD CONSTRAINT "SubmissionFileRequirement_submissionWindowId_fkey" FOREIGN KEY ("submissionWindowId") REFERENCES "SubmissionWindow"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- RoundSubmissionVisibility
|
-- RoundSubmissionVisibility
|
||||||
|
DO $$ BEGIN
|
||||||
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_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "RoundSubmissionVisibility" ADD CONSTRAINT "RoundSubmissionVisibility_submissionWindowId_fkey" FOREIGN KEY ("submissionWindowId") REFERENCES "SubmissionWindow"("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;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AssignmentIntent
|
-- AssignmentIntent
|
||||||
|
DO $$ BEGIN
|
||||||
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_juryGroupMemberId_fkey" FOREIGN KEY ("juryGroupMemberId") REFERENCES "JuryGroupMember"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
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_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "AssignmentIntent" ADD CONSTRAINT "AssignmentIntent_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("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;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- AssignmentException
|
-- AssignmentException
|
||||||
|
DO $$ BEGIN
|
||||||
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_assignmentId_fkey" FOREIGN KEY ("assignmentId") REFERENCES "Assignment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "AssignmentException" ADD CONSTRAINT "AssignmentException_approvedById_fkey" FOREIGN KEY ("approvedById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
ALTER TABLE "AssignmentException" ADD CONSTRAINT "AssignmentException_approvedById_fkey" FOREIGN KEY ("approvedById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- MentorFile
|
-- MentorFile
|
||||||
|
DO $$ BEGIN
|
||||||
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_mentorAssignmentId_fkey" FOREIGN KEY ("mentorAssignmentId") REFERENCES "MentorAssignment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
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_uploadedByUserId_fkey" FOREIGN KEY ("uploadedByUserId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
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_promotedByUserId_fkey" FOREIGN KEY ("promotedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "MentorFile" ADD CONSTRAINT "MentorFile_promotedToFileId_fkey" FOREIGN KEY ("promotedToFileId") REFERENCES "ProjectFile"("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;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- MentorFileComment
|
-- MentorFileComment
|
||||||
|
DO $$ BEGIN
|
||||||
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_mentorFileId_fkey" FOREIGN KEY ("mentorFileId") REFERENCES "MentorFile"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
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_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "MentorFileComment" ADD CONSTRAINT "MentorFileComment_parentCommentId_fkey" FOREIGN KEY ("parentCommentId") REFERENCES "MentorFileComment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE "MentorFileComment" ADD CONSTRAINT "MentorFileComment_parentCommentId_fkey" FOREIGN KEY ("parentCommentId") REFERENCES "MentorFileComment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- SubmissionPromotionEvent
|
-- SubmissionPromotionEvent
|
||||||
|
DO $$ BEGIN
|
||||||
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_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
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_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
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_sourceFileId_fkey" FOREIGN KEY ("sourceFileId") REFERENCES "MentorFile"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "SubmissionPromotionEvent" ADD CONSTRAINT "SubmissionPromotionEvent_promotedById_fkey" FOREIGN KEY ("promotedById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
ALTER TABLE "SubmissionPromotionEvent" ADD CONSTRAINT "SubmissionPromotionEvent_promotedById_fkey" FOREIGN KEY ("promotedById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- DeliberationSession
|
-- DeliberationSession
|
||||||
|
DO $$ BEGIN
|
||||||
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_competitionId_fkey" FOREIGN KEY ("competitionId") REFERENCES "Competition"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "DeliberationSession" ADD CONSTRAINT "DeliberationSession_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "Round"("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;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- DeliberationVote
|
-- DeliberationVote
|
||||||
|
DO $$ BEGIN
|
||||||
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_sessionId_fkey" FOREIGN KEY ("sessionId") REFERENCES "DeliberationSession"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
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_juryMemberId_fkey" FOREIGN KEY ("juryMemberId") REFERENCES "JuryGroupMember"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "DeliberationVote" ADD CONSTRAINT "DeliberationVote_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("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;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- DeliberationResult
|
-- DeliberationResult
|
||||||
|
DO $$ BEGIN
|
||||||
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_sessionId_fkey" FOREIGN KEY ("sessionId") REFERENCES "DeliberationSession"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "DeliberationResult" ADD CONSTRAINT "DeliberationResult_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("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;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- DeliberationParticipant
|
-- DeliberationParticipant
|
||||||
|
DO $$ BEGIN
|
||||||
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_sessionId_fkey" FOREIGN KEY ("sessionId") REFERENCES "DeliberationSession"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
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_userId_fkey" FOREIGN KEY ("userId") REFERENCES "JuryGroupMember"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "DeliberationParticipant" ADD CONSTRAINT "DeliberationParticipant_replacedById_fkey" FOREIGN KEY ("replacedById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
ALTER TABLE "DeliberationParticipant" ADD CONSTRAINT "DeliberationParticipant_replacedById_fkey" FOREIGN KEY ("replacedById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- ResultLock
|
-- ResultLock
|
||||||
|
DO $$ BEGIN
|
||||||
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_competitionId_fkey" FOREIGN KEY ("competitionId") REFERENCES "Competition"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
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_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "ResultLock" ADD CONSTRAINT "ResultLock_lockedById_fkey" FOREIGN KEY ("lockedById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
ALTER TABLE "ResultLock" ADD CONSTRAINT "ResultLock_lockedById_fkey" FOREIGN KEY ("lockedById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- ResultUnlockEvent
|
-- ResultUnlockEvent
|
||||||
|
DO $$ BEGIN
|
||||||
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_resultLockId_fkey" FOREIGN KEY ("resultLockId") REFERENCES "ResultLock"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "ResultUnlockEvent" ADD CONSTRAINT "ResultUnlockEvent_unlockedById_fkey" FOREIGN KEY ("unlockedById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
ALTER TABLE "ResultUnlockEvent" ADD CONSTRAINT "ResultUnlockEvent_unlockedById_fkey" FOREIGN KEY ("unlockedById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- FKs on modified existing tables
|
-- FKs on modified existing tables
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "Assignment" ADD CONSTRAINT "Assignment_juryGroupId_fkey" FOREIGN KEY ("juryGroupId") REFERENCES "JuryGroup"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
ALTER TABLE "Assignment" ADD CONSTRAINT "Assignment_juryGroupId_fkey" FOREIGN KEY ("juryGroupId") REFERENCES "JuryGroup"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
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_competitionId_fkey" FOREIGN KEY ("competitionId") REFERENCES "Competition"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
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_evaluationRoundId_fkey" FOREIGN KEY ("evaluationRoundId") REFERENCES "Round"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "SpecialAward" ADD CONSTRAINT "SpecialAward_juryGroupId_fkey" FOREIGN KEY ("juryGroupId") REFERENCES "JuryGroup"("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;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "MentorMessage" ADD CONSTRAINT "MentorMessage_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "MentorAssignment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
ALTER TABLE "MentorMessage" ADD CONSTRAINT "MentorMessage_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "MentorAssignment"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
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_submissionWindowId_fkey" FOREIGN KEY ("submissionWindowId") REFERENCES "SubmissionWindow"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "ProjectFile" ADD CONSTRAINT "ProjectFile_submissionFileRequirementId_fkey" FOREIGN KEY ("submissionFileRequirementId") REFERENCES "SubmissionFileRequirement"("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;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
-- AlterTable
|
-- AlterTable
|
||||||
ALTER TABLE "JuryGroupMember" ADD COLUMN "selfServiceCap" INTEGER,
|
DO $$ BEGIN
|
||||||
ADD COLUMN "selfServiceRatio" DOUBLE PRECISION;
|
ALTER TABLE "JuryGroupMember" ADD COLUMN "selfServiceCap" INTEGER;
|
||||||
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "JuryGroupMember" ADD COLUMN "selfServiceRatio" DOUBLE PRECISION;
|
||||||
|
EXCEPTION WHEN duplicate_column THEN NULL; END $$;
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
-- =============================================================================
|
-- =============================================================================
|
||||||
-- Phase 7/8 Migration Part 1: Rename stageId → roundId on 15 tables
|
-- Phase 7/8 Migration Part 1: Rename stageId -> roundId on 15 tables
|
||||||
-- =============================================================================
|
-- =============================================================================
|
||||||
-- This migration renames stageId columns to roundId and updates FK constraints
|
-- This migration renames stageId columns to roundId and updates FK constraints
|
||||||
-- to point to the Round table instead of Stage table.
|
-- to point to the Round table instead of Stage table.
|
||||||
--
|
--
|
||||||
-- NOTE: After the pipeline migration (20260213), most tables have BOTH a
|
-- NOTE: After the pipeline migration (20260213), most tables have BOTH a
|
||||||
-- nullable roundId column (legacy, no FK) AND a stageId column. We must
|
-- nullable roundId column (legacy, no FK) AND a stageId column. We must
|
||||||
-- drop the old roundId column before renaming stageId → roundId.
|
-- drop the old roundId column before renaming stageId -> roundId.
|
||||||
|
|
||||||
-- ─── 1. EvaluationForm ───────────────────────────────────────────────────────
|
-- --- 1. EvaluationForm ---
|
||||||
|
|
||||||
-- Drop old roundId column (nullable, no FK since 20260213 migration)
|
-- Drop old roundId column (nullable, no FK since 20260213 migration)
|
||||||
ALTER TABLE "EvaluationForm" DROP COLUMN IF EXISTS "roundId";
|
ALTER TABLE "EvaluationForm" DROP COLUMN IF EXISTS "roundId";
|
||||||
|
|
@ -20,18 +20,22 @@ ALTER TABLE "EvaluationForm" DROP CONSTRAINT IF EXISTS "EvaluationForm_stageId_f
|
||||||
DROP INDEX IF EXISTS "EvaluationForm_stageId_version_key";
|
DROP INDEX IF EXISTS "EvaluationForm_stageId_version_key";
|
||||||
DROP INDEX IF EXISTS "EvaluationForm_stageId_isActive_idx";
|
DROP INDEX IF EXISTS "EvaluationForm_stageId_isActive_idx";
|
||||||
|
|
||||||
-- Rename column
|
-- Rename column (only if stageId exists)
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "EvaluationForm" RENAME COLUMN "stageId" TO "roundId";
|
ALTER TABLE "EvaluationForm" RENAME COLUMN "stageId" TO "roundId";
|
||||||
|
EXCEPTION WHEN undefined_column THEN NULL; END $$;
|
||||||
|
|
||||||
-- Recreate indexes with new name
|
-- Recreate indexes with new name
|
||||||
CREATE UNIQUE INDEX "EvaluationForm_roundId_version_key" ON "EvaluationForm"("roundId", "version");
|
CREATE UNIQUE INDEX IF NOT EXISTS "EvaluationForm_roundId_version_key" ON "EvaluationForm"("roundId", "version");
|
||||||
CREATE INDEX "EvaluationForm_roundId_isActive_idx" ON "EvaluationForm"("roundId", "isActive");
|
CREATE INDEX IF NOT EXISTS "EvaluationForm_roundId_isActive_idx" ON "EvaluationForm"("roundId", "isActive");
|
||||||
|
|
||||||
-- Recreate FK pointing to Round
|
-- Recreate FK pointing to Round
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "EvaluationForm" ADD CONSTRAINT "EvaluationForm_roundId_fkey"
|
ALTER TABLE "EvaluationForm" ADD CONSTRAINT "EvaluationForm_roundId_fkey"
|
||||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- ─── 2. FileRequirement ──────────────────────────────────────────────────────
|
-- --- 2. FileRequirement ---
|
||||||
|
|
||||||
ALTER TABLE "FileRequirement" DROP COLUMN IF EXISTS "roundId";
|
ALTER TABLE "FileRequirement" DROP COLUMN IF EXISTS "roundId";
|
||||||
|
|
||||||
|
|
@ -39,14 +43,18 @@ ALTER TABLE "FileRequirement" DROP CONSTRAINT IF EXISTS "FileRequirement_stageId
|
||||||
|
|
||||||
DROP INDEX IF EXISTS "FileRequirement_stageId_idx";
|
DROP INDEX IF EXISTS "FileRequirement_stageId_idx";
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "FileRequirement" RENAME COLUMN "stageId" TO "roundId";
|
ALTER TABLE "FileRequirement" RENAME COLUMN "stageId" TO "roundId";
|
||||||
|
EXCEPTION WHEN undefined_column THEN NULL; END $$;
|
||||||
|
|
||||||
CREATE INDEX "FileRequirement_roundId_idx" ON "FileRequirement"("roundId");
|
CREATE INDEX IF NOT EXISTS "FileRequirement_roundId_idx" ON "FileRequirement"("roundId");
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "FileRequirement" ADD CONSTRAINT "FileRequirement_roundId_fkey"
|
ALTER TABLE "FileRequirement" ADD CONSTRAINT "FileRequirement_roundId_fkey"
|
||||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- ─── 3. Assignment ───────────────────────────────────────────────────────────
|
-- --- 3. Assignment ---
|
||||||
|
|
||||||
ALTER TABLE "Assignment" DROP COLUMN IF EXISTS "roundId";
|
ALTER TABLE "Assignment" DROP COLUMN IF EXISTS "roundId";
|
||||||
|
|
||||||
|
|
@ -55,15 +63,19 @@ ALTER TABLE "Assignment" DROP CONSTRAINT IF EXISTS "Assignment_stageId_fkey";
|
||||||
DROP INDEX IF EXISTS "Assignment_userId_projectId_stageId_key";
|
DROP INDEX IF EXISTS "Assignment_userId_projectId_stageId_key";
|
||||||
DROP INDEX IF EXISTS "Assignment_stageId_idx";
|
DROP INDEX IF EXISTS "Assignment_stageId_idx";
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "Assignment" RENAME COLUMN "stageId" TO "roundId";
|
ALTER TABLE "Assignment" RENAME COLUMN "stageId" TO "roundId";
|
||||||
|
EXCEPTION WHEN undefined_column THEN NULL; END $$;
|
||||||
|
|
||||||
CREATE UNIQUE INDEX "Assignment_userId_projectId_roundId_key" ON "Assignment"("userId", "projectId", "roundId");
|
CREATE UNIQUE INDEX IF NOT EXISTS "Assignment_userId_projectId_roundId_key" ON "Assignment"("userId", "projectId", "roundId");
|
||||||
CREATE INDEX "Assignment_roundId_idx" ON "Assignment"("roundId");
|
CREATE INDEX IF NOT EXISTS "Assignment_roundId_idx" ON "Assignment"("roundId");
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "Assignment" ADD CONSTRAINT "Assignment_roundId_fkey"
|
ALTER TABLE "Assignment" ADD CONSTRAINT "Assignment_roundId_fkey"
|
||||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- ─── 4. GracePeriod ──────────────────────────────────────────────────────────
|
-- --- 4. GracePeriod ---
|
||||||
|
|
||||||
ALTER TABLE "GracePeriod" DROP COLUMN IF EXISTS "roundId";
|
ALTER TABLE "GracePeriod" DROP COLUMN IF EXISTS "roundId";
|
||||||
|
|
||||||
|
|
@ -72,15 +84,19 @@ ALTER TABLE "GracePeriod" DROP CONSTRAINT IF EXISTS "GracePeriod_stageId_fkey";
|
||||||
DROP INDEX IF EXISTS "GracePeriod_stageId_idx";
|
DROP INDEX IF EXISTS "GracePeriod_stageId_idx";
|
||||||
DROP INDEX IF EXISTS "GracePeriod_stageId_userId_extendedUntil_idx";
|
DROP INDEX IF EXISTS "GracePeriod_stageId_userId_extendedUntil_idx";
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "GracePeriod" RENAME COLUMN "stageId" TO "roundId";
|
ALTER TABLE "GracePeriod" RENAME COLUMN "stageId" TO "roundId";
|
||||||
|
EXCEPTION WHEN undefined_column THEN NULL; END $$;
|
||||||
|
|
||||||
CREATE INDEX "GracePeriod_roundId_idx" ON "GracePeriod"("roundId");
|
CREATE INDEX IF NOT EXISTS "GracePeriod_roundId_idx" ON "GracePeriod"("roundId");
|
||||||
CREATE INDEX "GracePeriod_roundId_userId_extendedUntil_idx" ON "GracePeriod"("roundId", "userId", "extendedUntil");
|
CREATE INDEX IF NOT EXISTS "GracePeriod_roundId_userId_extendedUntil_idx" ON "GracePeriod"("roundId", "userId", "extendedUntil");
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "GracePeriod" ADD CONSTRAINT "GracePeriod_roundId_fkey"
|
ALTER TABLE "GracePeriod" ADD CONSTRAINT "GracePeriod_roundId_fkey"
|
||||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- ─── 5. LiveVotingSession ────────────────────────────────────────────────────
|
-- --- 5. LiveVotingSession ---
|
||||||
|
|
||||||
ALTER TABLE "LiveVotingSession" DROP COLUMN IF EXISTS "roundId";
|
ALTER TABLE "LiveVotingSession" DROP COLUMN IF EXISTS "roundId";
|
||||||
|
|
||||||
|
|
@ -88,14 +104,18 @@ ALTER TABLE "LiveVotingSession" DROP CONSTRAINT IF EXISTS "LiveVotingSession_sta
|
||||||
|
|
||||||
DROP INDEX IF EXISTS "LiveVotingSession_stageId_key";
|
DROP INDEX IF EXISTS "LiveVotingSession_stageId_key";
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "LiveVotingSession" RENAME COLUMN "stageId" TO "roundId";
|
ALTER TABLE "LiveVotingSession" RENAME COLUMN "stageId" TO "roundId";
|
||||||
|
EXCEPTION WHEN undefined_column THEN NULL; END $$;
|
||||||
|
|
||||||
CREATE UNIQUE INDEX "LiveVotingSession_roundId_key" ON "LiveVotingSession"("roundId");
|
CREATE UNIQUE INDEX IF NOT EXISTS "LiveVotingSession_roundId_key" ON "LiveVotingSession"("roundId");
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "LiveVotingSession" ADD CONSTRAINT "LiveVotingSession_roundId_fkey"
|
ALTER TABLE "LiveVotingSession" ADD CONSTRAINT "LiveVotingSession_roundId_fkey"
|
||||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- ─── 6. FilteringRule ────────────────────────────────────────────────────────
|
-- --- 6. FilteringRule ---
|
||||||
|
|
||||||
ALTER TABLE "FilteringRule" DROP COLUMN IF EXISTS "roundId";
|
ALTER TABLE "FilteringRule" DROP COLUMN IF EXISTS "roundId";
|
||||||
|
|
||||||
|
|
@ -103,14 +123,18 @@ ALTER TABLE "FilteringRule" DROP CONSTRAINT IF EXISTS "FilteringRule_stageId_fke
|
||||||
|
|
||||||
DROP INDEX IF EXISTS "FilteringRule_stageId_idx";
|
DROP INDEX IF EXISTS "FilteringRule_stageId_idx";
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "FilteringRule" RENAME COLUMN "stageId" TO "roundId";
|
ALTER TABLE "FilteringRule" RENAME COLUMN "stageId" TO "roundId";
|
||||||
|
EXCEPTION WHEN undefined_column THEN NULL; END $$;
|
||||||
|
|
||||||
CREATE INDEX "FilteringRule_roundId_idx" ON "FilteringRule"("roundId");
|
CREATE INDEX IF NOT EXISTS "FilteringRule_roundId_idx" ON "FilteringRule"("roundId");
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "FilteringRule" ADD CONSTRAINT "FilteringRule_roundId_fkey"
|
ALTER TABLE "FilteringRule" ADD CONSTRAINT "FilteringRule_roundId_fkey"
|
||||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- ─── 7. FilteringResult ──────────────────────────────────────────────────────
|
-- --- 7. FilteringResult ---
|
||||||
|
|
||||||
ALTER TABLE "FilteringResult" DROP COLUMN IF EXISTS "roundId";
|
ALTER TABLE "FilteringResult" DROP COLUMN IF EXISTS "roundId";
|
||||||
|
|
||||||
|
|
@ -119,15 +143,19 @@ ALTER TABLE "FilteringResult" DROP CONSTRAINT IF EXISTS "FilteringResult_stageId
|
||||||
DROP INDEX IF EXISTS "FilteringResult_stageId_projectId_key";
|
DROP INDEX IF EXISTS "FilteringResult_stageId_projectId_key";
|
||||||
DROP INDEX IF EXISTS "FilteringResult_stageId_idx";
|
DROP INDEX IF EXISTS "FilteringResult_stageId_idx";
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "FilteringResult" RENAME COLUMN "stageId" TO "roundId";
|
ALTER TABLE "FilteringResult" RENAME COLUMN "stageId" TO "roundId";
|
||||||
|
EXCEPTION WHEN undefined_column THEN NULL; END $$;
|
||||||
|
|
||||||
CREATE UNIQUE INDEX "FilteringResult_roundId_projectId_key" ON "FilteringResult"("roundId", "projectId");
|
CREATE UNIQUE INDEX IF NOT EXISTS "FilteringResult_roundId_projectId_key" ON "FilteringResult"("roundId", "projectId");
|
||||||
CREATE INDEX "FilteringResult_roundId_idx" ON "FilteringResult"("roundId");
|
CREATE INDEX IF NOT EXISTS "FilteringResult_roundId_idx" ON "FilteringResult"("roundId");
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "FilteringResult" ADD CONSTRAINT "FilteringResult_roundId_fkey"
|
ALTER TABLE "FilteringResult" ADD CONSTRAINT "FilteringResult_roundId_fkey"
|
||||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- ─── 8. FilteringJob ─────────────────────────────────────────────────────────
|
-- --- 8. FilteringJob ---
|
||||||
|
|
||||||
ALTER TABLE "FilteringJob" DROP COLUMN IF EXISTS "roundId";
|
ALTER TABLE "FilteringJob" DROP COLUMN IF EXISTS "roundId";
|
||||||
|
|
||||||
|
|
@ -135,14 +163,18 @@ ALTER TABLE "FilteringJob" DROP CONSTRAINT IF EXISTS "FilteringJob_stageId_fkey"
|
||||||
|
|
||||||
DROP INDEX IF EXISTS "FilteringJob_stageId_idx";
|
DROP INDEX IF EXISTS "FilteringJob_stageId_idx";
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "FilteringJob" RENAME COLUMN "stageId" TO "roundId";
|
ALTER TABLE "FilteringJob" RENAME COLUMN "stageId" TO "roundId";
|
||||||
|
EXCEPTION WHEN undefined_column THEN NULL; END $$;
|
||||||
|
|
||||||
CREATE INDEX "FilteringJob_roundId_idx" ON "FilteringJob"("roundId");
|
CREATE INDEX IF NOT EXISTS "FilteringJob_roundId_idx" ON "FilteringJob"("roundId");
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "FilteringJob" ADD CONSTRAINT "FilteringJob_roundId_fkey"
|
ALTER TABLE "FilteringJob" ADD CONSTRAINT "FilteringJob_roundId_fkey"
|
||||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- ─── 9. AssignmentJob ────────────────────────────────────────────────────────
|
-- --- 9. AssignmentJob ---
|
||||||
|
|
||||||
ALTER TABLE "AssignmentJob" DROP COLUMN IF EXISTS "roundId";
|
ALTER TABLE "AssignmentJob" DROP COLUMN IF EXISTS "roundId";
|
||||||
|
|
||||||
|
|
@ -150,14 +182,18 @@ ALTER TABLE "AssignmentJob" DROP CONSTRAINT IF EXISTS "AssignmentJob_stageId_fke
|
||||||
|
|
||||||
DROP INDEX IF EXISTS "AssignmentJob_stageId_idx";
|
DROP INDEX IF EXISTS "AssignmentJob_stageId_idx";
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "AssignmentJob" RENAME COLUMN "stageId" TO "roundId";
|
ALTER TABLE "AssignmentJob" RENAME COLUMN "stageId" TO "roundId";
|
||||||
|
EXCEPTION WHEN undefined_column THEN NULL; END $$;
|
||||||
|
|
||||||
CREATE INDEX "AssignmentJob_roundId_idx" ON "AssignmentJob"("roundId");
|
CREATE INDEX IF NOT EXISTS "AssignmentJob_roundId_idx" ON "AssignmentJob"("roundId");
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "AssignmentJob" ADD CONSTRAINT "AssignmentJob_roundId_fkey"
|
ALTER TABLE "AssignmentJob" ADD CONSTRAINT "AssignmentJob_roundId_fkey"
|
||||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- ─── 10. ReminderLog ─────────────────────────────────────────────────────────
|
-- --- 10. ReminderLog ---
|
||||||
|
|
||||||
ALTER TABLE "ReminderLog" DROP COLUMN IF EXISTS "roundId";
|
ALTER TABLE "ReminderLog" DROP COLUMN IF EXISTS "roundId";
|
||||||
|
|
||||||
|
|
@ -166,15 +202,19 @@ ALTER TABLE "ReminderLog" DROP CONSTRAINT IF EXISTS "ReminderLog_stageId_fkey";
|
||||||
DROP INDEX IF EXISTS "ReminderLog_stageId_userId_type_key";
|
DROP INDEX IF EXISTS "ReminderLog_stageId_userId_type_key";
|
||||||
DROP INDEX IF EXISTS "ReminderLog_stageId_idx";
|
DROP INDEX IF EXISTS "ReminderLog_stageId_idx";
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "ReminderLog" RENAME COLUMN "stageId" TO "roundId";
|
ALTER TABLE "ReminderLog" RENAME COLUMN "stageId" TO "roundId";
|
||||||
|
EXCEPTION WHEN undefined_column THEN NULL; END $$;
|
||||||
|
|
||||||
CREATE UNIQUE INDEX "ReminderLog_roundId_userId_type_key" ON "ReminderLog"("roundId", "userId", "type");
|
CREATE UNIQUE INDEX IF NOT EXISTS "ReminderLog_roundId_userId_type_key" ON "ReminderLog"("roundId", "userId", "type");
|
||||||
CREATE INDEX "ReminderLog_roundId_idx" ON "ReminderLog"("roundId");
|
CREATE INDEX IF NOT EXISTS "ReminderLog_roundId_idx" ON "ReminderLog"("roundId");
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "ReminderLog" ADD CONSTRAINT "ReminderLog_roundId_fkey"
|
ALTER TABLE "ReminderLog" ADD CONSTRAINT "ReminderLog_roundId_fkey"
|
||||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- ─── 11. EvaluationSummary ───────────────────────────────────────────────────
|
-- --- 11. EvaluationSummary ---
|
||||||
|
|
||||||
ALTER TABLE "EvaluationSummary" DROP COLUMN IF EXISTS "roundId";
|
ALTER TABLE "EvaluationSummary" DROP COLUMN IF EXISTS "roundId";
|
||||||
|
|
||||||
|
|
@ -183,15 +223,19 @@ ALTER TABLE "EvaluationSummary" DROP CONSTRAINT IF EXISTS "EvaluationSummary_sta
|
||||||
DROP INDEX IF EXISTS "EvaluationSummary_projectId_stageId_key";
|
DROP INDEX IF EXISTS "EvaluationSummary_projectId_stageId_key";
|
||||||
DROP INDEX IF EXISTS "EvaluationSummary_stageId_idx";
|
DROP INDEX IF EXISTS "EvaluationSummary_stageId_idx";
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "EvaluationSummary" RENAME COLUMN "stageId" TO "roundId";
|
ALTER TABLE "EvaluationSummary" RENAME COLUMN "stageId" TO "roundId";
|
||||||
|
EXCEPTION WHEN undefined_column THEN NULL; END $$;
|
||||||
|
|
||||||
CREATE UNIQUE INDEX "EvaluationSummary_projectId_roundId_key" ON "EvaluationSummary"("projectId", "roundId");
|
CREATE UNIQUE INDEX IF NOT EXISTS "EvaluationSummary_projectId_roundId_key" ON "EvaluationSummary"("projectId", "roundId");
|
||||||
CREATE INDEX "EvaluationSummary_roundId_idx" ON "EvaluationSummary"("roundId");
|
CREATE INDEX IF NOT EXISTS "EvaluationSummary_roundId_idx" ON "EvaluationSummary"("roundId");
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "EvaluationSummary" ADD CONSTRAINT "EvaluationSummary_roundId_fkey"
|
ALTER TABLE "EvaluationSummary" ADD CONSTRAINT "EvaluationSummary_roundId_fkey"
|
||||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- ─── 12. EvaluationDiscussion ────────────────────────────────────────────────
|
-- --- 12. EvaluationDiscussion ---
|
||||||
|
|
||||||
ALTER TABLE "EvaluationDiscussion" DROP COLUMN IF EXISTS "roundId";
|
ALTER TABLE "EvaluationDiscussion" DROP COLUMN IF EXISTS "roundId";
|
||||||
|
|
||||||
|
|
@ -200,15 +244,19 @@ ALTER TABLE "EvaluationDiscussion" DROP CONSTRAINT IF EXISTS "EvaluationDiscussi
|
||||||
DROP INDEX IF EXISTS "EvaluationDiscussion_projectId_stageId_key";
|
DROP INDEX IF EXISTS "EvaluationDiscussion_projectId_stageId_key";
|
||||||
DROP INDEX IF EXISTS "EvaluationDiscussion_stageId_idx";
|
DROP INDEX IF EXISTS "EvaluationDiscussion_stageId_idx";
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "EvaluationDiscussion" RENAME COLUMN "stageId" TO "roundId";
|
ALTER TABLE "EvaluationDiscussion" RENAME COLUMN "stageId" TO "roundId";
|
||||||
|
EXCEPTION WHEN undefined_column THEN NULL; END $$;
|
||||||
|
|
||||||
CREATE UNIQUE INDEX "EvaluationDiscussion_projectId_roundId_key" ON "EvaluationDiscussion"("projectId", "roundId");
|
CREATE UNIQUE INDEX IF NOT EXISTS "EvaluationDiscussion_projectId_roundId_key" ON "EvaluationDiscussion"("projectId", "roundId");
|
||||||
CREATE INDEX "EvaluationDiscussion_roundId_idx" ON "EvaluationDiscussion"("roundId");
|
CREATE INDEX IF NOT EXISTS "EvaluationDiscussion_roundId_idx" ON "EvaluationDiscussion"("roundId");
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "EvaluationDiscussion" ADD CONSTRAINT "EvaluationDiscussion_roundId_fkey"
|
ALTER TABLE "EvaluationDiscussion" ADD CONSTRAINT "EvaluationDiscussion_roundId_fkey"
|
||||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- ─── 13. Message ─────────────────────────────────────────────────────────────
|
-- --- 13. Message ---
|
||||||
|
|
||||||
-- Message has roundId (from init, nullable) and stageId (from pipeline, nullable)
|
-- Message has roundId (from init, nullable) and stageId (from pipeline, nullable)
|
||||||
ALTER TABLE "Message" DROP COLUMN IF EXISTS "roundId";
|
ALTER TABLE "Message" DROP COLUMN IF EXISTS "roundId";
|
||||||
|
|
@ -217,42 +265,54 @@ ALTER TABLE "Message" DROP CONSTRAINT IF EXISTS "Message_stageId_fkey";
|
||||||
|
|
||||||
DROP INDEX IF EXISTS "Message_stageId_idx";
|
DROP INDEX IF EXISTS "Message_stageId_idx";
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "Message" RENAME COLUMN "stageId" TO "roundId";
|
ALTER TABLE "Message" RENAME COLUMN "stageId" TO "roundId";
|
||||||
|
EXCEPTION WHEN undefined_column THEN NULL; END $$;
|
||||||
|
|
||||||
CREATE INDEX "Message_roundId_idx" ON "Message"("roundId");
|
CREATE INDEX IF NOT EXISTS "Message_roundId_idx" ON "Message"("roundId");
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "Message" ADD CONSTRAINT "Message_roundId_fkey"
|
ALTER TABLE "Message" ADD CONSTRAINT "Message_roundId_fkey"
|
||||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- ─── 14. Cohort ──────────────────────────────────────────────────────────────
|
-- --- 14. Cohort ---
|
||||||
-- Cohort was created in pipeline migration with stageId only (no roundId)
|
-- Cohort was created in pipeline migration with stageId only (no roundId)
|
||||||
|
|
||||||
ALTER TABLE "Cohort" DROP CONSTRAINT IF EXISTS "Cohort_stageId_fkey";
|
ALTER TABLE "Cohort" DROP CONSTRAINT IF EXISTS "Cohort_stageId_fkey";
|
||||||
|
|
||||||
DROP INDEX IF EXISTS "Cohort_stageId_idx";
|
DROP INDEX IF EXISTS "Cohort_stageId_idx";
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "Cohort" RENAME COLUMN "stageId" TO "roundId";
|
ALTER TABLE "Cohort" RENAME COLUMN "stageId" TO "roundId";
|
||||||
|
EXCEPTION WHEN undefined_column THEN NULL; END $$;
|
||||||
|
|
||||||
CREATE INDEX "Cohort_roundId_idx" ON "Cohort"("roundId");
|
CREATE INDEX IF NOT EXISTS "Cohort_roundId_idx" ON "Cohort"("roundId");
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "Cohort" ADD CONSTRAINT "Cohort_roundId_fkey"
|
ALTER TABLE "Cohort" ADD CONSTRAINT "Cohort_roundId_fkey"
|
||||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- ─── 15. LiveProgressCursor ──────────────────────────────────────────────────
|
-- --- 15. LiveProgressCursor ---
|
||||||
-- LiveProgressCursor was created in pipeline migration with stageId only (no roundId)
|
-- LiveProgressCursor was created in pipeline migration with stageId only (no roundId)
|
||||||
|
|
||||||
ALTER TABLE "LiveProgressCursor" DROP CONSTRAINT IF EXISTS "LiveProgressCursor_stageId_fkey";
|
ALTER TABLE "LiveProgressCursor" DROP CONSTRAINT IF EXISTS "LiveProgressCursor_stageId_fkey";
|
||||||
|
|
||||||
DROP INDEX IF EXISTS "LiveProgressCursor_stageId_key";
|
DROP INDEX IF EXISTS "LiveProgressCursor_stageId_key";
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "LiveProgressCursor" RENAME COLUMN "stageId" TO "roundId";
|
ALTER TABLE "LiveProgressCursor" RENAME COLUMN "stageId" TO "roundId";
|
||||||
|
EXCEPTION WHEN undefined_column THEN NULL; END $$;
|
||||||
|
|
||||||
CREATE UNIQUE INDEX "LiveProgressCursor_roundId_key" ON "LiveProgressCursor"("roundId");
|
CREATE UNIQUE INDEX IF NOT EXISTS "LiveProgressCursor_roundId_key" ON "LiveProgressCursor"("roundId");
|
||||||
|
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "LiveProgressCursor" ADD CONSTRAINT "LiveProgressCursor_roundId_fkey"
|
ALTER TABLE "LiveProgressCursor" ADD CONSTRAINT "LiveProgressCursor_roundId_fkey"
|
||||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- ─── 16. SpecialAward: Drop trackId column ───────────────────────────────────
|
-- --- 16. SpecialAward: Drop trackId column ---
|
||||||
|
|
||||||
ALTER TABLE "SpecialAward" DROP CONSTRAINT IF EXISTS "SpecialAward_trackId_fkey";
|
ALTER TABLE "SpecialAward" DROP CONSTRAINT IF EXISTS "SpecialAward_trackId_fkey";
|
||||||
|
|
||||||
|
|
@ -260,12 +320,16 @@ DROP INDEX IF EXISTS "SpecialAward_trackId_key";
|
||||||
|
|
||||||
ALTER TABLE "SpecialAward" DROP COLUMN IF EXISTS "trackId";
|
ALTER TABLE "SpecialAward" DROP COLUMN IF EXISTS "trackId";
|
||||||
|
|
||||||
-- ─── 17. ConflictOfInterest: roundId was made nullable in pipeline migration ─
|
-- --- 17. ConflictOfInterest: roundId was made nullable in pipeline migration ---
|
||||||
-- It still exists, just restore FK to new Round table
|
-- It still exists, just restore FK to new Round table
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "ConflictOfInterest" ADD CONSTRAINT "ConflictOfInterest_roundId_fkey"
|
ALTER TABLE "ConflictOfInterest" ADD CONSTRAINT "ConflictOfInterest_roundId_fkey"
|
||||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
||||||
-- ─── 18. TaggingJob: roundId was made nullable in pipeline migration ─────────
|
-- --- 18. TaggingJob: roundId was made nullable in pipeline migration ---
|
||||||
-- Restore FK to new Round table
|
-- Restore FK to new Round table
|
||||||
|
DO $$ BEGIN
|
||||||
ALTER TABLE "TaggingJob" ADD CONSTRAINT "TaggingJob_roundId_fkey"
|
ALTER TABLE "TaggingJob" ADD CONSTRAINT "TaggingJob_roundId_fkey"
|
||||||
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- Add pageCount column to ProjectFile (was in schema but missing migration)
|
||||||
|
ALTER TABLE "ProjectFile" ADD COLUMN IF NOT EXISTS "pageCount" INTEGER;
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
-- =============================================================================
|
||||||
|
-- Schema Reconciliation: Fill remaining gaps between migrations and schema.prisma
|
||||||
|
-- =============================================================================
|
||||||
|
-- All statements are idempotent (safe to re-run on any database state).
|
||||||
|
|
||||||
|
-- 1. ConflictOfInterest: add standalone hasConflict index (schema has @@index([hasConflict]))
|
||||||
|
-- Migration 20260205223133 only created composite (roundId, hasConflict) index.
|
||||||
|
CREATE INDEX IF NOT EXISTS "ConflictOfInterest_hasConflict_idx" ON "ConflictOfInterest"("hasConflict");
|
||||||
|
|
||||||
|
-- 2. Ensure ConflictOfInterest.roundId is nullable (schema says String?)
|
||||||
|
-- Pipeline migration (20260213) makes it nullable, but guard for safety.
|
||||||
|
DO $$ BEGIN
|
||||||
|
ALTER TABLE "ConflictOfInterest" ALTER COLUMN "roundId" DROP NOT NULL;
|
||||||
|
EXCEPTION WHEN others THEN NULL;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- 3. Drop stale composite index that no longer matches schema
|
||||||
|
-- Schema only has @@index([hasConflict]) and @@index([userId]), not (roundId, hasConflict).
|
||||||
|
DROP INDEX IF EXISTS "ConflictOfInterest_roundId_hasConflict_idx";
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useState, useCallback, useRef } from 'react'
|
import { useState, useCallback, useRef, useEffect, useMemo } from 'react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { trpc } from '@/lib/trpc/client'
|
import { trpc } from '@/lib/trpc/client'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
|
|
@ -46,6 +46,8 @@ import {
|
||||||
Loader2,
|
Loader2,
|
||||||
FileUp,
|
FileUp,
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
|
ExternalLink,
|
||||||
|
Trash2,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { cn, formatFileSize } from '@/lib/utils'
|
import { cn, formatFileSize } from '@/lib/utils'
|
||||||
import { Pagination } from '@/components/shared/pagination'
|
import { Pagination } from '@/components/shared/pagination'
|
||||||
|
|
@ -77,12 +79,13 @@ export default function BulkUploadPage() {
|
||||||
label: string
|
label: string
|
||||||
mimeTypes: string[]
|
mimeTypes: string[]
|
||||||
required: boolean
|
required: boolean
|
||||||
file: { id: string; fileName: string } | null
|
file: { id: string; fileName: string; bucket: string; objectKey: string } | null
|
||||||
}>
|
}>
|
||||||
} | null>(null)
|
} | null>(null)
|
||||||
const [bulkFiles, setBulkFiles] = useState<Record<string, File | null>>({})
|
const [bulkFiles, setBulkFiles] = useState<Record<string, File | null>>({})
|
||||||
|
|
||||||
const fileInputRefs = useRef<Record<string, HTMLInputElement | null>>({})
|
const fileInputRefs = useRef<Record<string, HTMLInputElement | null>>({})
|
||||||
|
const utils = trpc.useUtils()
|
||||||
|
|
||||||
// Debounce search
|
// Debounce search
|
||||||
const searchTimer = useRef<ReturnType<typeof setTimeout>>(undefined)
|
const searchTimer = useRef<ReturnType<typeof setTimeout>>(undefined)
|
||||||
|
|
@ -109,6 +112,70 @@ export default function BulkUploadPage() {
|
||||||
{ enabled: !!roundId }
|
{ enabled: !!roundId }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Collect all files from current data for existence verification
|
||||||
|
const filesToVerify = useMemo(() => {
|
||||||
|
if (!data?.projects) return []
|
||||||
|
const files: { bucket: string; objectKey: string }[] = []
|
||||||
|
for (const row of data.projects) {
|
||||||
|
for (const req of row.requirements) {
|
||||||
|
if (req.file?.bucket && req.file?.objectKey) {
|
||||||
|
files.push({ bucket: req.file.bucket, objectKey: req.file.objectKey })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return files
|
||||||
|
}, [data])
|
||||||
|
|
||||||
|
// Verify files actually exist in storage
|
||||||
|
const { data: fileExistence } = trpc.file.verifyFilesExist.useQuery(
|
||||||
|
{ files: filesToVerify },
|
||||||
|
{ enabled: filesToVerify.length > 0, staleTime: 30_000 }
|
||||||
|
)
|
||||||
|
|
||||||
|
// Track which files are missing from storage (objectKey → true means missing)
|
||||||
|
const missingFiles = useMemo(() => {
|
||||||
|
if (!fileExistence) return new Set<string>()
|
||||||
|
const missing = new Set<string>()
|
||||||
|
for (const [key, exists] of Object.entries(fileExistence)) {
|
||||||
|
if (!exists) missing.add(key)
|
||||||
|
}
|
||||||
|
return missing
|
||||||
|
}, [fileExistence])
|
||||||
|
|
||||||
|
// Open file in new tab via presigned URL
|
||||||
|
const handleViewFile = useCallback(
|
||||||
|
async (bucket: string, objectKey: string) => {
|
||||||
|
try {
|
||||||
|
const { url } = await utils.file.getDownloadUrl.fetch({ bucket, objectKey })
|
||||||
|
window.open(url, '_blank')
|
||||||
|
} catch {
|
||||||
|
toast.error('Failed to open file. It may have been deleted from storage.')
|
||||||
|
refetch()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[utils, refetch]
|
||||||
|
)
|
||||||
|
|
||||||
|
// Delete a file
|
||||||
|
const deleteMutation = trpc.file.delete.useMutation({
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success('File removed')
|
||||||
|
refetch()
|
||||||
|
},
|
||||||
|
onError: (err) => {
|
||||||
|
toast.error(`Failed to remove file: ${err.message}`)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleDeleteFile = useCallback(
|
||||||
|
(fileId: string) => {
|
||||||
|
if (confirm('Remove this uploaded file?')) {
|
||||||
|
deleteMutation.mutate({ id: fileId })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[deleteMutation]
|
||||||
|
)
|
||||||
|
|
||||||
const uploadMutation = trpc.file.adminUploadForRoundRequirement.useMutation()
|
const uploadMutation = trpc.file.adminUploadForRoundRequirement.useMutation()
|
||||||
|
|
||||||
// Upload a single file for a project requirement
|
// Upload a single file for a project requirement
|
||||||
|
|
@ -390,7 +457,7 @@ export default function BulkUploadPage() {
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{data.projects.map((row) => {
|
{data.projects.map((row) => {
|
||||||
const missingRequired = row.requirements.filter(
|
const missingRequired = row.requirements.filter(
|
||||||
(r) => r.required && !r.file
|
(r) => r.required && (!r.file || (r.file?.objectKey && missingFiles.has(r.file.objectKey)))
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
<TableRow
|
<TableRow
|
||||||
|
|
@ -446,12 +513,57 @@ export default function BulkUploadPage() {
|
||||||
Retry
|
Retry
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
) : req.file && req.file.objectKey && missingFiles.has(req.file.objectKey) ? (
|
||||||
|
<div className="flex flex-col items-center gap-1">
|
||||||
|
<AlertCircle className="h-4 w-4 text-amber-500" />
|
||||||
|
<span className="text-[10px] text-amber-600 font-medium">Missing</span>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="h-6 px-2 text-[10px]"
|
||||||
|
onClick={() =>
|
||||||
|
handleCellUpload(
|
||||||
|
row.project.id,
|
||||||
|
req.requirementId,
|
||||||
|
req.mimeTypes
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Re-upload
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
) : req.file || uploadState?.status === 'complete' ? (
|
) : req.file || uploadState?.status === 'complete' ? (
|
||||||
<div className="flex flex-col items-center gap-1">
|
<div className="flex flex-col items-center gap-1">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
<CheckCircle2 className="h-4 w-4 text-green-600" />
|
<CheckCircle2 className="h-4 w-4 text-green-600" />
|
||||||
|
{req.file && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="text-muted-foreground hover:text-destructive transition-colors cursor-pointer"
|
||||||
|
title="Remove file"
|
||||||
|
onClick={() => handleDeleteFile(req.file!.id)}
|
||||||
|
disabled={deleteMutation.isPending}
|
||||||
|
>
|
||||||
|
<Trash2 className="h-3 w-3" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{req.file?.bucket && req.file?.objectKey ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="text-[10px] text-teal-600 hover:text-teal-800 hover:underline truncate max-w-[120px] flex items-center gap-0.5 cursor-pointer"
|
||||||
|
onClick={() =>
|
||||||
|
handleViewFile(req.file!.bucket, req.file!.objectKey)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{req.file.fileName}
|
||||||
|
<ExternalLink className="h-2.5 w-2.5 shrink-0" />
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
<span className="text-[10px] text-muted-foreground truncate max-w-[120px]">
|
<span className="text-[10px] text-muted-foreground truncate max-w-[120px]">
|
||||||
{req.file?.fileName ?? 'Uploaded'}
|
{req.file?.fileName ?? 'Uploaded'}
|
||||||
</span>
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
|
|
|
||||||
|
|
@ -366,8 +366,9 @@ export default function ProjectsPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCloseTaggingDialog = () => {
|
const handleCloseTaggingDialog = () => {
|
||||||
if (!taggingInProgress) {
|
|
||||||
setAiTagDialogOpen(false)
|
setAiTagDialogOpen(false)
|
||||||
|
// Only reset job state if not in progress (preserve polling for background jobs)
|
||||||
|
if (!taggingInProgress) {
|
||||||
setActiveTaggingJobId(null)
|
setActiveTaggingJobId(null)
|
||||||
setSelectedRoundForTagging('')
|
setSelectedRoundForTagging('')
|
||||||
setSelectedProgramForTagging('')
|
setSelectedProgramForTagging('')
|
||||||
|
|
@ -618,9 +619,22 @@ export default function ProjectsPage() {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
<Button variant="outline" onClick={() => setAiTagDialogOpen(true)}>
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setAiTagDialogOpen(true)}
|
||||||
|
className={taggingInProgress ? 'border-amber-400 bg-amber-50 dark:bg-amber-950/20' : ''}
|
||||||
|
>
|
||||||
|
{taggingInProgress ? (
|
||||||
|
<Loader2 className="mr-2 h-4 w-4 animate-spin text-amber-600" />
|
||||||
|
) : (
|
||||||
<Bot className="mr-2 h-4 w-4" />
|
<Bot className="mr-2 h-4 w-4" />
|
||||||
|
)}
|
||||||
AI Tags
|
AI Tags
|
||||||
|
{taggingInProgress && (
|
||||||
|
<span className="ml-1.5 text-[10px] text-amber-600 font-medium">
|
||||||
|
{taggingProgressPercent}%
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outline" asChild>
|
<Button variant="outline" asChild>
|
||||||
<Link href="/admin/projects/pool">
|
<Link href="/admin/projects/pool">
|
||||||
|
|
@ -1833,9 +1847,8 @@ export default function ProjectsPage() {
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={handleCloseTaggingDialog}
|
onClick={handleCloseTaggingDialog}
|
||||||
disabled={taggingInProgress}
|
|
||||||
>
|
>
|
||||||
Cancel
|
{taggingInProgress ? 'Run in Background' : 'Cancel'}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleStartTagging}
|
onClick={handleStartTagging}
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,7 @@ import { CoverageReport } from '@/components/admin/assignment/coverage-report'
|
||||||
import { AssignmentPreviewSheet } from '@/components/admin/assignment/assignment-preview-sheet'
|
import { AssignmentPreviewSheet } from '@/components/admin/assignment/assignment-preview-sheet'
|
||||||
import { CsvExportDialog } from '@/components/shared/csv-export-dialog'
|
import { CsvExportDialog } from '@/components/shared/csv-export-dialog'
|
||||||
import { AnimatedCard } from '@/components/shared/animated-container'
|
import { AnimatedCard } from '@/components/shared/animated-container'
|
||||||
|
import { AddMemberDialog } from '@/components/admin/jury/add-member-dialog'
|
||||||
import { motion } from 'motion/react'
|
import { motion } from 'motion/react'
|
||||||
|
|
||||||
// ── Status & type config maps ──────────────────────────────────────────────
|
// ── Status & type config maps ──────────────────────────────────────────────
|
||||||
|
|
@ -156,6 +157,9 @@ export default function RoundDetailPage() {
|
||||||
BUSINESS_CONCEPT: Array<{ projectId: string; rank: number; score: number; category: string; strengths: string[]; concerns: string[]; recommendation: string }>
|
BUSINESS_CONCEPT: Array<{ projectId: string; rank: number; score: number; category: string; strengths: string[]; concerns: string[]; recommendation: string }>
|
||||||
} | null>(null)
|
} | null>(null)
|
||||||
const [shortlistDialogOpen, setShortlistDialogOpen] = useState(false)
|
const [shortlistDialogOpen, setShortlistDialogOpen] = useState(false)
|
||||||
|
const [createJuryOpen, setCreateJuryOpen] = useState(false)
|
||||||
|
const [newJuryName, setNewJuryName] = useState('')
|
||||||
|
const [addMemberOpen, setAddMemberOpen] = useState(false)
|
||||||
|
|
||||||
const utils = trpc.useUtils()
|
const utils = trpc.useUtils()
|
||||||
|
|
||||||
|
|
@ -249,11 +253,48 @@ export default function RoundDetailPage() {
|
||||||
const assignJuryMutation = trpc.round.update.useMutation({
|
const assignJuryMutation = trpc.round.update.useMutation({
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
utils.round.getById.invalidate({ id: roundId })
|
utils.round.getById.invalidate({ id: roundId })
|
||||||
|
utils.juryGroup.list.invalidate({ competitionId })
|
||||||
toast.success('Jury group updated')
|
toast.success('Jury group updated')
|
||||||
},
|
},
|
||||||
onError: (err) => toast.error(err.message),
|
onError: (err) => toast.error(err.message),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Jury group detail query (for the assigned group)
|
||||||
|
const juryGroupId = round?.juryGroupId ?? ''
|
||||||
|
const { data: juryGroupDetail } = trpc.juryGroup.getById.useQuery(
|
||||||
|
{ id: juryGroupId },
|
||||||
|
{ enabled: !!juryGroupId, refetchInterval: 10_000 },
|
||||||
|
)
|
||||||
|
|
||||||
|
const createJuryMutation = trpc.juryGroup.create.useMutation({
|
||||||
|
onSuccess: (newGroup) => {
|
||||||
|
utils.juryGroup.list.invalidate({ competitionId })
|
||||||
|
// Auto-assign the new jury group to this round
|
||||||
|
assignJuryMutation.mutate({ id: roundId, juryGroupId: newGroup.id })
|
||||||
|
toast.success(`Jury "${newGroup.name}" created and assigned`)
|
||||||
|
setCreateJuryOpen(false)
|
||||||
|
setNewJuryName('')
|
||||||
|
},
|
||||||
|
onError: (err) => toast.error(err.message),
|
||||||
|
})
|
||||||
|
|
||||||
|
const deleteJuryMutation = trpc.juryGroup.delete.useMutation({
|
||||||
|
onSuccess: (result) => {
|
||||||
|
utils.juryGroup.list.invalidate({ competitionId })
|
||||||
|
utils.round.getById.invalidate({ id: roundId })
|
||||||
|
toast.success(`Jury "${result.name}" deleted`)
|
||||||
|
},
|
||||||
|
onError: (err) => toast.error(err.message),
|
||||||
|
})
|
||||||
|
|
||||||
|
const removeJuryMemberMutation = trpc.juryGroup.removeMember.useMutation({
|
||||||
|
onSuccess: () => {
|
||||||
|
if (juryGroupId) utils.juryGroup.getById.invalidate({ id: juryGroupId })
|
||||||
|
toast.success('Member removed')
|
||||||
|
},
|
||||||
|
onError: (err) => toast.error(err.message),
|
||||||
|
})
|
||||||
|
|
||||||
const advanceMutation = trpc.round.advanceProjects.useMutation({
|
const advanceMutation = trpc.round.advanceProjects.useMutation({
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
utils.round.getById.invalidate({ id: roundId })
|
utils.round.getById.invalidate({ id: roundId })
|
||||||
|
|
@ -502,7 +543,7 @@ export default function RoundDetailPage() {
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Link href={poolLink}>
|
<Link href={poolLink}>
|
||||||
<Button variant="outline" size="sm" className="border-white/30 text-white hover:bg-white/10 hover:text-white">
|
<Button variant="outline" size="sm" className="border-white/40 bg-white/15 text-white hover:bg-white/30 hover:text-white">
|
||||||
<Layers className="h-4 w-4 mr-1.5" />
|
<Layers className="h-4 w-4 mr-1.5" />
|
||||||
Project Pool
|
Project Pool
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -653,6 +694,7 @@ export default function RoundDetailPage() {
|
||||||
{ value: 'projects', label: 'Projects', icon: Layers },
|
{ value: 'projects', label: 'Projects', icon: Layers },
|
||||||
...(isFiltering ? [{ value: 'filtering', label: 'Filtering', icon: Shield }] : []),
|
...(isFiltering ? [{ value: 'filtering', label: 'Filtering', icon: Shield }] : []),
|
||||||
...(isEvaluation ? [{ value: 'assignments', label: 'Assignments', icon: ClipboardList }] : []),
|
...(isEvaluation ? [{ value: 'assignments', label: 'Assignments', icon: ClipboardList }] : []),
|
||||||
|
{ value: 'jury', label: 'Jury', icon: Users },
|
||||||
{ value: 'config', label: 'Config', icon: Settings },
|
{ value: 'config', label: 'Config', icon: Settings },
|
||||||
{ value: 'windows', label: 'Submissions', icon: Clock },
|
{ value: 'windows', label: 'Submissions', icon: Clock },
|
||||||
{ value: 'awards', label: 'Awards', icon: Trophy },
|
{ value: 'awards', label: 'Awards', icon: Trophy },
|
||||||
|
|
@ -1128,6 +1170,248 @@ export default function RoundDetailPage() {
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* ═══════════ JURY TAB ═══════════ */}
|
||||||
|
<TabsContent value="jury" className="space-y-6">
|
||||||
|
{/* Jury Group Selector + Create */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<CardTitle className="text-base">Jury Group</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Select or create a jury group for this round
|
||||||
|
</CardDescription>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button size="sm" variant="outline" onClick={() => setCreateJuryOpen(true)}>
|
||||||
|
<Plus className="h-4 w-4 mr-1.5" />
|
||||||
|
New Jury
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
{juryGroups && juryGroups.length > 0 ? (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<Select
|
||||||
|
value={round.juryGroupId ?? '__none__'}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
assignJuryMutation.mutate({
|
||||||
|
id: roundId,
|
||||||
|
juryGroupId: value === '__none__' ? null : value,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
disabled={assignJuryMutation.isPending}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-full sm:w-80">
|
||||||
|
<SelectValue placeholder="Select jury group..." />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="__none__">No jury assigned</SelectItem>
|
||||||
|
{juryGroups.map((jg: any) => (
|
||||||
|
<SelectItem key={jg.id} value={jg.id}>
|
||||||
|
{jg.name} ({jg._count?.members ?? 0} members)
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
{/* Delete button for currently selected jury group */}
|
||||||
|
{round.juryGroupId && (
|
||||||
|
<AlertDialog>
|
||||||
|
<AlertDialogTrigger asChild>
|
||||||
|
<Button size="sm" variant="ghost" className="text-destructive hover:text-destructive">
|
||||||
|
<Trash2 className="h-4 w-4 mr-1.5" />
|
||||||
|
Delete "{juryGroup?.name}"
|
||||||
|
</Button>
|
||||||
|
</AlertDialogTrigger>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>Delete jury group?</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
This will permanently delete "{juryGroup?.name}" and remove all its members.
|
||||||
|
Rounds using this jury group will be unlinked. This action cannot be undone.
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
onClick={() => deleteJuryMutation.mutate({ id: round.juryGroupId! })}
|
||||||
|
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||||
|
disabled={deleteJuryMutation.isPending}
|
||||||
|
>
|
||||||
|
{deleteJuryMutation.isPending && <Loader2 className="h-4 w-4 mr-1.5 animate-spin" />}
|
||||||
|
Delete Jury
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex flex-col items-center justify-center py-10 text-center">
|
||||||
|
<div className="rounded-full bg-purple-50 p-4 mb-4">
|
||||||
|
<Users className="h-8 w-8 text-purple-400" />
|
||||||
|
</div>
|
||||||
|
<p className="text-sm font-medium">No Jury Groups</p>
|
||||||
|
<p className="text-xs text-muted-foreground mt-1 max-w-sm">
|
||||||
|
Create a jury group to assign members who will evaluate projects in this round.
|
||||||
|
</p>
|
||||||
|
<Button size="sm" className="mt-4" onClick={() => setCreateJuryOpen(true)}>
|
||||||
|
<Plus className="h-4 w-4 mr-1.5" />
|
||||||
|
Create First Jury
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Members list (only if a jury group is assigned) */}
|
||||||
|
{juryGroupDetail && (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<CardTitle className="text-base">
|
||||||
|
Members — {juryGroupDetail.name}
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
{juryGroupDetail.members.length} member{juryGroupDetail.members.length !== 1 ? 's' : ''}
|
||||||
|
</CardDescription>
|
||||||
|
</div>
|
||||||
|
<Button size="sm" onClick={() => setAddMemberOpen(true)}>
|
||||||
|
<UserPlus className="h-4 w-4 mr-1.5" />
|
||||||
|
Add Member
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
{juryGroupDetail.members.length === 0 ? (
|
||||||
|
<div className="flex flex-col items-center justify-center py-10 text-center">
|
||||||
|
<div className="rounded-full bg-muted p-4 mb-4">
|
||||||
|
<UserPlus className="h-8 w-8 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
<p className="text-sm font-medium">No Members Yet</p>
|
||||||
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
|
Add jury members to start assigning projects for evaluation.
|
||||||
|
</p>
|
||||||
|
<Button size="sm" variant="outline" className="mt-4" onClick={() => setAddMemberOpen(true)}>
|
||||||
|
<UserPlus className="h-4 w-4 mr-1.5" />
|
||||||
|
Add First Member
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-1">
|
||||||
|
{juryGroupDetail.members.map((member: any, idx: number) => (
|
||||||
|
<div
|
||||||
|
key={member.id}
|
||||||
|
className={cn(
|
||||||
|
'flex items-center justify-between py-2.5 px-3 rounded-md transition-colors',
|
||||||
|
idx % 2 === 1 && 'bg-muted/30',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<p className="text-sm font-medium truncate">
|
||||||
|
{member.user.name || 'Unnamed User'}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-muted-foreground truncate">{member.user.email}</p>
|
||||||
|
</div>
|
||||||
|
<AlertDialog>
|
||||||
|
<AlertDialogTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="h-7 w-7 text-destructive hover:text-destructive shrink-0"
|
||||||
|
>
|
||||||
|
<Trash2 className="h-3.5 w-3.5" />
|
||||||
|
</Button>
|
||||||
|
</AlertDialogTrigger>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>Remove member?</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
Remove {member.user.name || member.user.email} from {juryGroupDetail.name}?
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
onClick={() => removeJuryMemberMutation.mutate({ id: member.id })}
|
||||||
|
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Create Jury Dialog */}
|
||||||
|
<Dialog open={createJuryOpen} onOpenChange={setCreateJuryOpen}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Create Jury Group</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Create a new jury group for this competition. It will be automatically assigned to this round.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium">Name</label>
|
||||||
|
<Input
|
||||||
|
placeholder="e.g. Round 1 Jury, Expert Panel, Final Jury"
|
||||||
|
value={newJuryName}
|
||||||
|
onChange={(e) => setNewJuryName(e.target.value)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' && newJuryName.trim()) {
|
||||||
|
createJuryMutation.mutate({
|
||||||
|
competitionId,
|
||||||
|
name: newJuryName.trim(),
|
||||||
|
slug: newJuryName.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, ''),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="outline" onClick={() => setCreateJuryOpen(false)}>Cancel</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
createJuryMutation.mutate({
|
||||||
|
competitionId,
|
||||||
|
name: newJuryName.trim(),
|
||||||
|
slug: newJuryName.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, ''),
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
disabled={createJuryMutation.isPending || !newJuryName.trim()}
|
||||||
|
>
|
||||||
|
{createJuryMutation.isPending && <Loader2 className="h-4 w-4 mr-1.5 animate-spin" />}
|
||||||
|
Create
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
{/* Add Member Dialog */}
|
||||||
|
{juryGroupId && (
|
||||||
|
<AddMemberDialog
|
||||||
|
juryGroupId={juryGroupId}
|
||||||
|
open={addMemberOpen}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
setAddMemberOpen(open)
|
||||||
|
if (!open) utils.juryGroup.getById.invalidate({ id: juryGroupId })
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
{/* ═══════════ ASSIGNMENTS TAB (Evaluation rounds) ═══════════ */}
|
{/* ═══════════ ASSIGNMENTS TAB (Evaluation rounds) ═══════════ */}
|
||||||
{isEvaluation && (
|
{isEvaluation && (
|
||||||
<TabsContent value="assignments" className="space-y-6">
|
<TabsContent value="assignments" className="space-y-6">
|
||||||
|
|
|
||||||
|
|
@ -36,14 +36,12 @@ export function AddMemberDialog({ juryGroupId, open, onOpenChange }: AddMemberDi
|
||||||
// Search existing user state
|
// Search existing user state
|
||||||
const [searchQuery, setSearchQuery] = useState('')
|
const [searchQuery, setSearchQuery] = useState('')
|
||||||
const [selectedUserId, setSelectedUserId] = useState<string>('')
|
const [selectedUserId, setSelectedUserId] = useState<string>('')
|
||||||
const [role, setRole] = useState<'CHAIR' | 'MEMBER' | 'OBSERVER'>('MEMBER')
|
|
||||||
const [maxAssignments, setMaxAssignments] = useState<string>('')
|
const [maxAssignments, setMaxAssignments] = useState<string>('')
|
||||||
const [capMode, setCapMode] = useState<string>('')
|
const [capMode, setCapMode] = useState<string>('')
|
||||||
|
|
||||||
// Invite new user state
|
// Invite new user state
|
||||||
const [inviteName, setInviteName] = useState('')
|
const [inviteName, setInviteName] = useState('')
|
||||||
const [inviteEmail, setInviteEmail] = useState('')
|
const [inviteEmail, setInviteEmail] = useState('')
|
||||||
const [inviteRole, setInviteRole] = useState<'CHAIR' | 'MEMBER' | 'OBSERVER'>('MEMBER')
|
|
||||||
const [inviteMaxAssignments, setInviteMaxAssignments] = useState<string>('')
|
const [inviteMaxAssignments, setInviteMaxAssignments] = useState<string>('')
|
||||||
const [inviteCapMode, setInviteCapMode] = useState<string>('')
|
const [inviteCapMode, setInviteCapMode] = useState<string>('')
|
||||||
const [inviteExpertise, setInviteExpertise] = useState('')
|
const [inviteExpertise, setInviteExpertise] = useState('')
|
||||||
|
|
@ -75,7 +73,7 @@ export function AddMemberDialog({ juryGroupId, open, onOpenChange }: AddMemberDi
|
||||||
addMember({
|
addMember({
|
||||||
juryGroupId,
|
juryGroupId,
|
||||||
userId: newUser.id,
|
userId: newUser.id,
|
||||||
role: inviteRole,
|
role: 'MEMBER',
|
||||||
maxAssignmentsOverride: inviteMaxAssignments ? parseInt(inviteMaxAssignments, 10) : null,
|
maxAssignmentsOverride: inviteMaxAssignments ? parseInt(inviteMaxAssignments, 10) : null,
|
||||||
capModeOverride: inviteCapMode && inviteCapMode !== 'DEFAULT' ? (inviteCapMode as 'HARD' | 'SOFT' | 'NONE') : null,
|
capModeOverride: inviteCapMode && inviteCapMode !== 'DEFAULT' ? (inviteCapMode as 'HARD' | 'SOFT' | 'NONE') : null,
|
||||||
})
|
})
|
||||||
|
|
@ -100,12 +98,10 @@ export function AddMemberDialog({ juryGroupId, open, onOpenChange }: AddMemberDi
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
setSearchQuery('')
|
setSearchQuery('')
|
||||||
setSelectedUserId('')
|
setSelectedUserId('')
|
||||||
setRole('MEMBER')
|
|
||||||
setMaxAssignments('')
|
setMaxAssignments('')
|
||||||
setCapMode('')
|
setCapMode('')
|
||||||
setInviteName('')
|
setInviteName('')
|
||||||
setInviteEmail('')
|
setInviteEmail('')
|
||||||
setInviteRole('MEMBER')
|
|
||||||
setInviteMaxAssignments('')
|
setInviteMaxAssignments('')
|
||||||
setInviteCapMode('')
|
setInviteCapMode('')
|
||||||
setInviteExpertise('')
|
setInviteExpertise('')
|
||||||
|
|
@ -122,7 +118,7 @@ export function AddMemberDialog({ juryGroupId, open, onOpenChange }: AddMemberDi
|
||||||
addMember({
|
addMember({
|
||||||
juryGroupId,
|
juryGroupId,
|
||||||
userId: selectedUserId,
|
userId: selectedUserId,
|
||||||
role,
|
role: 'MEMBER',
|
||||||
maxAssignmentsOverride: maxAssignments ? parseInt(maxAssignments, 10) : null,
|
maxAssignmentsOverride: maxAssignments ? parseInt(maxAssignments, 10) : null,
|
||||||
capModeOverride: capMode && capMode !== 'DEFAULT' ? (capMode as 'HARD' | 'SOFT' | 'NONE') : null,
|
capModeOverride: capMode && capMode !== 'DEFAULT' ? (capMode as 'HARD' | 'SOFT' | 'NONE') : null,
|
||||||
})
|
})
|
||||||
|
|
@ -215,20 +211,6 @@ export function AddMemberDialog({ juryGroupId, open, onOpenChange }: AddMemberDi
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="role">Group Role</Label>
|
|
||||||
<Select value={role} onValueChange={(val) => setRole(val as typeof role)}>
|
|
||||||
<SelectTrigger id="role">
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="MEMBER">Member</SelectItem>
|
|
||||||
<SelectItem value="CHAIR">Chair</SelectItem>
|
|
||||||
<SelectItem value="OBSERVER">Observer</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="capMode">Cap Mode</Label>
|
<Label htmlFor="capMode">Cap Mode</Label>
|
||||||
<Select value={capMode || 'DEFAULT'} onValueChange={setCapMode}>
|
<Select value={capMode || 'DEFAULT'} onValueChange={setCapMode}>
|
||||||
|
|
@ -298,20 +280,6 @@ export function AddMemberDialog({ juryGroupId, open, onOpenChange }: AddMemberDi
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="inviteGroupRole">Group Role</Label>
|
|
||||||
<Select value={inviteRole} onValueChange={(val) => setInviteRole(val as typeof inviteRole)}>
|
|
||||||
<SelectTrigger id="inviteGroupRole">
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="MEMBER">Member</SelectItem>
|
|
||||||
<SelectItem value="CHAIR">Chair</SelectItem>
|
|
||||||
<SelectItem value="OBSERVER">Observer</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="inviteCapMode">Cap Mode</Label>
|
<Label htmlFor="inviteCapMode">Cap Mode</Label>
|
||||||
<Select value={inviteCapMode || 'DEFAULT'} onValueChange={setInviteCapMode}>
|
<Select value={inviteCapMode || 'DEFAULT'} onValueChange={setInviteCapMode}>
|
||||||
|
|
|
||||||
|
|
@ -29,15 +29,15 @@ import { AddMemberDialog } from './add-member-dialog'
|
||||||
interface JuryMember {
|
interface JuryMember {
|
||||||
id: string
|
id: string
|
||||||
userId: string
|
userId: string
|
||||||
role: string
|
role?: string
|
||||||
user: {
|
user: {
|
||||||
id: string
|
id: string
|
||||||
name: string | null
|
name: string | null
|
||||||
email: string
|
email: string
|
||||||
}
|
}
|
||||||
maxAssignmentsOverride: number | null
|
maxAssignmentsOverride?: number | null
|
||||||
capModeOverride: string | null
|
capModeOverride?: string | null
|
||||||
preferredStartupRatio: number | null
|
preferredStartupRatio?: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
interface JuryMembersTableProps {
|
interface JuryMembersTableProps {
|
||||||
|
|
@ -81,7 +81,6 @@ export function JuryMembersTable({ juryGroupId, members }: JuryMembersTableProps
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>Name</TableHead>
|
<TableHead>Name</TableHead>
|
||||||
<TableHead>Email</TableHead>
|
<TableHead>Email</TableHead>
|
||||||
<TableHead className="hidden md:table-cell">Role</TableHead>
|
|
||||||
<TableHead className="hidden sm:table-cell">Max Assignments</TableHead>
|
<TableHead className="hidden sm:table-cell">Max Assignments</TableHead>
|
||||||
<TableHead className="hidden lg:table-cell">Cap Mode</TableHead>
|
<TableHead className="hidden lg:table-cell">Cap Mode</TableHead>
|
||||||
<TableHead>Actions</TableHead>
|
<TableHead>Actions</TableHead>
|
||||||
|
|
@ -90,7 +89,7 @@ export function JuryMembersTable({ juryGroupId, members }: JuryMembersTableProps
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{members.length === 0 ? (
|
{members.length === 0 ? (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell colSpan={6} className="text-center text-muted-foreground">
|
<TableCell colSpan={5} className="text-center text-muted-foreground">
|
||||||
No members yet. Add members to get started.
|
No members yet. Add members to get started.
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|
@ -103,11 +102,6 @@ export function JuryMembersTable({ juryGroupId, members }: JuryMembersTableProps
|
||||||
<TableCell className="text-sm text-muted-foreground">
|
<TableCell className="text-sm text-muted-foreground">
|
||||||
{member.user.email}
|
{member.user.email}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="hidden md:table-cell">
|
|
||||||
<Badge variant={member.role === 'CHAIR' ? 'default' : 'secondary'}>
|
|
||||||
{member.role}
|
|
||||||
</Badge>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="hidden sm:table-cell">
|
<TableCell className="hidden sm:table-cell">
|
||||||
{member.maxAssignmentsOverride ?? '—'}
|
{member.maxAssignmentsOverride ?? '—'}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|
|
||||||
|
|
@ -1295,6 +1295,8 @@ export const fileRouter = router({
|
||||||
size: true,
|
size: true,
|
||||||
createdAt: true,
|
createdAt: true,
|
||||||
requirementId: true,
|
requirementId: true,
|
||||||
|
bucket: true,
|
||||||
|
objectKey: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -1487,4 +1489,37 @@ export const fileRouter = router({
|
||||||
|
|
||||||
return { uploadUrl, file }
|
return { uploadUrl, file }
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that files actually exist in storage (MinIO/S3).
|
||||||
|
* Returns a map of objectKey → exists boolean.
|
||||||
|
*/
|
||||||
|
verifyFilesExist: adminProcedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
files: z.array(
|
||||||
|
z.object({
|
||||||
|
bucket: z.string(),
|
||||||
|
objectKey: z.string(),
|
||||||
|
})
|
||||||
|
).max(200),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.query(async ({ input }) => {
|
||||||
|
const { getMinioClient } = await import('@/lib/minio')
|
||||||
|
const client = getMinioClient()
|
||||||
|
|
||||||
|
const results: Record<string, boolean> = {}
|
||||||
|
await Promise.all(
|
||||||
|
input.files.map(async ({ bucket, objectKey }) => {
|
||||||
|
try {
|
||||||
|
await client.statObject(bucket, objectKey)
|
||||||
|
results[objectKey] = true
|
||||||
|
} catch {
|
||||||
|
results[objectKey] = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return results
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -249,6 +249,50 @@ export const juryGroupRouter = router({
|
||||||
return existing
|
return existing
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a jury group entirely
|
||||||
|
*/
|
||||||
|
delete: adminProcedure
|
||||||
|
.input(z.object({ id: z.string() }))
|
||||||
|
.mutation(async ({ ctx, input }) => {
|
||||||
|
const group = await ctx.prisma.juryGroup.findUniqueOrThrow({
|
||||||
|
where: { id: input.id },
|
||||||
|
include: {
|
||||||
|
_count: { select: { assignments: true, rounds: true } },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Unlink any rounds that reference this jury group
|
||||||
|
await ctx.prisma.round.updateMany({
|
||||||
|
where: { juryGroupId: input.id },
|
||||||
|
data: { juryGroupId: null },
|
||||||
|
})
|
||||||
|
|
||||||
|
// Delete all members first (cascade should handle this, but be explicit)
|
||||||
|
await ctx.prisma.juryGroupMember.deleteMany({
|
||||||
|
where: { juryGroupId: input.id },
|
||||||
|
})
|
||||||
|
|
||||||
|
await ctx.prisma.juryGroup.delete({ where: { id: input.id } })
|
||||||
|
|
||||||
|
await logAudit({
|
||||||
|
prisma: ctx.prisma,
|
||||||
|
userId: ctx.user.id,
|
||||||
|
action: 'DELETE',
|
||||||
|
entityType: 'JuryGroup',
|
||||||
|
entityId: input.id,
|
||||||
|
detailsJson: {
|
||||||
|
name: group.name,
|
||||||
|
competitionId: group.competitionId,
|
||||||
|
memberCount: group._count.assignments,
|
||||||
|
},
|
||||||
|
ipAddress: ctx.ip,
|
||||||
|
userAgent: ctx.userAgent,
|
||||||
|
})
|
||||||
|
|
||||||
|
return { success: true, name: group.name }
|
||||||
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update a jury group member's role/overrides
|
* Update a jury group member's role/overrides
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { prisma } from '@/lib/prisma'
|
||||||
import { logAudit } from '../utils/audit'
|
import { logAudit } from '../utils/audit'
|
||||||
import {
|
import {
|
||||||
tagProject,
|
tagProject,
|
||||||
|
tagProjectsBatch,
|
||||||
getTagSuggestions,
|
getTagSuggestions,
|
||||||
addProjectTag,
|
addProjectTag,
|
||||||
removeProjectTag,
|
removeProjectTag,
|
||||||
|
|
@ -17,7 +18,7 @@ import {
|
||||||
NotificationTypes,
|
NotificationTypes,
|
||||||
} from '../services/in-app-notification'
|
} from '../services/in-app-notification'
|
||||||
|
|
||||||
// Background job runner for tagging
|
// Background job runner for tagging — uses batched API calls for efficiency
|
||||||
async function runTaggingJob(jobId: string, userId: string) {
|
async function runTaggingJob(jobId: string, userId: string) {
|
||||||
const job = await prisma.taggingJob.findUnique({
|
const job = await prisma.taggingJob.findUnique({
|
||||||
where: { id: jobId },
|
where: { id: jobId },
|
||||||
|
|
@ -28,7 +29,7 @@ async function runTaggingJob(jobId: string, userId: string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[AI Tagging Job] Starting job ${jobId}...`)
|
console.log(`[AI Tagging Job] Starting job ${jobId} (batched mode)...`)
|
||||||
|
|
||||||
// Mark as running
|
// Mark as running
|
||||||
await prisma.taggingJob.update({
|
await prisma.taggingJob.update({
|
||||||
|
|
@ -56,7 +57,7 @@ async function runTaggingJob(jobId: string, userId: string) {
|
||||||
|
|
||||||
const allProjects = await prisma.project.findMany({
|
const allProjects = await prisma.project.findMany({
|
||||||
where: whereClause,
|
where: whereClause,
|
||||||
select: { id: true, title: true, tags: true },
|
select: { id: true, title: true, tags: true, projectTags: { select: { tagId: true } } },
|
||||||
})
|
})
|
||||||
|
|
||||||
const untaggedProjects = allProjects.filter(p => p.tags.length === 0)
|
const untaggedProjects = allProjects.filter(p => p.tags.length === 0)
|
||||||
|
|
@ -83,48 +84,33 @@ async function runTaggingJob(jobId: string, userId: string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let taggedCount = 0
|
|
||||||
let failedCount = 0
|
|
||||||
const errors: string[] = []
|
|
||||||
const startTime = Date.now()
|
const startTime = Date.now()
|
||||||
|
|
||||||
for (let i = 0; i < untaggedProjects.length; i++) {
|
// Use batched tagging — processes 10 projects per API call, 3 concurrent calls
|
||||||
const project = untaggedProjects[i]
|
const { results, totalTokens } = await tagProjectsBatch(
|
||||||
console.log(`[AI Tagging Job] Processing ${i + 1}/${untaggedProjects.length}: "${project.title.substring(0, 40)}..."`)
|
untaggedProjects,
|
||||||
|
userId,
|
||||||
try {
|
async (processed, total) => {
|
||||||
const result = await tagProject(project.id, userId)
|
// Update job progress on each batch completion
|
||||||
taggedCount++
|
const taggedSoFar = results?.length ?? processed
|
||||||
console.log(`[AI Tagging Job] ✓ Tagged with ${result.applied.length} tags`)
|
|
||||||
} catch (error) {
|
|
||||||
failedCount++
|
|
||||||
const errorMsg = error instanceof Error ? error.message : 'Unknown error'
|
|
||||||
errors.push(`${project.title}: ${errorMsg}`)
|
|
||||||
console.error(`[AI Tagging Job] ✗ Failed: ${errorMsg}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update progress
|
|
||||||
await prisma.taggingJob.update({
|
await prisma.taggingJob.update({
|
||||||
where: { id: jobId },
|
where: { id: jobId },
|
||||||
data: {
|
data: {
|
||||||
processedCount: i + 1,
|
processedCount: processed,
|
||||||
taggedCount,
|
taggedCount: taggedSoFar,
|
||||||
failedCount,
|
|
||||||
errorsJson: errors.length > 0 ? errors.slice(0, 20) : undefined, // Keep last 20 errors
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Log progress every 10 projects
|
|
||||||
if ((i + 1) % 10 === 0) {
|
|
||||||
const elapsed = ((Date.now() - startTime) / 1000).toFixed(0)
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(0)
|
||||||
const avgTime = (Date.now() - startTime) / (i + 1) / 1000
|
console.log(`[AI Tagging Job] Progress: ${processed}/${total} (${elapsed}s elapsed)`)
|
||||||
const remaining = avgTime * (untaggedProjects.length - i - 1)
|
|
||||||
console.log(`[AI Tagging Job] Progress: ${i + 1}/${untaggedProjects.length} (${elapsed}s elapsed, ~${remaining.toFixed(0)}s remaining)`)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const taggedCount = results.filter(r => r.applied.length > 0).length
|
||||||
|
const failedCount = untaggedProjects.length - results.length
|
||||||
|
|
||||||
const totalTime = ((Date.now() - startTime) / 1000).toFixed(1)
|
const totalTime = ((Date.now() - startTime) / 1000).toFixed(1)
|
||||||
console.log(`[AI Tagging Job] Complete: ${taggedCount} tagged, ${failedCount} failed in ${totalTime}s`)
|
console.log(`[AI Tagging Job] Complete: ${taggedCount} tagged, ${failedCount} failed in ${totalTime}s (${totalTokens} tokens)`)
|
||||||
|
|
||||||
// Mark as completed
|
// Mark as completed
|
||||||
await prisma.taggingJob.update({
|
await prisma.taggingJob.update({
|
||||||
|
|
@ -132,7 +118,9 @@ async function runTaggingJob(jobId: string, userId: string) {
|
||||||
data: {
|
data: {
|
||||||
status: 'COMPLETED',
|
status: 'COMPLETED',
|
||||||
completedAt: new Date(),
|
completedAt: new Date(),
|
||||||
errorsJson: errors.length > 0 ? errors : undefined,
|
processedCount: results.length,
|
||||||
|
taggedCount,
|
||||||
|
failedCount,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -144,7 +132,7 @@ async function runTaggingJob(jobId: string, userId: string) {
|
||||||
linkUrl: '/admin/projects',
|
linkUrl: '/admin/projects',
|
||||||
linkLabel: 'View Projects',
|
linkLabel: 'View Projects',
|
||||||
priority: 'normal',
|
priority: 'normal',
|
||||||
metadata: { jobId, taggedCount, failedCount, skippedCount },
|
metadata: { jobId, taggedCount, failedCount, skippedCount, totalTokens },
|
||||||
})
|
})
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,7 @@ interface FilteringRuleInput {
|
||||||
const DEFAULT_BATCH_SIZE = 20
|
const DEFAULT_BATCH_SIZE = 20
|
||||||
const MAX_BATCH_SIZE = 50
|
const MAX_BATCH_SIZE = 50
|
||||||
const MIN_BATCH_SIZE = 1
|
const MIN_BATCH_SIZE = 1
|
||||||
const DEFAULT_PARALLEL_BATCHES = 1
|
const DEFAULT_PARALLEL_BATCHES = 3
|
||||||
const MAX_PARALLEL_BATCHES = 10
|
const MAX_PARALLEL_BATCHES = 10
|
||||||
|
|
||||||
// Structured system prompt for AI screening
|
// Structured system prompt for AI screening
|
||||||
|
|
|
||||||
|
|
@ -344,8 +344,8 @@ export async function generateShortlist(
|
||||||
let totalTokens = 0
|
let totalTokens = 0
|
||||||
const allErrors: string[] = []
|
const allErrors: string[] = []
|
||||||
|
|
||||||
// Run each category independently
|
// Run categories in parallel for efficiency
|
||||||
for (const cat of categories) {
|
const categoryPromises = categories.map(async (cat) => {
|
||||||
const catTopN = cat === 'STARTUP'
|
const catTopN = cat === 'STARTUP'
|
||||||
? (startupTopN ?? topN)
|
? (startupTopN ?? topN)
|
||||||
: (conceptTopN ?? topN)
|
: (conceptTopN ?? topN)
|
||||||
|
|
@ -357,6 +357,12 @@ export async function generateShortlist(
|
||||||
prisma,
|
prisma,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return { cat, result }
|
||||||
|
})
|
||||||
|
|
||||||
|
const categoryResults = await Promise.all(categoryPromises)
|
||||||
|
|
||||||
|
for (const { cat, result } of categoryResults) {
|
||||||
if (cat === 'STARTUP') {
|
if (cat === 'STARTUP') {
|
||||||
allRecommendations.STARTUP = result.recommendations
|
allRecommendations.STARTUP = result.recommendations
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
*
|
*
|
||||||
* Features:
|
* Features:
|
||||||
* - Single project tagging (on-submit or manual)
|
* - Single project tagging (on-submit or manual)
|
||||||
* - Batch tagging for rounds
|
* - Batch tagging with concurrent processing (10 projects/batch, 3 concurrent)
|
||||||
* - Confidence scores for each tag
|
* - Confidence scores for each tag
|
||||||
* - Additive only - never removes existing tags
|
* - Additive only - never removes existing tags
|
||||||
*
|
*
|
||||||
|
|
@ -53,8 +53,10 @@ interface AvailableTag {
|
||||||
|
|
||||||
const CONFIDENCE_THRESHOLD = 0.5
|
const CONFIDENCE_THRESHOLD = 0.5
|
||||||
const DEFAULT_MAX_TAGS = 5
|
const DEFAULT_MAX_TAGS = 5
|
||||||
|
const BATCH_SIZE = 10 // Projects per API call
|
||||||
|
const BATCH_CONCURRENCY = 3 // Concurrent API calls
|
||||||
|
|
||||||
// System prompt optimized for tag suggestion
|
// System prompt optimized for single-project tag suggestion
|
||||||
const TAG_SUGGESTION_SYSTEM_PROMPT = `You are an expert at categorizing ocean conservation and sustainability projects.
|
const TAG_SUGGESTION_SYSTEM_PROMPT = `You are an expert at categorizing ocean conservation and sustainability projects.
|
||||||
|
|
||||||
Analyze the project and suggest the most relevant expertise tags from the provided list.
|
Analyze the project and suggest the most relevant expertise tags from the provided list.
|
||||||
|
|
@ -78,6 +80,36 @@ Rules:
|
||||||
- Maximum 7 suggestions per project
|
- Maximum 7 suggestions per project
|
||||||
- Be conservative - only suggest tags that truly apply`
|
- Be conservative - only suggest tags that truly apply`
|
||||||
|
|
||||||
|
// System prompt optimized for batch tagging (multiple projects in one call)
|
||||||
|
const BATCH_TAG_SYSTEM_PROMPT = `You are an expert at categorizing ocean conservation and sustainability projects.
|
||||||
|
|
||||||
|
Analyze EACH project and suggest the most relevant expertise tags from the provided list.
|
||||||
|
Consider each project's focus areas, technology, methodology, and domain.
|
||||||
|
|
||||||
|
Return JSON with this format:
|
||||||
|
{
|
||||||
|
"projects": [
|
||||||
|
{
|
||||||
|
"project_id": "PROJECT_001",
|
||||||
|
"suggestions": [
|
||||||
|
{
|
||||||
|
"tag_name": "exact tag name from list",
|
||||||
|
"confidence": 0.0-1.0,
|
||||||
|
"reasoning": "brief explanation"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
- Only suggest tags from the provided list (exact names)
|
||||||
|
- Order by relevance (most relevant first)
|
||||||
|
- Confidence should reflect how well the tag matches
|
||||||
|
- Maximum 7 suggestions per project
|
||||||
|
- Be conservative - only suggest tags that truly apply
|
||||||
|
- Return results for ALL projects provided`
|
||||||
|
|
||||||
// ─── Helper Functions ────────────────────────────────────────────────────────
|
// ─── Helper Functions ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -132,7 +164,8 @@ export async function getAvailableTags(): Promise<AvailableTag[]> {
|
||||||
// ─── AI Tagging Core ─────────────────────────────────────────────────────────
|
// ─── AI Tagging Core ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call OpenAI to get tag suggestions for a project
|
* Call OpenAI to get tag suggestions for a single project
|
||||||
|
* Used for on-demand single-project tagging
|
||||||
*/
|
*/
|
||||||
async function getAISuggestions(
|
async function getAISuggestions(
|
||||||
anonymizedProject: AnonymizedProjectForAI,
|
anonymizedProject: AnonymizedProjectForAI,
|
||||||
|
|
@ -147,7 +180,7 @@ async function getAISuggestions(
|
||||||
|
|
||||||
const model = await getConfiguredModel()
|
const model = await getConfiguredModel()
|
||||||
|
|
||||||
// Build tag list for prompt
|
// Build compact tag list for prompt
|
||||||
const tagList = availableTags.map((t) => ({
|
const tagList = availableTags.map((t) => ({
|
||||||
name: t.name,
|
name: t.name,
|
||||||
category: t.category,
|
category: t.category,
|
||||||
|
|
@ -155,10 +188,10 @@ async function getAISuggestions(
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const userPrompt = `PROJECT:
|
const userPrompt = `PROJECT:
|
||||||
${JSON.stringify(anonymizedProject, null, 2)}
|
${JSON.stringify(anonymizedProject)}
|
||||||
|
|
||||||
AVAILABLE TAGS:
|
AVAILABLE TAGS:
|
||||||
${JSON.stringify(tagList, null, 2)}
|
${JSON.stringify(tagList)}
|
||||||
|
|
||||||
Suggest relevant tags for this project.`
|
Suggest relevant tags for this project.`
|
||||||
|
|
||||||
|
|
@ -246,6 +279,160 @@ Suggest relevant tags for this project.`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call OpenAI to get tag suggestions for a batch of projects in one API call.
|
||||||
|
* Returns a map of project_id -> TagSuggestion[].
|
||||||
|
*/
|
||||||
|
async function getAISuggestionsBatch(
|
||||||
|
anonymizedProjects: AnonymizedProjectForAI[],
|
||||||
|
availableTags: AvailableTag[],
|
||||||
|
userId?: string
|
||||||
|
): Promise<{ suggestionsMap: Map<string, TagSuggestion[]>; tokensUsed: number }> {
|
||||||
|
const openai = await getOpenAI()
|
||||||
|
if (!openai) {
|
||||||
|
console.warn('[AI Tagging] OpenAI not configured')
|
||||||
|
return { suggestionsMap: new Map(), tokensUsed: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
const model = await getConfiguredModel()
|
||||||
|
const suggestionsMap = new Map<string, TagSuggestion[]>()
|
||||||
|
|
||||||
|
// Build compact tag list (sent once for entire batch)
|
||||||
|
const tagList = availableTags.map((t) => ({
|
||||||
|
name: t.name,
|
||||||
|
category: t.category,
|
||||||
|
description: t.description,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const userPrompt = `PROJECTS (${anonymizedProjects.length}):
|
||||||
|
${JSON.stringify(anonymizedProjects)}
|
||||||
|
|
||||||
|
AVAILABLE TAGS:
|
||||||
|
${JSON.stringify(tagList)}
|
||||||
|
|
||||||
|
Suggest relevant tags for each project.`
|
||||||
|
|
||||||
|
const MAX_PARSE_RETRIES = 2
|
||||||
|
let parseAttempts = 0
|
||||||
|
|
||||||
|
try {
|
||||||
|
const params = buildCompletionParams(model, {
|
||||||
|
messages: [
|
||||||
|
{ role: 'system', content: BATCH_TAG_SYSTEM_PROMPT },
|
||||||
|
{ role: 'user', content: userPrompt },
|
||||||
|
],
|
||||||
|
jsonMode: true,
|
||||||
|
temperature: 0.1,
|
||||||
|
maxTokens: Math.min(4000, anonymizedProjects.length * 500),
|
||||||
|
})
|
||||||
|
|
||||||
|
let response = await openai.chat.completions.create(params)
|
||||||
|
let usage = extractTokenUsage(response)
|
||||||
|
let totalTokens = usage.totalTokens
|
||||||
|
|
||||||
|
// Parse with retry logic
|
||||||
|
let parsed: {
|
||||||
|
projects: Array<{
|
||||||
|
project_id: string
|
||||||
|
suggestions: Array<{
|
||||||
|
tag_name: string
|
||||||
|
confidence: number
|
||||||
|
reasoning: string
|
||||||
|
}>
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
const content = response.choices[0]?.message?.content
|
||||||
|
if (!content) throw new Error('Empty response from AI')
|
||||||
|
|
||||||
|
const raw = JSON.parse(content)
|
||||||
|
parsed = raw.projects ? raw : { projects: Array.isArray(raw) ? raw : [] }
|
||||||
|
break
|
||||||
|
} catch (parseError) {
|
||||||
|
if (parseError instanceof SyntaxError && parseAttempts < MAX_PARSE_RETRIES) {
|
||||||
|
parseAttempts++
|
||||||
|
console.warn(`[AI Tagging Batch] JSON parse failed, retrying (${parseAttempts}/${MAX_PARSE_RETRIES})`)
|
||||||
|
const retryParams = buildCompletionParams(model, {
|
||||||
|
messages: [
|
||||||
|
{ role: 'system', content: BATCH_TAG_SYSTEM_PROMPT },
|
||||||
|
{ role: 'user', content: userPrompt + '\n\nIMPORTANT: Please ensure valid JSON output.' },
|
||||||
|
],
|
||||||
|
jsonMode: true,
|
||||||
|
temperature: 0.1,
|
||||||
|
maxTokens: Math.min(4000, anonymizedProjects.length * 500),
|
||||||
|
})
|
||||||
|
response = await openai.chat.completions.create(retryParams)
|
||||||
|
const retryUsage = extractTokenUsage(response)
|
||||||
|
totalTokens += retryUsage.totalTokens
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
throw parseError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log usage for the entire batch
|
||||||
|
await logAIUsage({
|
||||||
|
userId,
|
||||||
|
action: 'PROJECT_TAGGING',
|
||||||
|
entityType: 'Project',
|
||||||
|
model,
|
||||||
|
promptTokens: usage.promptTokens,
|
||||||
|
completionTokens: usage.completionTokens,
|
||||||
|
totalTokens,
|
||||||
|
batchSize: anonymizedProjects.length,
|
||||||
|
itemsProcessed: parsed.projects?.length || 0,
|
||||||
|
status: 'SUCCESS',
|
||||||
|
})
|
||||||
|
|
||||||
|
// Map results back to TagSuggestion format
|
||||||
|
for (const projectResult of parsed.projects || []) {
|
||||||
|
const suggestions: TagSuggestion[] = []
|
||||||
|
for (const s of projectResult.suggestions || []) {
|
||||||
|
const tag = availableTags.find(
|
||||||
|
(t) => t.name.toLowerCase() === s.tag_name.toLowerCase()
|
||||||
|
)
|
||||||
|
if (tag) {
|
||||||
|
suggestions.push({
|
||||||
|
tagId: tag.id,
|
||||||
|
tagName: tag.name,
|
||||||
|
confidence: Math.max(0, Math.min(1, s.confidence)),
|
||||||
|
reasoning: s.reasoning || '',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
suggestionsMap.set(projectResult.project_id, suggestions)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { suggestionsMap, tokensUsed: totalTokens }
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof SyntaxError) {
|
||||||
|
const parseError = createParseError(error.message)
|
||||||
|
logAIError('Tagging', 'getAISuggestionsBatch', parseError)
|
||||||
|
}
|
||||||
|
|
||||||
|
const classified = classifyAIError(error)
|
||||||
|
logAIError('Tagging', 'getAISuggestionsBatch', classified)
|
||||||
|
|
||||||
|
await logAIUsage({
|
||||||
|
userId,
|
||||||
|
action: 'PROJECT_TAGGING',
|
||||||
|
entityType: 'Project',
|
||||||
|
model: 'unknown',
|
||||||
|
promptTokens: 0,
|
||||||
|
completionTokens: 0,
|
||||||
|
totalTokens: 0,
|
||||||
|
batchSize: anonymizedProjects.length,
|
||||||
|
itemsProcessed: 0,
|
||||||
|
status: 'ERROR',
|
||||||
|
errorMessage: error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
})
|
||||||
|
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Public API ──────────────────────────────────────────────────────────────
|
// ─── Public API ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -355,6 +542,153 @@ export async function tagProject(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag a batch of projects using batched API calls with concurrency.
|
||||||
|
* Much more efficient than tagging one-by-one for bulk operations.
|
||||||
|
*
|
||||||
|
* @param projects Array of { id, projectTags } to tag
|
||||||
|
* @param userId The user initiating the tagging
|
||||||
|
* @param onProgress Callback for progress updates
|
||||||
|
* @returns Array of TaggingResult
|
||||||
|
*/
|
||||||
|
export async function tagProjectsBatch(
|
||||||
|
projects: Array<{
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
projectTags: Array<{ tagId: string }>
|
||||||
|
}>,
|
||||||
|
userId: string,
|
||||||
|
onProgress?: (processed: number, total: number) => Promise<void>
|
||||||
|
): Promise<{ results: TaggingResult[]; totalTokens: number }> {
|
||||||
|
const settings = await getTaggingSettings()
|
||||||
|
if (!settings.enabled) {
|
||||||
|
return { results: [], totalTokens: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
const availableTags = await getAvailableTags()
|
||||||
|
if (availableTags.length === 0) {
|
||||||
|
return { results: [], totalTokens: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch full project data for all projects at once (single DB query)
|
||||||
|
const fullProjects = await prisma.project.findMany({
|
||||||
|
where: { id: { in: projects.map((p) => p.id) } },
|
||||||
|
include: {
|
||||||
|
projectTags: true,
|
||||||
|
files: { select: { fileType: true } },
|
||||||
|
_count: { select: { teamMembers: true, files: true } },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const projectMap = new Map(fullProjects.map((p) => [p.id, p]))
|
||||||
|
|
||||||
|
// Anonymize all projects at once
|
||||||
|
const projectsWithRelations = fullProjects.map(toProjectWithRelations)
|
||||||
|
const { anonymized, mappings } = anonymizeProjectsForAI(projectsWithRelations, 'FILTERING')
|
||||||
|
|
||||||
|
if (!validateAnonymizedProjects(anonymized)) {
|
||||||
|
throw new Error('GDPR compliance check failed: PII detected in anonymized data')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build mapping from anonymous ID to real project
|
||||||
|
const anonToRealMap = new Map<string, string>()
|
||||||
|
for (const mapping of mappings) {
|
||||||
|
anonToRealMap.set(mapping.anonymousId, mapping.realId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split into batches
|
||||||
|
const batches: AnonymizedProjectForAI[][] = []
|
||||||
|
for (let i = 0; i < anonymized.length; i += BATCH_SIZE) {
|
||||||
|
batches.push(anonymized.slice(i, i + BATCH_SIZE))
|
||||||
|
}
|
||||||
|
|
||||||
|
const allResults: TaggingResult[] = []
|
||||||
|
let totalTokens = 0
|
||||||
|
let processedCount = 0
|
||||||
|
|
||||||
|
// Process batches with concurrency
|
||||||
|
for (let i = 0; i < batches.length; i += BATCH_CONCURRENCY) {
|
||||||
|
const concurrentBatches = batches.slice(i, i + BATCH_CONCURRENCY)
|
||||||
|
|
||||||
|
const batchPromises = concurrentBatches.map(async (batch) => {
|
||||||
|
try {
|
||||||
|
const { suggestionsMap, tokensUsed } = await getAISuggestionsBatch(
|
||||||
|
batch,
|
||||||
|
availableTags,
|
||||||
|
userId
|
||||||
|
)
|
||||||
|
return { suggestionsMap, tokensUsed, error: null }
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[AI Tagging Batch] Batch failed:', error)
|
||||||
|
return { suggestionsMap: new Map<string, TagSuggestion[]>(), tokensUsed: 0, error }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const batchResults = await Promise.all(batchPromises)
|
||||||
|
|
||||||
|
// Process results from all concurrent batches
|
||||||
|
for (const { suggestionsMap, tokensUsed } of batchResults) {
|
||||||
|
totalTokens += tokensUsed
|
||||||
|
|
||||||
|
for (const [anonId, suggestions] of suggestionsMap) {
|
||||||
|
const realId = anonToRealMap.get(anonId)
|
||||||
|
if (!realId) continue
|
||||||
|
|
||||||
|
const project = projectMap.get(realId)
|
||||||
|
if (!project) continue
|
||||||
|
|
||||||
|
// Filter by confidence
|
||||||
|
const validSuggestions = suggestions.filter(
|
||||||
|
(s) => s.confidence >= settings.confidenceThreshold
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get existing tags
|
||||||
|
const existingTagIds = new Set(project.projectTags.map((pt) => pt.tagId))
|
||||||
|
const currentTagCount = project.projectTags.length
|
||||||
|
const remainingSlots = Math.max(0, settings.maxTags - currentTagCount)
|
||||||
|
|
||||||
|
const newSuggestions = validSuggestions
|
||||||
|
.filter((s) => !existingTagIds.has(s.tagId))
|
||||||
|
.slice(0, remainingSlots)
|
||||||
|
|
||||||
|
// Apply tags
|
||||||
|
const applied: TagSuggestion[] = []
|
||||||
|
for (const suggestion of newSuggestions) {
|
||||||
|
try {
|
||||||
|
await prisma.projectTag.create({
|
||||||
|
data: {
|
||||||
|
projectId: realId,
|
||||||
|
tagId: suggestion.tagId,
|
||||||
|
confidence: suggestion.confidence,
|
||||||
|
source: 'AI',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
applied.push(suggestion)
|
||||||
|
} catch {
|
||||||
|
// Skip duplicates
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allResults.push({
|
||||||
|
projectId: realId,
|
||||||
|
suggestions,
|
||||||
|
applied,
|
||||||
|
tokensUsed: 0, // Token tracking is per-batch, not per-project
|
||||||
|
})
|
||||||
|
|
||||||
|
processedCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report progress after each concurrent chunk
|
||||||
|
if (onProgress) {
|
||||||
|
await onProgress(processedCount, projects.length)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { results: allResults, totalTokens }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get tag suggestions for a project without applying them
|
* Get tag suggestions for a project without applying them
|
||||||
* Useful for preview/review before applying
|
* Useful for preview/review before applying
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue