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:
Matt
2026-05-22 15:58:16 +02:00
parent 3bcbf72ad6
commit e89dca24c3
2 changed files with 207 additions and 84 deletions

View File

@@ -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");

View File

@@ -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.01.0 confidence
analyzedAt DateTime? // When analysis last ran
detectedLang String? // ISO 639-3 code (e.g. 'eng', 'fra', 'und')
langConfidence Float? // 0.01.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)