diff --git a/prisma/migrations/20260211000000_add_file_requirements/migration.sql b/prisma/migrations/20260211000000_add_file_requirements/migration.sql new file mode 100644 index 0000000..d132a75 --- /dev/null +++ b/prisma/migrations/20260211000000_add_file_requirements/migration.sql @@ -0,0 +1,30 @@ +-- CreateTable +CREATE TABLE "FileRequirement" ( + "id" TEXT NOT NULL, + "roundId" TEXT NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT, + "acceptedMimeTypes" TEXT[], + "maxSizeMB" INTEGER, + "isRequired" BOOLEAN NOT NULL DEFAULT true, + "sortOrder" INTEGER NOT NULL DEFAULT 0, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "FileRequirement_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "FileRequirement_roundId_idx" ON "FileRequirement"("roundId"); + +-- AddForeignKey +ALTER TABLE "FileRequirement" ADD CONSTRAINT "FileRequirement_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AlterTable: add requirementId to ProjectFile +ALTER TABLE "ProjectFile" ADD COLUMN "requirementId" TEXT; + +-- CreateIndex +CREATE INDEX "ProjectFile_requirementId_idx" ON "ProjectFile"("requirementId"); + +-- AddForeignKey +ALTER TABLE "ProjectFile" ADD CONSTRAINT "ProjectFile_requirementId_fkey" FOREIGN KEY ("requirementId") REFERENCES "FileRequirement"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/src/app/(admin)/admin/rounds/new/page.tsx b/src/app/(admin)/admin/rounds/new/page.tsx index 0246940..fc2edb7 100644 --- a/src/app/(admin)/admin/rounds/new/page.tsx +++ b/src/app/(admin)/admin/rounds/new/page.tsx @@ -417,7 +417,20 @@ function CreateRoundContent() {

- {createRound.error.message} + {(() => { + const msg = createRound.error.message + try { + const parsed = JSON.parse(msg) + if (Array.isArray(parsed)) { + return parsed.map((e: { message?: string; path?: string[] }) => + e.message || 'Validation error' + ).join('. ') + } + } catch { + // Not JSON, use as-is + } + return msg + })()}

diff --git a/src/server/routers/project.ts b/src/server/routers/project.ts index 5b819e8..c8733a0 100644 --- a/src/server/routers/project.ts +++ b/src/server/routers/project.ts @@ -327,17 +327,21 @@ export const projectRouter = router({ }, }, }, - projectTags: { - include: { - tag: { - select: { id: true, name: true, category: true, color: true }, - }, - }, - orderBy: { confidence: 'desc' }, - }, }, }) + // Fetch project tags separately (table may not exist if migrations are pending) + let projectTags: { id: string; projectId: string; tagId: string; confidence: number; tag: { id: string; name: string; category: string | null; color: string | null } }[] = [] + try { + projectTags = await ctx.prisma.projectTag.findMany({ + where: { projectId: input.id }, + include: { tag: { select: { id: true, name: true, category: true, color: true } } }, + orderBy: { confidence: 'desc' }, + }) + } catch { + // ProjectTag table may not exist yet + } + // Check access for jury members if (ctx.user.role === 'JURY_MEMBER') { const assignment = await ctx.prisma.assignment.findFirst({ @@ -381,6 +385,7 @@ export const projectRouter = router({ return { ...project, + projectTags, teamMembers: teamMembersWithAvatars, mentorAssignment: mentorWithAvatar, } diff --git a/src/server/routers/round.ts b/src/server/routers/round.ts index 0e3ad5f..f53bbc7 100644 --- a/src/server/routers/round.ts +++ b/src/server/routers/round.ts @@ -111,7 +111,7 @@ export const roundRouter = router({ programId: z.string(), name: z.string().min(1).max(255), roundType: z.enum(['FILTERING', 'EVALUATION', 'LIVE_EVENT']).default('EVALUATION'), - requiredReviews: z.number().int().min(1).max(10).default(3), + requiredReviews: z.number().int().min(0).max(10).default(3), minAssignmentsPerJuror: z.number().int().min(1).max(50).default(5), maxAssignmentsPerJuror: z.number().int().min(1).max(100).default(20), sortOrder: z.number().int().optional(), @@ -208,7 +208,7 @@ export const roundRouter = router({ name: z.string().min(1).max(255).optional(), slug: z.string().min(1).max(100).regex(/^[a-z0-9-]+$/).optional().nullable(), roundType: z.enum(['FILTERING', 'EVALUATION', 'LIVE_EVENT']).optional(), - requiredReviews: z.number().int().min(1).max(10).optional(), + requiredReviews: z.number().int().min(0).max(10).optional(), minAssignmentsPerJuror: z.number().int().min(1).max(50).optional(), maxAssignmentsPerJuror: z.number().int().min(1).max(100).optional(), submissionDeadline: z.date().optional().nullable(), @@ -615,6 +615,12 @@ export const roundRouter = router({ userAgent: ctx.userAgent, }) + // Reset status for projects that will lose their roundId (ON DELETE SET NULL) + await tx.project.updateMany({ + where: { roundId: input.id }, + data: { status: 'SUBMITTED' }, + }) + // Delete evaluations first to avoid FK constraint on Evaluation.formId // (formId FK may not have CASCADE in older DB schemas) await tx.evaluation.deleteMany({