Competition/Round architecture: full platform rewrite (Phases 1-9)
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m45s
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m45s
Replace Pipeline/Stage system with Competition/Round architecture. New schema: Competition, Round (7 types), JuryGroup, AssignmentPolicy, ProjectRoundState, DeliberationSession, ResultLock, SubmissionWindow. New services: round-engine, round-assignment, deliberation, result-lock, submission-manager, competition-context, ai-prompt-guard. Full admin/jury/applicant/mentor UI rewrite. AI prompt hardening with structured prompts, retry logic, and injection detection. All legacy pipeline/stage code removed. 4 new migrations + seed aligned. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
143
src/server/routers/roundEngine.ts
Normal file
143
src/server/routers/roundEngine.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import { z } from 'zod'
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import { router, adminProcedure, protectedProcedure } from '../trpc'
|
||||
import {
|
||||
activateRound,
|
||||
closeRound,
|
||||
archiveRound,
|
||||
transitionProject,
|
||||
batchTransitionProjects,
|
||||
getProjectRoundStates,
|
||||
getProjectRoundState,
|
||||
} from '../services/round-engine'
|
||||
|
||||
const projectRoundStateEnum = z.enum([
|
||||
'PENDING',
|
||||
'IN_PROGRESS',
|
||||
'PASSED',
|
||||
'REJECTED',
|
||||
'COMPLETED',
|
||||
'WITHDRAWN',
|
||||
])
|
||||
|
||||
export const roundEngineRouter = router({
|
||||
/**
|
||||
* Activate a round: ROUND_DRAFT → ROUND_ACTIVE
|
||||
*/
|
||||
activate: adminProcedure
|
||||
.input(z.object({ roundId: z.string() }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const result = await activateRound(input.roundId, ctx.user.id, ctx.prisma)
|
||||
if (!result.success) {
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: result.errors?.join('; ') ?? 'Failed to activate round',
|
||||
})
|
||||
}
|
||||
return result
|
||||
}),
|
||||
|
||||
/**
|
||||
* Close a round: ROUND_ACTIVE → ROUND_CLOSED
|
||||
*/
|
||||
close: adminProcedure
|
||||
.input(z.object({ roundId: z.string() }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const result = await closeRound(input.roundId, ctx.user.id, ctx.prisma)
|
||||
if (!result.success) {
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: result.errors?.join('; ') ?? 'Failed to close round',
|
||||
})
|
||||
}
|
||||
return result
|
||||
}),
|
||||
|
||||
/**
|
||||
* Archive a round: ROUND_CLOSED → ROUND_ARCHIVED
|
||||
*/
|
||||
archive: adminProcedure
|
||||
.input(z.object({ roundId: z.string() }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const result = await archiveRound(input.roundId, ctx.user.id, ctx.prisma)
|
||||
if (!result.success) {
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: result.errors?.join('; ') ?? 'Failed to archive round',
|
||||
})
|
||||
}
|
||||
return result
|
||||
}),
|
||||
|
||||
/**
|
||||
* Transition a single project within a round
|
||||
*/
|
||||
transitionProject: adminProcedure
|
||||
.input(
|
||||
z.object({
|
||||
projectId: z.string(),
|
||||
roundId: z.string(),
|
||||
newState: projectRoundStateEnum,
|
||||
})
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const result = await transitionProject(
|
||||
input.projectId,
|
||||
input.roundId,
|
||||
input.newState,
|
||||
ctx.user.id,
|
||||
ctx.prisma,
|
||||
)
|
||||
if (!result.success) {
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: result.errors?.join('; ') ?? 'Failed to transition project',
|
||||
})
|
||||
}
|
||||
return result
|
||||
}),
|
||||
|
||||
/**
|
||||
* Batch transition multiple projects within a round
|
||||
*/
|
||||
batchTransition: adminProcedure
|
||||
.input(
|
||||
z.object({
|
||||
projectIds: z.array(z.string()).min(1),
|
||||
roundId: z.string(),
|
||||
newState: projectRoundStateEnum,
|
||||
})
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
return batchTransitionProjects(
|
||||
input.projectIds,
|
||||
input.roundId,
|
||||
input.newState,
|
||||
ctx.user.id,
|
||||
ctx.prisma,
|
||||
)
|
||||
}),
|
||||
|
||||
/**
|
||||
* Get all project round states for a round
|
||||
*/
|
||||
getProjectStates: protectedProcedure
|
||||
.input(z.object({ roundId: z.string() }))
|
||||
.query(async ({ ctx, input }) => {
|
||||
return getProjectRoundStates(input.roundId, ctx.prisma)
|
||||
}),
|
||||
|
||||
/**
|
||||
* Get a single project's state within a round
|
||||
*/
|
||||
getProjectState: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
projectId: z.string(),
|
||||
roundId: z.string(),
|
||||
})
|
||||
)
|
||||
.query(async ({ ctx, input }) => {
|
||||
return getProjectRoundState(input.projectId, input.roundId, ctx.prisma)
|
||||
}),
|
||||
})
|
||||
Reference in New Issue
Block a user