Add file requirements per round and super admin promotion via UI

Part A: File Requirements per Round
- New FileRequirement model with name, description, accepted MIME types, max size, required flag, sort order
- Added requirementId FK to ProjectFile for linking uploads to requirements
- Backend CRUD (create/update/delete/reorder) in file router with audit logging
- Mime type validation and team member upload authorization in applicant router
- Admin UI: FileRequirementsEditor component in round edit page
- Applicant UI: RequirementUploadSlot/List components in submission detail and team pages
- Viewer UI: RequirementChecklist with fulfillment status in file-viewer

Part B: Super Admin Promotion
- Added SUPER_ADMIN to role enums in user create/update/bulkCreate with guards
- Member detail page: SUPER_ADMIN dropdown option with AlertDialog confirmation
- Invite page: SUPER_ADMIN option visible only to super admins

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-08 23:01:33 +01:00
parent e73a676412
commit 829acf8d4e
12 changed files with 1229 additions and 62 deletions

View File

@@ -452,6 +452,7 @@ model Round {
taggingJobs TaggingJob[]
reminderLogs ReminderLog[]
projectFiles ProjectFile[]
fileRequirements FileRequirement[]
evaluationDiscussions EvaluationDiscussion[]
messages Message[]
@@ -581,10 +582,31 @@ model Project {
@@index([country])
}
model FileRequirement {
id String @id @default(cuid())
roundId String
name String
description String?
acceptedMimeTypes String[] // e.g. ["application/pdf", "video/*"]
maxSizeMB Int? // Max file size in MB
isRequired Boolean @default(true)
sortOrder Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Relations
round Round @relation(fields: [roundId], references: [id], onDelete: Cascade)
files ProjectFile[]
@@index([roundId])
}
model ProjectFile {
id String @id @default(cuid())
projectId String
roundId String? // Which round this file was submitted for
id String @id @default(cuid())
projectId String
roundId String? // Which round this file was submitted for
requirementId String? // FK to FileRequirement (if uploaded against a requirement)
// File info
fileType FileType
@@ -605,16 +627,18 @@ model ProjectFile {
createdAt DateTime @default(now())
// Relations
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
round Round? @relation(fields: [roundId], references: [id])
replacedBy ProjectFile? @relation("FileVersions", fields: [replacedById], references: [id], onDelete: SetNull)
replacements ProjectFile[] @relation("FileVersions")
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
round Round? @relation(fields: [roundId], references: [id])
requirement FileRequirement? @relation(fields: [requirementId], references: [id], onDelete: SetNull)
replacedBy ProjectFile? @relation("FileVersions", fields: [replacedById], references: [id], onDelete: SetNull)
replacements ProjectFile[] @relation("FileVersions")
@@unique([bucket, objectKey])
@@index([projectId])
@@index([roundId])
@@index([projectId, roundId])
@@index([fileType])
@@index([requirementId])
}
// =============================================================================