feat(schema): multi-mentor per team + change-requests + per-assignment email field
- MentorAssignment: drop projectId @unique -> composite (projectId, mentorId) - MentorAssignment: add notificationSentAt for idempotent per-team email - MentorFile: add projectId (primary scope); mentorAssignmentId becomes nullable audit FK - MentorChangeRequest: new model + status enum - Migration hand-written with IF EXISTS guards (safe for docker-entrypoint retry)
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
-- Hand-written migration for PR8 (multi-mentor per team).
|
||||
--
|
||||
-- All DDL guarded with IF EXISTS / IF NOT EXISTS so the docker-entrypoint
|
||||
-- retry loop is safe to re-run. No regex (the 2026-05-07 prod incident was
|
||||
-- caused by Prisma 6 generating regex-based DDL that Postgres rejected).
|
||||
-- No BEGIN/COMMIT blocks — Prisma wraps the migration in a transaction.
|
||||
|
||||
-- Phase 1: MentorAssignment — drop unique, add composite, add notification field
|
||||
ALTER TABLE "MentorAssignment" DROP CONSTRAINT IF EXISTS "MentorAssignment_projectId_key";
|
||||
DROP INDEX IF EXISTS "MentorAssignment_projectId_key";
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "MentorAssignment_projectId_mentorId_key"
|
||||
ON "MentorAssignment"("projectId", "mentorId");
|
||||
CREATE INDEX IF NOT EXISTS "MentorAssignment_projectId_idx"
|
||||
ON "MentorAssignment"("projectId");
|
||||
ALTER TABLE "MentorAssignment" ADD COLUMN IF NOT EXISTS "notificationSentAt" TIMESTAMP(3);
|
||||
|
||||
-- Phase 2: MentorFile — re-scope to project (two-phase backfill)
|
||||
ALTER TABLE "MentorFile" ADD COLUMN IF NOT EXISTS "projectId" TEXT;
|
||||
UPDATE "MentorFile" mf
|
||||
SET "projectId" = ma."projectId"
|
||||
FROM "MentorAssignment" ma
|
||||
WHERE mf."mentorAssignmentId" = ma."id"
|
||||
AND mf."projectId" IS NULL;
|
||||
ALTER TABLE "MentorFile" ALTER COLUMN "projectId" SET NOT NULL;
|
||||
ALTER TABLE "MentorFile" DROP CONSTRAINT IF EXISTS "MentorFile_projectId_fkey";
|
||||
ALTER TABLE "MentorFile" ADD CONSTRAINT "MentorFile_projectId_fkey"
|
||||
FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
CREATE INDEX IF NOT EXISTS "MentorFile_projectId_idx" ON "MentorFile"("projectId");
|
||||
|
||||
-- Phase 2b: Make MentorFile.mentorAssignmentId nullable + switch its FK to SetNull
|
||||
ALTER TABLE "MentorFile" ALTER COLUMN "mentorAssignmentId" DROP NOT NULL;
|
||||
ALTER TABLE "MentorFile" DROP CONSTRAINT IF EXISTS "MentorFile_mentorAssignmentId_fkey";
|
||||
ALTER TABLE "MentorFile" ADD CONSTRAINT "MentorFile_mentorAssignmentId_fkey"
|
||||
FOREIGN KEY ("mentorAssignmentId") REFERENCES "MentorAssignment"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- Phase 3: MentorChangeRequest table
|
||||
-- Postgres < 14 doesn't support CREATE TYPE ... IF NOT EXISTS, so wrap in a
|
||||
-- DO block that swallows duplicate_object errors (idempotent for re-runs).
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "MentorChangeRequestStatus" AS ENUM ('PENDING', 'RESOLVED', 'DISMISSED');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN NULL;
|
||||
END $$;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "MentorChangeRequest" (
|
||||
"id" TEXT NOT NULL,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"targetAssignmentId" TEXT,
|
||||
"requestedByUserId" TEXT,
|
||||
"reason" TEXT NOT NULL,
|
||||
"status" "MentorChangeRequestStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"resolvedByUserId" TEXT,
|
||||
"resolvedAt" TIMESTAMP(3),
|
||||
"resolutionNote" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
CONSTRAINT "MentorChangeRequest_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
ALTER TABLE "MentorChangeRequest" DROP CONSTRAINT IF EXISTS "MentorChangeRequest_projectId_fkey";
|
||||
ALTER TABLE "MentorChangeRequest" ADD CONSTRAINT "MentorChangeRequest_projectId_fkey"
|
||||
FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "MentorChangeRequest" DROP CONSTRAINT IF EXISTS "MentorChangeRequest_targetAssignmentId_fkey";
|
||||
ALTER TABLE "MentorChangeRequest" ADD CONSTRAINT "MentorChangeRequest_targetAssignmentId_fkey"
|
||||
FOREIGN KEY ("targetAssignmentId") REFERENCES "MentorAssignment"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "MentorChangeRequest" DROP CONSTRAINT IF EXISTS "MentorChangeRequest_requestedByUserId_fkey";
|
||||
ALTER TABLE "MentorChangeRequest" ADD CONSTRAINT "MentorChangeRequest_requestedByUserId_fkey"
|
||||
FOREIGN KEY ("requestedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE "MentorChangeRequest" DROP CONSTRAINT IF EXISTS "MentorChangeRequest_resolvedByUserId_fkey";
|
||||
ALTER TABLE "MentorChangeRequest" ADD CONSTRAINT "MentorChangeRequest_resolvedByUserId_fkey"
|
||||
FOREIGN KEY ("resolvedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "MentorChangeRequest_projectId_idx" ON "MentorChangeRequest"("projectId");
|
||||
CREATE INDEX IF NOT EXISTS "MentorChangeRequest_status_idx" ON "MentorChangeRequest"("status");
|
||||
CREATE INDEX IF NOT EXISTS "MentorChangeRequest_targetAssignmentId_idx" ON "MentorChangeRequest"("targetAssignmentId");
|
||||
@@ -118,7 +118,6 @@ enum NotificationChannel {
|
||||
NONE
|
||||
}
|
||||
|
||||
|
||||
enum PartnerVisibility {
|
||||
ADMIN_ONLY
|
||||
JURY_VISIBLE
|
||||
@@ -133,7 +132,6 @@ enum PartnerType {
|
||||
OTHER
|
||||
}
|
||||
|
||||
|
||||
// =============================================================================
|
||||
// COMPETITION / ROUND ENGINE ENUMS
|
||||
// =============================================================================
|
||||
@@ -171,7 +169,6 @@ enum ProjectRoundStateValue {
|
||||
WITHDRAWN
|
||||
}
|
||||
|
||||
|
||||
enum CapMode {
|
||||
HARD
|
||||
SOFT
|
||||
@@ -328,8 +325,8 @@ model User {
|
||||
inviteTokenExpiresAt DateTime?
|
||||
|
||||
// Password reset token
|
||||
passwordResetToken String? @unique
|
||||
passwordResetExpiresAt DateTime?
|
||||
passwordResetToken String? @unique
|
||||
passwordResetExpiresAt DateTime?
|
||||
|
||||
// Digest & availability preferences
|
||||
digestFrequency String @default("none") // 'none' | 'daily' | 'weekly'
|
||||
@@ -363,9 +360,9 @@ model User {
|
||||
filteringOverrides FilteringResult[] @relation("FilteringOverriddenBy")
|
||||
|
||||
// Award overrides
|
||||
awardEligibilityOverrides AwardEligibility[] @relation("AwardEligibilityOverriddenBy")
|
||||
awardEligibilityConfirms AwardEligibility[] @relation("AwardEligibilityConfirmer")
|
||||
awardWinnerOverrides SpecialAward[] @relation("AwardOverriddenBy")
|
||||
awardEligibilityOverrides AwardEligibility[] @relation("AwardEligibilityOverriddenBy")
|
||||
awardEligibilityConfirms AwardEligibility[] @relation("AwardEligibilityConfirmer")
|
||||
awardWinnerOverrides SpecialAward[] @relation("AwardOverriddenBy")
|
||||
|
||||
// In-app notifications
|
||||
notifications InAppNotification[] @relation("UserNotifications")
|
||||
@@ -413,20 +410,24 @@ model User {
|
||||
sessions Session[]
|
||||
|
||||
// ── Competition/Round architecture relations ──
|
||||
juryGroupMemberships JuryGroupMember[]
|
||||
mentorFilesUploaded MentorFile[] @relation("MentorFileUploader")
|
||||
mentorFilesPromoted MentorFile[] @relation("MentorFilePromoter")
|
||||
mentorFileComments MentorFileComment[] @relation("MentorFileCommentAuthor")
|
||||
resultLocksCreated ResultLock[] @relation("ResultLockCreator")
|
||||
resultUnlockEvents ResultUnlockEvent[] @relation("ResultUnlocker")
|
||||
submissionPromotions SubmissionPromotionEvent[] @relation("SubmissionPromoter")
|
||||
deliberationReplacements DeliberationParticipant[] @relation("DeliberationReplacement")
|
||||
juryGroupMemberships JuryGroupMember[]
|
||||
mentorFilesUploaded MentorFile[] @relation("MentorFileUploader")
|
||||
mentorFilesPromoted MentorFile[] @relation("MentorFilePromoter")
|
||||
mentorFileComments MentorFileComment[] @relation("MentorFileCommentAuthor")
|
||||
resultLocksCreated ResultLock[] @relation("ResultLockCreator")
|
||||
resultUnlockEvents ResultUnlockEvent[] @relation("ResultUnlocker")
|
||||
submissionPromotions SubmissionPromotionEvent[] @relation("SubmissionPromoter")
|
||||
deliberationReplacements DeliberationParticipant[] @relation("DeliberationReplacement")
|
||||
|
||||
// AI Ranking
|
||||
rankingSnapshots RankingSnapshot[] @relation("TriggeredRankingSnapshots")
|
||||
rankingSnapshots RankingSnapshot[] @relation("TriggeredRankingSnapshots")
|
||||
|
||||
// Grand-finale logistics
|
||||
finalistAttendances AttendingMember[]
|
||||
finalistAttendances AttendingMember[]
|
||||
|
||||
// Mentor change requests
|
||||
mentorChangeRequestsRequested MentorChangeRequest[] @relation("MentorChangeRequester")
|
||||
mentorChangeRequestsResolved MentorChangeRequest[] @relation("MentorChangeResolver")
|
||||
|
||||
@@index([role])
|
||||
@@index([status])
|
||||
@@ -629,7 +630,9 @@ model Project {
|
||||
assignments Assignment[]
|
||||
submittedBy User? @relation("ProjectSubmittedBy", fields: [submittedByUserId], references: [id], onDelete: SetNull)
|
||||
teamMembers TeamMember[]
|
||||
mentorAssignment MentorAssignment?
|
||||
mentorAssignments MentorAssignment[]
|
||||
mentorFiles MentorFile[]
|
||||
mentorChangeRequests MentorChangeRequest[]
|
||||
filteringResults FilteringResult[]
|
||||
awardEligibilities AwardEligibility[]
|
||||
awardVotes AwardVote[]
|
||||
@@ -642,12 +645,12 @@ model Project {
|
||||
cohortProjects CohortProject[]
|
||||
|
||||
// ── Competition/Round architecture relations ──
|
||||
projectRoundStates ProjectRoundState[]
|
||||
assignmentIntents AssignmentIntent[]
|
||||
deliberationVotes DeliberationVote[]
|
||||
deliberationResults DeliberationResult[]
|
||||
submissionPromotions SubmissionPromotionEvent[]
|
||||
notificationLogs NotificationLog[]
|
||||
projectRoundStates ProjectRoundState[]
|
||||
assignmentIntents AssignmentIntent[]
|
||||
deliberationVotes DeliberationVote[]
|
||||
deliberationResults DeliberationResult[]
|
||||
submissionPromotions SubmissionPromotionEvent[]
|
||||
notificationLogs NotificationLog[]
|
||||
|
||||
// Grand-finale logistics
|
||||
waitlistEntry WaitlistEntry?
|
||||
@@ -699,9 +702,9 @@ model ProjectFile {
|
||||
|
||||
// Document analysis (optional, populated by document-analyzer service)
|
||||
textPreview String? @db.Text // First ~2000 chars of extracted text
|
||||
detectedLang String? // ISO 639-3 code (e.g. 'eng', 'fra', 'und')
|
||||
langConfidence Float? // 0.0–1.0 confidence
|
||||
analyzedAt DateTime? // When analysis last ran
|
||||
detectedLang String? // ISO 639-3 code (e.g. 'eng', 'fra', 'und')
|
||||
langConfidence Float? // 0.0–1.0 confidence
|
||||
analyzedAt DateTime? // When analysis last ran
|
||||
|
||||
// MinIO location
|
||||
bucket String
|
||||
@@ -714,7 +717,7 @@ model ProjectFile {
|
||||
replacedById String? // FK to the newer file that replaced this one
|
||||
|
||||
// ── Competition/Round architecture fields ──
|
||||
submissionWindowId String? // FK to SubmissionWindow
|
||||
submissionWindowId String? // FK to SubmissionWindow
|
||||
submissionFileRequirementId String? // FK to SubmissionFileRequirement
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
@@ -763,10 +766,10 @@ model Assignment {
|
||||
juryGroupId String?
|
||||
|
||||
// Relations
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
round Round @relation(fields: [roundId], references: [id], onDelete: Cascade)
|
||||
juryGroup JuryGroup? @relation(fields: [juryGroupId], references: [id], onDelete: SetNull)
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
round Round @relation(fields: [roundId], references: [id], onDelete: Cascade)
|
||||
juryGroup JuryGroup? @relation(fields: [juryGroupId], references: [id], onDelete: SetNull)
|
||||
evaluation Evaluation?
|
||||
conflictOfInterest ConflictOfInterest?
|
||||
|
||||
@@ -1026,12 +1029,12 @@ model NotificationEmailSetting {
|
||||
// =============================================================================
|
||||
|
||||
model LearningResource {
|
||||
id String @id @default(cuid())
|
||||
programId String? // null = global resource
|
||||
title String
|
||||
description String? @db.Text
|
||||
contentJson Json? @db.JsonB // BlockNote document structure
|
||||
accessJson Json? @db.JsonB // Fine-grained access rules
|
||||
id String @id @default(cuid())
|
||||
programId String? // null = global resource
|
||||
title String
|
||||
description String? @db.Text
|
||||
contentJson Json? @db.JsonB // BlockNote document structure
|
||||
accessJson Json? @db.JsonB // Fine-grained access rules
|
||||
|
||||
// File storage (for uploaded resources)
|
||||
fileName String?
|
||||
@@ -1270,7 +1273,7 @@ model TeamMember {
|
||||
|
||||
model MentorAssignment {
|
||||
id String @id @default(cuid())
|
||||
projectId String @unique // One mentor per project
|
||||
projectId String // Team can have multiple mentors; uniqueness enforced via composite below
|
||||
mentorId String // User with MENTOR role or expertise
|
||||
|
||||
// Assignment tracking
|
||||
@@ -1278,6 +1281,9 @@ model MentorAssignment {
|
||||
assignedAt DateTime @default(now())
|
||||
assignedBy String? // Admin who assigned
|
||||
|
||||
// Per-assignment email idempotency: stamped once the assignment notification email is sent.
|
||||
notificationSentAt DateTime?
|
||||
|
||||
// AI assignment metadata
|
||||
aiConfidenceScore Float?
|
||||
expertiseMatchScore Float?
|
||||
@@ -1304,11 +1310,47 @@ model MentorAssignment {
|
||||
milestoneCompletions MentorMilestoneCompletion[]
|
||||
messages MentorMessage[]
|
||||
files MentorFile[]
|
||||
changeRequests MentorChangeRequest[] @relation("MentorChangeRequestTarget")
|
||||
|
||||
@@unique([projectId, mentorId])
|
||||
@@index([projectId])
|
||||
@@index([mentorId])
|
||||
@@index([method])
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// MENTOR CHANGE REQUESTS
|
||||
// =============================================================================
|
||||
|
||||
enum MentorChangeRequestStatus {
|
||||
PENDING
|
||||
RESOLVED
|
||||
DISMISSED
|
||||
}
|
||||
|
||||
model MentorChangeRequest {
|
||||
id String @id @default(cuid())
|
||||
projectId String
|
||||
targetAssignmentId String? // Optional: a specific co-mentor the request is about
|
||||
requestedByUserId String?
|
||||
reason String @db.Text
|
||||
status MentorChangeRequestStatus @default(PENDING)
|
||||
resolvedByUserId String?
|
||||
resolvedAt DateTime?
|
||||
resolutionNote String? @db.Text
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
targetAssignment MentorAssignment? @relation("MentorChangeRequestTarget", fields: [targetAssignmentId], references: [id], onDelete: SetNull)
|
||||
requestedBy User? @relation("MentorChangeRequester", fields: [requestedByUserId], references: [id], onDelete: SetNull)
|
||||
resolvedBy User? @relation("MentorChangeResolver", fields: [resolvedByUserId], references: [id])
|
||||
|
||||
@@index([projectId])
|
||||
@@index([status])
|
||||
@@index([targetAssignmentId])
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// FILTERING ROUND SYSTEM
|
||||
// =============================================================================
|
||||
@@ -1443,17 +1485,17 @@ enum AssignmentJobStatus {
|
||||
// =============================================================================
|
||||
|
||||
enum RankingTriggerType {
|
||||
MANUAL // Admin clicked "Run ranking"
|
||||
AUTO // Auto-triggered by assignment completion
|
||||
MANUAL // Admin clicked "Run ranking"
|
||||
AUTO // Auto-triggered by assignment completion
|
||||
RETROACTIVE // Retroactive scan on deployment
|
||||
QUICK // Quick-rank mode (no preview)
|
||||
QUICK // Quick-rank mode (no preview)
|
||||
}
|
||||
|
||||
enum RankingMode {
|
||||
PREVIEW // Parsed rules shown to admin (not yet applied)
|
||||
PREVIEW // Parsed rules shown to admin (not yet applied)
|
||||
CONFIRMED // Admin confirmed rules, ranking applied
|
||||
QUICK // Quick-rank: parse + apply without preview
|
||||
FORMULA // Formula-only: no LLM, pure math ranking
|
||||
QUICK // Quick-rank: parse + apply without preview
|
||||
FORMULA // Formula-only: no LLM, pure math ranking
|
||||
}
|
||||
|
||||
enum RankingSnapshotStatus {
|
||||
@@ -1470,7 +1512,7 @@ model RankingSnapshot {
|
||||
roundId String
|
||||
|
||||
// Trigger metadata
|
||||
triggeredById String? // null = auto-triggered
|
||||
triggeredById String? // null = auto-triggered
|
||||
triggerType RankingTriggerType @default(MANUAL)
|
||||
|
||||
// Criteria used
|
||||
@@ -1599,7 +1641,7 @@ model SpecialAward {
|
||||
evaluationRoundId String?
|
||||
juryGroupId String?
|
||||
eligibilityMode AwardEligibilityMode @default(STAY_IN_MAIN)
|
||||
decisionMode String? // "JURY_VOTE" | "ADMIN_DECISION"
|
||||
decisionMode String? // "JURY_VOTE" | "ADMIN_DECISION"
|
||||
shortlistSize Int @default(10)
|
||||
|
||||
// Eligibility job tracking
|
||||
@@ -1621,10 +1663,10 @@ model SpecialAward {
|
||||
votes AwardVote[]
|
||||
|
||||
// Competition/Round architecture relations
|
||||
competition Competition? @relation(fields: [competitionId], references: [id], onDelete: SetNull)
|
||||
evaluationRound Round? @relation(fields: [evaluationRoundId], references: [id], onDelete: SetNull)
|
||||
awardJuryGroup JuryGroup? @relation(fields: [juryGroupId], references: [id], onDelete: SetNull)
|
||||
rounds Round[] @relation("AwardRounds")
|
||||
competition Competition? @relation(fields: [competitionId], references: [id], onDelete: SetNull)
|
||||
evaluationRound Round? @relation(fields: [evaluationRoundId], references: [id], onDelete: SetNull)
|
||||
awardJuryGroup JuryGroup? @relation(fields: [juryGroupId], references: [id], onDelete: SetNull)
|
||||
rounds Round[] @relation("AwardRounds")
|
||||
|
||||
@@index([programId])
|
||||
@@index([status])
|
||||
@@ -1688,12 +1730,12 @@ model AwardJuror {
|
||||
}
|
||||
|
||||
model AwardVote {
|
||||
id String @id @default(cuid())
|
||||
awardId String
|
||||
userId String
|
||||
projectId String
|
||||
rank Int? // For RANKED mode
|
||||
justification String? @db.Text
|
||||
id String @id @default(cuid())
|
||||
awardId String
|
||||
userId String
|
||||
projectId String
|
||||
rank Int? // For RANKED mode
|
||||
justification String? @db.Text
|
||||
votedAt DateTime @default(now())
|
||||
|
||||
// Relations
|
||||
@@ -1810,7 +1852,7 @@ model MentorMessage {
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
// ── Competition/Round architecture fields ──
|
||||
workspaceId String? // FK to MentorAssignment (used as workspace)
|
||||
workspaceId String? // FK to MentorAssignment (used as workspace)
|
||||
senderRole MentorMessageRole?
|
||||
|
||||
// Relations
|
||||
@@ -2146,9 +2188,9 @@ model Competition {
|
||||
status CompetitionStatus @default(DRAFT)
|
||||
|
||||
// Competition-wide settings
|
||||
categoryMode String @default("SHARED")
|
||||
startupFinalistCount Int @default(3)
|
||||
conceptFinalistCount Int @default(3)
|
||||
categoryMode String @default("SHARED")
|
||||
startupFinalistCount Int @default(3)
|
||||
conceptFinalistCount Int @default(3)
|
||||
|
||||
// Notification preferences
|
||||
notifyOnRoundAdvance Boolean @default(true)
|
||||
@@ -2159,7 +2201,7 @@ model Competition {
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Relations
|
||||
program Program @relation(fields: [programId], references: [id], onDelete: Cascade)
|
||||
program Program @relation(fields: [programId], references: [id], onDelete: Cascade)
|
||||
rounds Round[]
|
||||
juryGroups JuryGroup[]
|
||||
submissionWindows SubmissionWindow[]
|
||||
@@ -2204,10 +2246,10 @@ model Round {
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Relations
|
||||
competition Competition @relation(fields: [competitionId], references: [id], onDelete: Cascade)
|
||||
specialAward SpecialAward? @relation("AwardRounds", fields: [specialAwardId], references: [id], onDelete: SetNull)
|
||||
juryGroup JuryGroup? @relation(fields: [juryGroupId], references: [id], onDelete: SetNull)
|
||||
submissionWindow SubmissionWindow? @relation(fields: [submissionWindowId], references: [id], onDelete: SetNull)
|
||||
competition Competition @relation(fields: [competitionId], references: [id], onDelete: Cascade)
|
||||
specialAward SpecialAward? @relation("AwardRounds", fields: [specialAwardId], references: [id], onDelete: SetNull)
|
||||
juryGroup JuryGroup? @relation(fields: [juryGroupId], references: [id], onDelete: SetNull)
|
||||
submissionWindow SubmissionWindow? @relation(fields: [submissionWindowId], references: [id], onDelete: SetNull)
|
||||
projectRoundStates ProjectRoundState[]
|
||||
visibleSubmissionWindows RoundSubmissionVisibility[]
|
||||
assignmentIntents AssignmentIntent[]
|
||||
@@ -2226,7 +2268,7 @@ model Round {
|
||||
filteringResults FilteringResult[]
|
||||
filteringJobs FilteringJob[]
|
||||
assignmentJobs AssignmentJob[]
|
||||
rankingSnapshots RankingSnapshot[] @relation("RoundRankingSnapshots")
|
||||
rankingSnapshots RankingSnapshot[] @relation("RoundRankingSnapshots")
|
||||
reminderLogs ReminderLog[]
|
||||
evaluationSummaries EvaluationSummary[]
|
||||
evaluationDiscussions EvaluationDiscussion[]
|
||||
@@ -2272,7 +2314,7 @@ model ProjectRoundState {
|
||||
// =============================================================================
|
||||
|
||||
model JuryGroup {
|
||||
id String @id @default(cuid())
|
||||
id String @id @default(cuid())
|
||||
competitionId String
|
||||
name String
|
||||
slug String
|
||||
@@ -2330,8 +2372,8 @@ model JuryGroupMember {
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Relations
|
||||
juryGroup JuryGroup @relation(fields: [juryGroupId], references: [id], onDelete: Cascade)
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
juryGroup JuryGroup @relation(fields: [juryGroupId], references: [id], onDelete: Cascade)
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
assignmentIntents AssignmentIntent[]
|
||||
deliberationVotes DeliberationVote[]
|
||||
deliberationParticipations DeliberationParticipant[]
|
||||
@@ -2369,7 +2411,7 @@ model SubmissionWindow {
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Relations
|
||||
competition Competition @relation(fields: [competitionId], references: [id], onDelete: Cascade)
|
||||
competition Competition @relation(fields: [competitionId], references: [id], onDelete: Cascade)
|
||||
fileRequirements SubmissionFileRequirement[]
|
||||
projectFiles ProjectFile[]
|
||||
rounds Round[]
|
||||
@@ -2403,7 +2445,7 @@ model SubmissionFileRequirement {
|
||||
}
|
||||
|
||||
model RoundSubmissionVisibility {
|
||||
id String @id @default(cuid())
|
||||
id String @id @default(cuid())
|
||||
roundId String
|
||||
submissionWindowId String
|
||||
canView Boolean @default(true)
|
||||
@@ -2448,8 +2490,9 @@ model AssignmentIntent {
|
||||
// =============================================================================
|
||||
|
||||
model MentorFile {
|
||||
id String @id @default(cuid())
|
||||
mentorAssignmentId String
|
||||
id String @id @default(cuid())
|
||||
projectId String // Primary access scope: files belong to the team
|
||||
mentorAssignmentId String? // Nullable audit FK: which assignment uploaded; survives mentor drop
|
||||
uploadedByUserId String
|
||||
|
||||
fileName String
|
||||
@@ -2468,13 +2511,15 @@ model MentorFile {
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
// Relations
|
||||
mentorAssignment MentorAssignment @relation(fields: [mentorAssignmentId], references: [id], onDelete: Cascade)
|
||||
uploadedBy User @relation("MentorFileUploader", fields: [uploadedByUserId], references: [id])
|
||||
promotedBy User? @relation("MentorFilePromoter", fields: [promotedByUserId], references: [id])
|
||||
promotedFile ProjectFile? @relation("PromotedFromMentorFile", fields: [promotedToFileId], references: [id], onDelete: SetNull)
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
mentorAssignment MentorAssignment? @relation(fields: [mentorAssignmentId], references: [id], onDelete: SetNull)
|
||||
uploadedBy User @relation("MentorFileUploader", fields: [uploadedByUserId], references: [id])
|
||||
promotedBy User? @relation("MentorFilePromoter", fields: [promotedByUserId], references: [id])
|
||||
promotedFile ProjectFile? @relation("PromotedFromMentorFile", fields: [promotedToFileId], references: [id], onDelete: SetNull)
|
||||
comments MentorFileComment[]
|
||||
promotionEvents SubmissionPromotionEvent[]
|
||||
|
||||
@@index([projectId])
|
||||
@@index([mentorAssignmentId])
|
||||
@@index([uploadedByUserId])
|
||||
}
|
||||
@@ -2492,9 +2537,9 @@ model MentorFileComment {
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Relations
|
||||
mentorFile MentorFile @relation(fields: [mentorFileId], references: [id], onDelete: Cascade)
|
||||
author User @relation("MentorFileCommentAuthor", fields: [authorId], references: [id])
|
||||
parentComment MentorFileComment? @relation("CommentThread", fields: [parentCommentId], references: [id], onDelete: Cascade)
|
||||
mentorFile MentorFile @relation(fields: [mentorFileId], references: [id], onDelete: Cascade)
|
||||
author User @relation("MentorFileCommentAuthor", fields: [authorId], references: [id])
|
||||
parentComment MentorFileComment? @relation("CommentThread", fields: [parentCommentId], references: [id], onDelete: Cascade)
|
||||
replies MentorFileComment[] @relation("CommentThread")
|
||||
|
||||
@@index([mentorFileId])
|
||||
@@ -2503,14 +2548,14 @@ model MentorFileComment {
|
||||
}
|
||||
|
||||
model SubmissionPromotionEvent {
|
||||
id String @id @default(cuid())
|
||||
id String @id @default(cuid())
|
||||
projectId String
|
||||
roundId String
|
||||
slotKey String
|
||||
sourceType SubmissionPromotionSource
|
||||
sourceFileId String?
|
||||
promotedById String
|
||||
createdAt DateTime @default(now())
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
// Relations
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
|
||||
Reference in New Issue
Block a user