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
|
NONE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
enum PartnerVisibility {
|
enum PartnerVisibility {
|
||||||
ADMIN_ONLY
|
ADMIN_ONLY
|
||||||
JURY_VISIBLE
|
JURY_VISIBLE
|
||||||
@@ -133,7 +132,6 @@ enum PartnerType {
|
|||||||
OTHER
|
OTHER
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// COMPETITION / ROUND ENGINE ENUMS
|
// COMPETITION / ROUND ENGINE ENUMS
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@@ -171,7 +169,6 @@ enum ProjectRoundStateValue {
|
|||||||
WITHDRAWN
|
WITHDRAWN
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
enum CapMode {
|
enum CapMode {
|
||||||
HARD
|
HARD
|
||||||
SOFT
|
SOFT
|
||||||
@@ -328,8 +325,8 @@ model User {
|
|||||||
inviteTokenExpiresAt DateTime?
|
inviteTokenExpiresAt DateTime?
|
||||||
|
|
||||||
// Password reset token
|
// Password reset token
|
||||||
passwordResetToken String? @unique
|
passwordResetToken String? @unique
|
||||||
passwordResetExpiresAt DateTime?
|
passwordResetExpiresAt DateTime?
|
||||||
|
|
||||||
// Digest & availability preferences
|
// Digest & availability preferences
|
||||||
digestFrequency String @default("none") // 'none' | 'daily' | 'weekly'
|
digestFrequency String @default("none") // 'none' | 'daily' | 'weekly'
|
||||||
@@ -363,9 +360,9 @@ model User {
|
|||||||
filteringOverrides FilteringResult[] @relation("FilteringOverriddenBy")
|
filteringOverrides FilteringResult[] @relation("FilteringOverriddenBy")
|
||||||
|
|
||||||
// Award overrides
|
// Award overrides
|
||||||
awardEligibilityOverrides AwardEligibility[] @relation("AwardEligibilityOverriddenBy")
|
awardEligibilityOverrides AwardEligibility[] @relation("AwardEligibilityOverriddenBy")
|
||||||
awardEligibilityConfirms AwardEligibility[] @relation("AwardEligibilityConfirmer")
|
awardEligibilityConfirms AwardEligibility[] @relation("AwardEligibilityConfirmer")
|
||||||
awardWinnerOverrides SpecialAward[] @relation("AwardOverriddenBy")
|
awardWinnerOverrides SpecialAward[] @relation("AwardOverriddenBy")
|
||||||
|
|
||||||
// In-app notifications
|
// In-app notifications
|
||||||
notifications InAppNotification[] @relation("UserNotifications")
|
notifications InAppNotification[] @relation("UserNotifications")
|
||||||
@@ -413,20 +410,24 @@ model User {
|
|||||||
sessions Session[]
|
sessions Session[]
|
||||||
|
|
||||||
// ── Competition/Round architecture relations ──
|
// ── Competition/Round architecture relations ──
|
||||||
juryGroupMemberships JuryGroupMember[]
|
juryGroupMemberships JuryGroupMember[]
|
||||||
mentorFilesUploaded MentorFile[] @relation("MentorFileUploader")
|
mentorFilesUploaded MentorFile[] @relation("MentorFileUploader")
|
||||||
mentorFilesPromoted MentorFile[] @relation("MentorFilePromoter")
|
mentorFilesPromoted MentorFile[] @relation("MentorFilePromoter")
|
||||||
mentorFileComments MentorFileComment[] @relation("MentorFileCommentAuthor")
|
mentorFileComments MentorFileComment[] @relation("MentorFileCommentAuthor")
|
||||||
resultLocksCreated ResultLock[] @relation("ResultLockCreator")
|
resultLocksCreated ResultLock[] @relation("ResultLockCreator")
|
||||||
resultUnlockEvents ResultUnlockEvent[] @relation("ResultUnlocker")
|
resultUnlockEvents ResultUnlockEvent[] @relation("ResultUnlocker")
|
||||||
submissionPromotions SubmissionPromotionEvent[] @relation("SubmissionPromoter")
|
submissionPromotions SubmissionPromotionEvent[] @relation("SubmissionPromoter")
|
||||||
deliberationReplacements DeliberationParticipant[] @relation("DeliberationReplacement")
|
deliberationReplacements DeliberationParticipant[] @relation("DeliberationReplacement")
|
||||||
|
|
||||||
// AI Ranking
|
// AI Ranking
|
||||||
rankingSnapshots RankingSnapshot[] @relation("TriggeredRankingSnapshots")
|
rankingSnapshots RankingSnapshot[] @relation("TriggeredRankingSnapshots")
|
||||||
|
|
||||||
// Grand-finale logistics
|
// Grand-finale logistics
|
||||||
finalistAttendances AttendingMember[]
|
finalistAttendances AttendingMember[]
|
||||||
|
|
||||||
|
// Mentor change requests
|
||||||
|
mentorChangeRequestsRequested MentorChangeRequest[] @relation("MentorChangeRequester")
|
||||||
|
mentorChangeRequestsResolved MentorChangeRequest[] @relation("MentorChangeResolver")
|
||||||
|
|
||||||
@@index([role])
|
@@index([role])
|
||||||
@@index([status])
|
@@index([status])
|
||||||
@@ -629,7 +630,9 @@ model Project {
|
|||||||
assignments Assignment[]
|
assignments Assignment[]
|
||||||
submittedBy User? @relation("ProjectSubmittedBy", fields: [submittedByUserId], references: [id], onDelete: SetNull)
|
submittedBy User? @relation("ProjectSubmittedBy", fields: [submittedByUserId], references: [id], onDelete: SetNull)
|
||||||
teamMembers TeamMember[]
|
teamMembers TeamMember[]
|
||||||
mentorAssignment MentorAssignment?
|
mentorAssignments MentorAssignment[]
|
||||||
|
mentorFiles MentorFile[]
|
||||||
|
mentorChangeRequests MentorChangeRequest[]
|
||||||
filteringResults FilteringResult[]
|
filteringResults FilteringResult[]
|
||||||
awardEligibilities AwardEligibility[]
|
awardEligibilities AwardEligibility[]
|
||||||
awardVotes AwardVote[]
|
awardVotes AwardVote[]
|
||||||
@@ -642,12 +645,12 @@ model Project {
|
|||||||
cohortProjects CohortProject[]
|
cohortProjects CohortProject[]
|
||||||
|
|
||||||
// ── Competition/Round architecture relations ──
|
// ── Competition/Round architecture relations ──
|
||||||
projectRoundStates ProjectRoundState[]
|
projectRoundStates ProjectRoundState[]
|
||||||
assignmentIntents AssignmentIntent[]
|
assignmentIntents AssignmentIntent[]
|
||||||
deliberationVotes DeliberationVote[]
|
deliberationVotes DeliberationVote[]
|
||||||
deliberationResults DeliberationResult[]
|
deliberationResults DeliberationResult[]
|
||||||
submissionPromotions SubmissionPromotionEvent[]
|
submissionPromotions SubmissionPromotionEvent[]
|
||||||
notificationLogs NotificationLog[]
|
notificationLogs NotificationLog[]
|
||||||
|
|
||||||
// Grand-finale logistics
|
// Grand-finale logistics
|
||||||
waitlistEntry WaitlistEntry?
|
waitlistEntry WaitlistEntry?
|
||||||
@@ -699,9 +702,9 @@ model ProjectFile {
|
|||||||
|
|
||||||
// Document analysis (optional, populated by document-analyzer service)
|
// Document analysis (optional, populated by document-analyzer service)
|
||||||
textPreview String? @db.Text // First ~2000 chars of extracted text
|
textPreview String? @db.Text // First ~2000 chars of extracted text
|
||||||
detectedLang String? // ISO 639-3 code (e.g. 'eng', 'fra', 'und')
|
detectedLang String? // ISO 639-3 code (e.g. 'eng', 'fra', 'und')
|
||||||
langConfidence Float? // 0.0–1.0 confidence
|
langConfidence Float? // 0.0–1.0 confidence
|
||||||
analyzedAt DateTime? // When analysis last ran
|
analyzedAt DateTime? // When analysis last ran
|
||||||
|
|
||||||
// MinIO location
|
// MinIO location
|
||||||
bucket String
|
bucket String
|
||||||
@@ -714,7 +717,7 @@ model ProjectFile {
|
|||||||
replacedById String? // FK to the newer file that replaced this one
|
replacedById String? // FK to the newer file that replaced this one
|
||||||
|
|
||||||
// ── Competition/Round architecture fields ──
|
// ── Competition/Round architecture fields ──
|
||||||
submissionWindowId String? // FK to SubmissionWindow
|
submissionWindowId String? // FK to SubmissionWindow
|
||||||
submissionFileRequirementId String? // FK to SubmissionFileRequirement
|
submissionFileRequirementId String? // FK to SubmissionFileRequirement
|
||||||
|
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
@@ -763,10 +766,10 @@ model Assignment {
|
|||||||
juryGroupId String?
|
juryGroupId String?
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||||
round Round @relation(fields: [roundId], references: [id], onDelete: Cascade)
|
round Round @relation(fields: [roundId], references: [id], onDelete: Cascade)
|
||||||
juryGroup JuryGroup? @relation(fields: [juryGroupId], references: [id], onDelete: SetNull)
|
juryGroup JuryGroup? @relation(fields: [juryGroupId], references: [id], onDelete: SetNull)
|
||||||
evaluation Evaluation?
|
evaluation Evaluation?
|
||||||
conflictOfInterest ConflictOfInterest?
|
conflictOfInterest ConflictOfInterest?
|
||||||
|
|
||||||
@@ -1026,12 +1029,12 @@ model NotificationEmailSetting {
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
model LearningResource {
|
model LearningResource {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
programId String? // null = global resource
|
programId String? // null = global resource
|
||||||
title String
|
title String
|
||||||
description String? @db.Text
|
description String? @db.Text
|
||||||
contentJson Json? @db.JsonB // BlockNote document structure
|
contentJson Json? @db.JsonB // BlockNote document structure
|
||||||
accessJson Json? @db.JsonB // Fine-grained access rules
|
accessJson Json? @db.JsonB // Fine-grained access rules
|
||||||
|
|
||||||
// File storage (for uploaded resources)
|
// File storage (for uploaded resources)
|
||||||
fileName String?
|
fileName String?
|
||||||
@@ -1270,7 +1273,7 @@ model TeamMember {
|
|||||||
|
|
||||||
model MentorAssignment {
|
model MentorAssignment {
|
||||||
id String @id @default(cuid())
|
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
|
mentorId String // User with MENTOR role or expertise
|
||||||
|
|
||||||
// Assignment tracking
|
// Assignment tracking
|
||||||
@@ -1278,6 +1281,9 @@ model MentorAssignment {
|
|||||||
assignedAt DateTime @default(now())
|
assignedAt DateTime @default(now())
|
||||||
assignedBy String? // Admin who assigned
|
assignedBy String? // Admin who assigned
|
||||||
|
|
||||||
|
// Per-assignment email idempotency: stamped once the assignment notification email is sent.
|
||||||
|
notificationSentAt DateTime?
|
||||||
|
|
||||||
// AI assignment metadata
|
// AI assignment metadata
|
||||||
aiConfidenceScore Float?
|
aiConfidenceScore Float?
|
||||||
expertiseMatchScore Float?
|
expertiseMatchScore Float?
|
||||||
@@ -1304,11 +1310,47 @@ model MentorAssignment {
|
|||||||
milestoneCompletions MentorMilestoneCompletion[]
|
milestoneCompletions MentorMilestoneCompletion[]
|
||||||
messages MentorMessage[]
|
messages MentorMessage[]
|
||||||
files MentorFile[]
|
files MentorFile[]
|
||||||
|
changeRequests MentorChangeRequest[] @relation("MentorChangeRequestTarget")
|
||||||
|
|
||||||
|
@@unique([projectId, mentorId])
|
||||||
|
@@index([projectId])
|
||||||
@@index([mentorId])
|
@@index([mentorId])
|
||||||
@@index([method])
|
@@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
|
// FILTERING ROUND SYSTEM
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@@ -1443,17 +1485,17 @@ enum AssignmentJobStatus {
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
enum RankingTriggerType {
|
enum RankingTriggerType {
|
||||||
MANUAL // Admin clicked "Run ranking"
|
MANUAL // Admin clicked "Run ranking"
|
||||||
AUTO // Auto-triggered by assignment completion
|
AUTO // Auto-triggered by assignment completion
|
||||||
RETROACTIVE // Retroactive scan on deployment
|
RETROACTIVE // Retroactive scan on deployment
|
||||||
QUICK // Quick-rank mode (no preview)
|
QUICK // Quick-rank mode (no preview)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RankingMode {
|
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
|
CONFIRMED // Admin confirmed rules, ranking applied
|
||||||
QUICK // Quick-rank: parse + apply without preview
|
QUICK // Quick-rank: parse + apply without preview
|
||||||
FORMULA // Formula-only: no LLM, pure math ranking
|
FORMULA // Formula-only: no LLM, pure math ranking
|
||||||
}
|
}
|
||||||
|
|
||||||
enum RankingSnapshotStatus {
|
enum RankingSnapshotStatus {
|
||||||
@@ -1470,7 +1512,7 @@ model RankingSnapshot {
|
|||||||
roundId String
|
roundId String
|
||||||
|
|
||||||
// Trigger metadata
|
// Trigger metadata
|
||||||
triggeredById String? // null = auto-triggered
|
triggeredById String? // null = auto-triggered
|
||||||
triggerType RankingTriggerType @default(MANUAL)
|
triggerType RankingTriggerType @default(MANUAL)
|
||||||
|
|
||||||
// Criteria used
|
// Criteria used
|
||||||
@@ -1599,7 +1641,7 @@ model SpecialAward {
|
|||||||
evaluationRoundId String?
|
evaluationRoundId String?
|
||||||
juryGroupId String?
|
juryGroupId String?
|
||||||
eligibilityMode AwardEligibilityMode @default(STAY_IN_MAIN)
|
eligibilityMode AwardEligibilityMode @default(STAY_IN_MAIN)
|
||||||
decisionMode String? // "JURY_VOTE" | "ADMIN_DECISION"
|
decisionMode String? // "JURY_VOTE" | "ADMIN_DECISION"
|
||||||
shortlistSize Int @default(10)
|
shortlistSize Int @default(10)
|
||||||
|
|
||||||
// Eligibility job tracking
|
// Eligibility job tracking
|
||||||
@@ -1621,10 +1663,10 @@ model SpecialAward {
|
|||||||
votes AwardVote[]
|
votes AwardVote[]
|
||||||
|
|
||||||
// Competition/Round architecture relations
|
// Competition/Round architecture relations
|
||||||
competition Competition? @relation(fields: [competitionId], references: [id], onDelete: SetNull)
|
competition Competition? @relation(fields: [competitionId], references: [id], onDelete: SetNull)
|
||||||
evaluationRound Round? @relation(fields: [evaluationRoundId], references: [id], onDelete: SetNull)
|
evaluationRound Round? @relation(fields: [evaluationRoundId], references: [id], onDelete: SetNull)
|
||||||
awardJuryGroup JuryGroup? @relation(fields: [juryGroupId], references: [id], onDelete: SetNull)
|
awardJuryGroup JuryGroup? @relation(fields: [juryGroupId], references: [id], onDelete: SetNull)
|
||||||
rounds Round[] @relation("AwardRounds")
|
rounds Round[] @relation("AwardRounds")
|
||||||
|
|
||||||
@@index([programId])
|
@@index([programId])
|
||||||
@@index([status])
|
@@index([status])
|
||||||
@@ -1688,12 +1730,12 @@ model AwardJuror {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model AwardVote {
|
model AwardVote {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
awardId String
|
awardId String
|
||||||
userId String
|
userId String
|
||||||
projectId String
|
projectId String
|
||||||
rank Int? // For RANKED mode
|
rank Int? // For RANKED mode
|
||||||
justification String? @db.Text
|
justification String? @db.Text
|
||||||
votedAt DateTime @default(now())
|
votedAt DateTime @default(now())
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
@@ -1810,7 +1852,7 @@ model MentorMessage {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
// ── Competition/Round architecture fields ──
|
// ── Competition/Round architecture fields ──
|
||||||
workspaceId String? // FK to MentorAssignment (used as workspace)
|
workspaceId String? // FK to MentorAssignment (used as workspace)
|
||||||
senderRole MentorMessageRole?
|
senderRole MentorMessageRole?
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
@@ -2146,9 +2188,9 @@ model Competition {
|
|||||||
status CompetitionStatus @default(DRAFT)
|
status CompetitionStatus @default(DRAFT)
|
||||||
|
|
||||||
// Competition-wide settings
|
// Competition-wide settings
|
||||||
categoryMode String @default("SHARED")
|
categoryMode String @default("SHARED")
|
||||||
startupFinalistCount Int @default(3)
|
startupFinalistCount Int @default(3)
|
||||||
conceptFinalistCount Int @default(3)
|
conceptFinalistCount Int @default(3)
|
||||||
|
|
||||||
// Notification preferences
|
// Notification preferences
|
||||||
notifyOnRoundAdvance Boolean @default(true)
|
notifyOnRoundAdvance Boolean @default(true)
|
||||||
@@ -2159,7 +2201,7 @@ model Competition {
|
|||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
program Program @relation(fields: [programId], references: [id], onDelete: Cascade)
|
program Program @relation(fields: [programId], references: [id], onDelete: Cascade)
|
||||||
rounds Round[]
|
rounds Round[]
|
||||||
juryGroups JuryGroup[]
|
juryGroups JuryGroup[]
|
||||||
submissionWindows SubmissionWindow[]
|
submissionWindows SubmissionWindow[]
|
||||||
@@ -2204,10 +2246,10 @@ model Round {
|
|||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
competition Competition @relation(fields: [competitionId], references: [id], onDelete: Cascade)
|
competition Competition @relation(fields: [competitionId], references: [id], onDelete: Cascade)
|
||||||
specialAward SpecialAward? @relation("AwardRounds", fields: [specialAwardId], references: [id], onDelete: SetNull)
|
specialAward SpecialAward? @relation("AwardRounds", fields: [specialAwardId], references: [id], onDelete: SetNull)
|
||||||
juryGroup JuryGroup? @relation(fields: [juryGroupId], references: [id], onDelete: SetNull)
|
juryGroup JuryGroup? @relation(fields: [juryGroupId], references: [id], onDelete: SetNull)
|
||||||
submissionWindow SubmissionWindow? @relation(fields: [submissionWindowId], references: [id], onDelete: SetNull)
|
submissionWindow SubmissionWindow? @relation(fields: [submissionWindowId], references: [id], onDelete: SetNull)
|
||||||
projectRoundStates ProjectRoundState[]
|
projectRoundStates ProjectRoundState[]
|
||||||
visibleSubmissionWindows RoundSubmissionVisibility[]
|
visibleSubmissionWindows RoundSubmissionVisibility[]
|
||||||
assignmentIntents AssignmentIntent[]
|
assignmentIntents AssignmentIntent[]
|
||||||
@@ -2226,7 +2268,7 @@ model Round {
|
|||||||
filteringResults FilteringResult[]
|
filteringResults FilteringResult[]
|
||||||
filteringJobs FilteringJob[]
|
filteringJobs FilteringJob[]
|
||||||
assignmentJobs AssignmentJob[]
|
assignmentJobs AssignmentJob[]
|
||||||
rankingSnapshots RankingSnapshot[] @relation("RoundRankingSnapshots")
|
rankingSnapshots RankingSnapshot[] @relation("RoundRankingSnapshots")
|
||||||
reminderLogs ReminderLog[]
|
reminderLogs ReminderLog[]
|
||||||
evaluationSummaries EvaluationSummary[]
|
evaluationSummaries EvaluationSummary[]
|
||||||
evaluationDiscussions EvaluationDiscussion[]
|
evaluationDiscussions EvaluationDiscussion[]
|
||||||
@@ -2272,7 +2314,7 @@ model ProjectRoundState {
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
model JuryGroup {
|
model JuryGroup {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
competitionId String
|
competitionId String
|
||||||
name String
|
name String
|
||||||
slug String
|
slug String
|
||||||
@@ -2330,8 +2372,8 @@ model JuryGroupMember {
|
|||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
juryGroup JuryGroup @relation(fields: [juryGroupId], references: [id], onDelete: Cascade)
|
juryGroup JuryGroup @relation(fields: [juryGroupId], references: [id], onDelete: Cascade)
|
||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
assignmentIntents AssignmentIntent[]
|
assignmentIntents AssignmentIntent[]
|
||||||
deliberationVotes DeliberationVote[]
|
deliberationVotes DeliberationVote[]
|
||||||
deliberationParticipations DeliberationParticipant[]
|
deliberationParticipations DeliberationParticipant[]
|
||||||
@@ -2369,7 +2411,7 @@ model SubmissionWindow {
|
|||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
competition Competition @relation(fields: [competitionId], references: [id], onDelete: Cascade)
|
competition Competition @relation(fields: [competitionId], references: [id], onDelete: Cascade)
|
||||||
fileRequirements SubmissionFileRequirement[]
|
fileRequirements SubmissionFileRequirement[]
|
||||||
projectFiles ProjectFile[]
|
projectFiles ProjectFile[]
|
||||||
rounds Round[]
|
rounds Round[]
|
||||||
@@ -2403,7 +2445,7 @@ model SubmissionFileRequirement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model RoundSubmissionVisibility {
|
model RoundSubmissionVisibility {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
roundId String
|
roundId String
|
||||||
submissionWindowId String
|
submissionWindowId String
|
||||||
canView Boolean @default(true)
|
canView Boolean @default(true)
|
||||||
@@ -2448,8 +2490,9 @@ model AssignmentIntent {
|
|||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
model MentorFile {
|
model MentorFile {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
mentorAssignmentId String
|
projectId String // Primary access scope: files belong to the team
|
||||||
|
mentorAssignmentId String? // Nullable audit FK: which assignment uploaded; survives mentor drop
|
||||||
uploadedByUserId String
|
uploadedByUserId String
|
||||||
|
|
||||||
fileName String
|
fileName String
|
||||||
@@ -2468,13 +2511,15 @@ model MentorFile {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
mentorAssignment MentorAssignment @relation(fields: [mentorAssignmentId], references: [id], onDelete: Cascade)
|
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||||
uploadedBy User @relation("MentorFileUploader", fields: [uploadedByUserId], references: [id])
|
mentorAssignment MentorAssignment? @relation(fields: [mentorAssignmentId], references: [id], onDelete: SetNull)
|
||||||
promotedBy User? @relation("MentorFilePromoter", fields: [promotedByUserId], references: [id])
|
uploadedBy User @relation("MentorFileUploader", fields: [uploadedByUserId], references: [id])
|
||||||
promotedFile ProjectFile? @relation("PromotedFromMentorFile", fields: [promotedToFileId], references: [id], onDelete: SetNull)
|
promotedBy User? @relation("MentorFilePromoter", fields: [promotedByUserId], references: [id])
|
||||||
|
promotedFile ProjectFile? @relation("PromotedFromMentorFile", fields: [promotedToFileId], references: [id], onDelete: SetNull)
|
||||||
comments MentorFileComment[]
|
comments MentorFileComment[]
|
||||||
promotionEvents SubmissionPromotionEvent[]
|
promotionEvents SubmissionPromotionEvent[]
|
||||||
|
|
||||||
|
@@index([projectId])
|
||||||
@@index([mentorAssignmentId])
|
@@index([mentorAssignmentId])
|
||||||
@@index([uploadedByUserId])
|
@@index([uploadedByUserId])
|
||||||
}
|
}
|
||||||
@@ -2492,9 +2537,9 @@ model MentorFileComment {
|
|||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
mentorFile MentorFile @relation(fields: [mentorFileId], references: [id], onDelete: Cascade)
|
mentorFile MentorFile @relation(fields: [mentorFileId], references: [id], onDelete: Cascade)
|
||||||
author User @relation("MentorFileCommentAuthor", fields: [authorId], references: [id])
|
author User @relation("MentorFileCommentAuthor", fields: [authorId], references: [id])
|
||||||
parentComment MentorFileComment? @relation("CommentThread", fields: [parentCommentId], references: [id], onDelete: Cascade)
|
parentComment MentorFileComment? @relation("CommentThread", fields: [parentCommentId], references: [id], onDelete: Cascade)
|
||||||
replies MentorFileComment[] @relation("CommentThread")
|
replies MentorFileComment[] @relation("CommentThread")
|
||||||
|
|
||||||
@@index([mentorFileId])
|
@@index([mentorFileId])
|
||||||
@@ -2503,14 +2548,14 @@ model MentorFileComment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model SubmissionPromotionEvent {
|
model SubmissionPromotionEvent {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
projectId String
|
projectId String
|
||||||
roundId String
|
roundId String
|
||||||
slotKey String
|
slotKey String
|
||||||
sourceType SubmissionPromotionSource
|
sourceType SubmissionPromotionSource
|
||||||
sourceFileId String?
|
sourceFileId String?
|
||||||
promotedById String
|
promotedById String
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||||
|
|||||||
Reference in New Issue
Block a user