Competition/Round architecture: full platform rewrite (Phases 1-9)
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>
2026-02-15 23:04:15 +01:00
|
|
|
import { z } from 'zod'
|
|
|
|
|
import { TRPCError } from '@trpc/server'
|
|
|
|
|
import { router, adminProcedure, juryProcedure, protectedProcedure } from '../trpc'
|
Admin platform audit: fix bugs, harden backend, add auto-refresh, clean dead code
Phase 1 — Critical bugs:
- Fix deliberation participant selection (wire jury group query)
- Fix reports "By Round" tab (inline content instead of 404 route)
- Fix messages "Sent History" (add message.sent procedure, wire tab)
- Add missing fields to competition award form (criteriaText, maxRankedPicks)
- Wire LiveControlPanel buttons (cursor, voting, scores)
- Fix ResultLockControls empty snapshot (fetch actual data before lock)
- Fix SubmissionWindowManager losing fields on edit
Phase 2 — Backend fixes:
- Remove write-in-query from specialAward.get
- Fix award eligibility job overwriting manual shortlist overrides
- Fix filtering startJob deleting all prior results (defer cleanup to post-success)
- Tighten access control: protectedProcedure → adminProcedure on 8 procedures
- Add audit logging to deliberation mutations
- Add FINALIST/SEMIFINALIST delete guard on project.delete/bulkDelete
Phase 3 — Auto-refresh:
- Add refetchInterval to 15+ admin pages/components (10s–30s)
- Fix AI job polling: derive speed from job status for all viewers
Phase 4 — Dead code cleanup:
- Delete unused command-palette, pdf-report, admin-page-transition
- Remove dead subItems sidebar code, unused GripVertical import
- Replace redundant isGenerating state with mutation.isPending
- Add Role column to jury members table
- Remove misleading manual mentor assignment stub
Phase 5 — UX improvements:
- Fix rounds page single-competition assumption (add selector)
- Remove raw UUID fallback in deliberation config
- Fix programs page "Stage" → "Round" terminology
Phase 6 — Backend hardening:
- Complete logAudit calls (add prisma, ipAddress, userAgent)
- Batch analytics queries (fix N+1 in getCrossRoundComparison, getYearOverYear)
- Batch user.bulkCreate writes (assignments, jury memberships, intents)
- Remove any casts from deliberation service (typed PrismaClient + TransactionClient)
- Fix stale DeliberationStatus enum values blocking build
40 files changed, 1010 insertions(+), 612 deletions(-)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 08:20:13 +01:00
|
|
|
import { logAudit } from '@/server/utils/audit'
|
Competition/Round architecture: full platform rewrite (Phases 1-9)
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>
2026-02-15 23:04:15 +01:00
|
|
|
import {
|
|
|
|
|
createSession,
|
|
|
|
|
openVoting,
|
|
|
|
|
closeVoting,
|
|
|
|
|
submitVote,
|
|
|
|
|
aggregateVotes,
|
|
|
|
|
initRunoff,
|
|
|
|
|
adminDecide,
|
|
|
|
|
finalizeResults,
|
|
|
|
|
updateParticipantStatus,
|
|
|
|
|
getSessionWithVotes,
|
|
|
|
|
} from '../services/deliberation'
|
|
|
|
|
|
|
|
|
|
const categoryEnum = z.enum([
|
|
|
|
|
'STARTUP',
|
|
|
|
|
'BUSINESS_CONCEPT',
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
const deliberationModeEnum = z.enum(['SINGLE_WINNER_VOTE', 'FULL_RANKING'])
|
|
|
|
|
|
|
|
|
|
const tieBreakMethodEnum = z.enum(['TIE_RUNOFF', 'TIE_ADMIN_DECIDES', 'SCORE_FALLBACK'])
|
|
|
|
|
|
|
|
|
|
const participantStatusEnum = z.enum([
|
|
|
|
|
'REQUIRED',
|
|
|
|
|
'ABSENT_EXCUSED',
|
|
|
|
|
'REPLACED',
|
|
|
|
|
'REPLACEMENT_ACTIVE',
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
export const deliberationRouter = router({
|
|
|
|
|
/**
|
|
|
|
|
* Create a new deliberation session with participants
|
|
|
|
|
*/
|
|
|
|
|
createSession: adminProcedure
|
|
|
|
|
.input(
|
|
|
|
|
z.object({
|
|
|
|
|
competitionId: z.string(),
|
|
|
|
|
roundId: z.string(),
|
|
|
|
|
category: categoryEnum,
|
|
|
|
|
mode: deliberationModeEnum,
|
|
|
|
|
tieBreakMethod: tieBreakMethodEnum,
|
|
|
|
|
showCollectiveRankings: z.boolean().default(false),
|
|
|
|
|
showPriorJuryData: z.boolean().default(false),
|
|
|
|
|
participantUserIds: z.array(z.string()).min(1),
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
.mutation(async ({ ctx, input }) => {
|
Admin platform audit: fix bugs, harden backend, add auto-refresh, clean dead code
Phase 1 — Critical bugs:
- Fix deliberation participant selection (wire jury group query)
- Fix reports "By Round" tab (inline content instead of 404 route)
- Fix messages "Sent History" (add message.sent procedure, wire tab)
- Add missing fields to competition award form (criteriaText, maxRankedPicks)
- Wire LiveControlPanel buttons (cursor, voting, scores)
- Fix ResultLockControls empty snapshot (fetch actual data before lock)
- Fix SubmissionWindowManager losing fields on edit
Phase 2 — Backend fixes:
- Remove write-in-query from specialAward.get
- Fix award eligibility job overwriting manual shortlist overrides
- Fix filtering startJob deleting all prior results (defer cleanup to post-success)
- Tighten access control: protectedProcedure → adminProcedure on 8 procedures
- Add audit logging to deliberation mutations
- Add FINALIST/SEMIFINALIST delete guard on project.delete/bulkDelete
Phase 3 — Auto-refresh:
- Add refetchInterval to 15+ admin pages/components (10s–30s)
- Fix AI job polling: derive speed from job status for all viewers
Phase 4 — Dead code cleanup:
- Delete unused command-palette, pdf-report, admin-page-transition
- Remove dead subItems sidebar code, unused GripVertical import
- Replace redundant isGenerating state with mutation.isPending
- Add Role column to jury members table
- Remove misleading manual mentor assignment stub
Phase 5 — UX improvements:
- Fix rounds page single-competition assumption (add selector)
- Remove raw UUID fallback in deliberation config
- Fix programs page "Stage" → "Round" terminology
Phase 6 — Backend hardening:
- Complete logAudit calls (add prisma, ipAddress, userAgent)
- Batch analytics queries (fix N+1 in getCrossRoundComparison, getYearOverYear)
- Batch user.bulkCreate writes (assignments, jury memberships, intents)
- Remove any casts from deliberation service (typed PrismaClient + TransactionClient)
- Fix stale DeliberationStatus enum values blocking build
40 files changed, 1010 insertions(+), 612 deletions(-)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 08:20:13 +01:00
|
|
|
const session = await createSession(input, ctx.prisma)
|
|
|
|
|
|
|
|
|
|
await logAudit({
|
|
|
|
|
prisma: ctx.prisma,
|
|
|
|
|
userId: ctx.user.id,
|
|
|
|
|
action: 'CREATE',
|
|
|
|
|
entityType: 'DeliberationSession',
|
|
|
|
|
entityId: session.id,
|
|
|
|
|
detailsJson: {
|
|
|
|
|
competitionId: input.competitionId,
|
|
|
|
|
roundId: input.roundId,
|
|
|
|
|
category: input.category,
|
|
|
|
|
mode: input.mode,
|
|
|
|
|
participantCount: input.participantUserIds.length,
|
|
|
|
|
},
|
|
|
|
|
ipAddress: ctx.ip,
|
|
|
|
|
userAgent: ctx.userAgent,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return session
|
Competition/Round architecture: full platform rewrite (Phases 1-9)
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>
2026-02-15 23:04:15 +01:00
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Open voting: DELIB_OPEN → VOTING
|
|
|
|
|
*/
|
|
|
|
|
openVoting: adminProcedure
|
|
|
|
|
.input(z.object({ sessionId: z.string() }))
|
|
|
|
|
.mutation(async ({ ctx, input }) => {
|
|
|
|
|
const result = await openVoting(input.sessionId, ctx.user.id, ctx.prisma)
|
|
|
|
|
if (!result.success) {
|
|
|
|
|
throw new TRPCError({
|
|
|
|
|
code: 'BAD_REQUEST',
|
|
|
|
|
message: result.errors?.join('; ') ?? 'Failed to open voting',
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return result
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Close voting: VOTING → TALLYING
|
|
|
|
|
*/
|
|
|
|
|
closeVoting: adminProcedure
|
|
|
|
|
.input(z.object({ sessionId: z.string() }))
|
|
|
|
|
.mutation(async ({ ctx, input }) => {
|
|
|
|
|
const result = await closeVoting(input.sessionId, ctx.user.id, ctx.prisma)
|
|
|
|
|
if (!result.success) {
|
|
|
|
|
throw new TRPCError({
|
|
|
|
|
code: 'BAD_REQUEST',
|
|
|
|
|
message: result.errors?.join('; ') ?? 'Failed to close voting',
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return result
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Submit a vote (jury member)
|
|
|
|
|
*/
|
|
|
|
|
submitVote: juryProcedure
|
|
|
|
|
.input(
|
|
|
|
|
z.object({
|
|
|
|
|
sessionId: z.string(),
|
|
|
|
|
juryMemberId: z.string(),
|
|
|
|
|
projectId: z.string(),
|
|
|
|
|
rank: z.number().int().min(1).optional(),
|
|
|
|
|
isWinnerPick: z.boolean().optional(),
|
|
|
|
|
runoffRound: z.number().int().min(0).optional(),
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
.mutation(async ({ ctx, input }) => {
|
Admin platform audit: fix bugs, harden backend, add auto-refresh, clean dead code
Phase 1 — Critical bugs:
- Fix deliberation participant selection (wire jury group query)
- Fix reports "By Round" tab (inline content instead of 404 route)
- Fix messages "Sent History" (add message.sent procedure, wire tab)
- Add missing fields to competition award form (criteriaText, maxRankedPicks)
- Wire LiveControlPanel buttons (cursor, voting, scores)
- Fix ResultLockControls empty snapshot (fetch actual data before lock)
- Fix SubmissionWindowManager losing fields on edit
Phase 2 — Backend fixes:
- Remove write-in-query from specialAward.get
- Fix award eligibility job overwriting manual shortlist overrides
- Fix filtering startJob deleting all prior results (defer cleanup to post-success)
- Tighten access control: protectedProcedure → adminProcedure on 8 procedures
- Add audit logging to deliberation mutations
- Add FINALIST/SEMIFINALIST delete guard on project.delete/bulkDelete
Phase 3 — Auto-refresh:
- Add refetchInterval to 15+ admin pages/components (10s–30s)
- Fix AI job polling: derive speed from job status for all viewers
Phase 4 — Dead code cleanup:
- Delete unused command-palette, pdf-report, admin-page-transition
- Remove dead subItems sidebar code, unused GripVertical import
- Replace redundant isGenerating state with mutation.isPending
- Add Role column to jury members table
- Remove misleading manual mentor assignment stub
Phase 5 — UX improvements:
- Fix rounds page single-competition assumption (add selector)
- Remove raw UUID fallback in deliberation config
- Fix programs page "Stage" → "Round" terminology
Phase 6 — Backend hardening:
- Complete logAudit calls (add prisma, ipAddress, userAgent)
- Batch analytics queries (fix N+1 in getCrossRoundComparison, getYearOverYear)
- Batch user.bulkCreate writes (assignments, jury memberships, intents)
- Remove any casts from deliberation service (typed PrismaClient + TransactionClient)
- Fix stale DeliberationStatus enum values blocking build
40 files changed, 1010 insertions(+), 612 deletions(-)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 08:20:13 +01:00
|
|
|
const vote = await submitVote(input, ctx.prisma)
|
|
|
|
|
|
|
|
|
|
await logAudit({
|
|
|
|
|
prisma: ctx.prisma,
|
|
|
|
|
userId: ctx.user.id,
|
|
|
|
|
action: 'CREATE',
|
|
|
|
|
entityType: 'DeliberationVote',
|
|
|
|
|
entityId: input.sessionId,
|
|
|
|
|
detailsJson: {
|
|
|
|
|
sessionId: input.sessionId,
|
|
|
|
|
projectId: input.projectId,
|
|
|
|
|
rank: input.rank,
|
|
|
|
|
isWinnerPick: input.isWinnerPick,
|
|
|
|
|
runoffRound: input.runoffRound,
|
|
|
|
|
},
|
|
|
|
|
ipAddress: ctx.ip,
|
|
|
|
|
userAgent: ctx.userAgent,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return vote
|
Competition/Round architecture: full platform rewrite (Phases 1-9)
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>
2026-02-15 23:04:15 +01:00
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Aggregate votes for a session
|
|
|
|
|
*/
|
|
|
|
|
aggregate: adminProcedure
|
|
|
|
|
.input(z.object({ sessionId: z.string() }))
|
|
|
|
|
.query(async ({ ctx, input }) => {
|
2026-02-19 11:11:00 +01:00
|
|
|
const result = await aggregateVotes(input.sessionId, ctx.prisma)
|
|
|
|
|
// Enrich rankings with project titles
|
|
|
|
|
const projectIds = result.rankings.map((r) => r.projectId)
|
|
|
|
|
const projects = projectIds.length > 0
|
|
|
|
|
? await ctx.prisma.project.findMany({
|
|
|
|
|
where: { id: { in: projectIds } },
|
|
|
|
|
select: { id: true, title: true, teamName: true },
|
|
|
|
|
})
|
|
|
|
|
: []
|
|
|
|
|
const projectMap = new Map(projects.map((p) => [p.id, p]))
|
|
|
|
|
return {
|
|
|
|
|
...result,
|
|
|
|
|
rankings: result.rankings.map((r) => ({
|
|
|
|
|
...r,
|
|
|
|
|
projectTitle: projectMap.get(r.projectId)?.title ?? 'Unknown Project',
|
|
|
|
|
teamName: projectMap.get(r.projectId)?.teamName ?? '',
|
|
|
|
|
})),
|
|
|
|
|
}
|
Competition/Round architecture: full platform rewrite (Phases 1-9)
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>
2026-02-15 23:04:15 +01:00
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Initiate a runoff: TALLYING → RUNOFF
|
|
|
|
|
*/
|
|
|
|
|
initRunoff: adminProcedure
|
|
|
|
|
.input(
|
|
|
|
|
z.object({
|
|
|
|
|
sessionId: z.string(),
|
|
|
|
|
tiedProjectIds: z.array(z.string()).min(2),
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
.mutation(async ({ ctx, input }) => {
|
|
|
|
|
const result = await initRunoff(
|
|
|
|
|
input.sessionId,
|
|
|
|
|
input.tiedProjectIds,
|
|
|
|
|
ctx.user.id,
|
|
|
|
|
ctx.prisma,
|
|
|
|
|
)
|
|
|
|
|
if (!result.success) {
|
|
|
|
|
throw new TRPCError({
|
|
|
|
|
code: 'BAD_REQUEST',
|
|
|
|
|
message: result.errors?.join('; ') ?? 'Failed to initiate runoff',
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return result
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Admin override: directly set final rankings
|
|
|
|
|
*/
|
|
|
|
|
adminDecide: adminProcedure
|
|
|
|
|
.input(
|
|
|
|
|
z.object({
|
|
|
|
|
sessionId: z.string(),
|
|
|
|
|
rankings: z.array(
|
|
|
|
|
z.object({
|
|
|
|
|
projectId: z.string(),
|
|
|
|
|
rank: z.number().int().min(1),
|
|
|
|
|
})
|
|
|
|
|
).min(1),
|
|
|
|
|
reason: z.string().min(1).max(2000),
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
.mutation(async ({ ctx, input }) => {
|
|
|
|
|
const result = await adminDecide(
|
|
|
|
|
input.sessionId,
|
|
|
|
|
input.rankings,
|
|
|
|
|
input.reason,
|
|
|
|
|
ctx.user.id,
|
|
|
|
|
ctx.prisma,
|
|
|
|
|
)
|
|
|
|
|
if (!result.success) {
|
|
|
|
|
throw new TRPCError({
|
|
|
|
|
code: 'BAD_REQUEST',
|
|
|
|
|
message: result.errors?.join('; ') ?? 'Failed to admin-decide',
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return result
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Finalize results: TALLYING → DELIB_LOCKED
|
|
|
|
|
*/
|
|
|
|
|
finalize: adminProcedure
|
|
|
|
|
.input(z.object({ sessionId: z.string() }))
|
|
|
|
|
.mutation(async ({ ctx, input }) => {
|
|
|
|
|
const result = await finalizeResults(input.sessionId, ctx.user.id, ctx.prisma)
|
|
|
|
|
if (!result.success) {
|
|
|
|
|
throw new TRPCError({
|
|
|
|
|
code: 'BAD_REQUEST',
|
|
|
|
|
message: result.errors?.join('; ') ?? 'Failed to finalize results',
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return result
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
/**
|
2026-02-17 12:33:20 +01:00
|
|
|
* Get session with votes, results, and participants.
|
|
|
|
|
* Redacts juror identities for non-admin users when session flags are off.
|
Competition/Round architecture: full platform rewrite (Phases 1-9)
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>
2026-02-15 23:04:15 +01:00
|
|
|
*/
|
|
|
|
|
getSession: protectedProcedure
|
|
|
|
|
.input(z.object({ sessionId: z.string() }))
|
|
|
|
|
.query(async ({ ctx, input }) => {
|
|
|
|
|
const session = await getSessionWithVotes(input.sessionId, ctx.prisma)
|
|
|
|
|
if (!session) {
|
|
|
|
|
throw new TRPCError({ code: 'NOT_FOUND', message: 'Session not found' })
|
|
|
|
|
}
|
2026-02-17 12:33:20 +01:00
|
|
|
|
|
|
|
|
const isAdmin = ctx.user.role === 'SUPER_ADMIN' || ctx.user.role === 'PROGRAM_ADMIN'
|
|
|
|
|
if (isAdmin) return session
|
|
|
|
|
|
|
|
|
|
// Non-admin: enforce visibility flags
|
|
|
|
|
if (!session.showCollectiveRankings) {
|
|
|
|
|
// Anonymize juror identity on votes — only show own votes with identity
|
|
|
|
|
session.votes = session.votes.map((v: any, i: number) => {
|
|
|
|
|
const isOwn = v.juryMember?.user?.id === ctx.user.id
|
|
|
|
|
if (isOwn) return v
|
|
|
|
|
return {
|
|
|
|
|
...v,
|
|
|
|
|
juryMember: {
|
|
|
|
|
...v.juryMember,
|
|
|
|
|
user: { id: `anon-${i}`, name: `Juror ${i + 1}`, email: '' },
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
// Anonymize participants
|
|
|
|
|
session.participants = session.participants.map((p: any, i: number) => {
|
|
|
|
|
const isOwn = p.user?.user?.id === ctx.user.id
|
|
|
|
|
if (isOwn) return p
|
|
|
|
|
return {
|
|
|
|
|
...p,
|
|
|
|
|
user: p.user
|
|
|
|
|
? { ...p.user, user: { id: `anon-${i}`, name: `Juror ${i + 1}`, email: '' } }
|
|
|
|
|
: p.user,
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
Competition/Round architecture: full platform rewrite (Phases 1-9)
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>
2026-02-15 23:04:15 +01:00
|
|
|
return session
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* List deliberation sessions for a competition
|
|
|
|
|
*/
|
|
|
|
|
listSessions: adminProcedure
|
|
|
|
|
.input(z.object({ competitionId: z.string() }))
|
|
|
|
|
.query(async ({ ctx, input }) => {
|
|
|
|
|
return ctx.prisma.deliberationSession.findMany({
|
|
|
|
|
where: {
|
|
|
|
|
round: { competitionId: input.competitionId },
|
|
|
|
|
},
|
|
|
|
|
include: {
|
|
|
|
|
round: { select: { id: true, name: true, roundType: true } },
|
|
|
|
|
_count: { select: { votes: true, participants: true } },
|
|
|
|
|
participants: {
|
|
|
|
|
select: { userId: true },
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
orderBy: { createdAt: 'desc' },
|
|
|
|
|
})
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Update participant status (mark absent, replace, etc.)
|
|
|
|
|
*/
|
|
|
|
|
updateParticipant: adminProcedure
|
|
|
|
|
.input(
|
|
|
|
|
z.object({
|
|
|
|
|
sessionId: z.string(),
|
|
|
|
|
userId: z.string(),
|
|
|
|
|
status: participantStatusEnum,
|
|
|
|
|
replacedById: z.string().optional(),
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
.mutation(async ({ ctx, input }) => {
|
Admin platform audit: fix bugs, harden backend, add auto-refresh, clean dead code
Phase 1 — Critical bugs:
- Fix deliberation participant selection (wire jury group query)
- Fix reports "By Round" tab (inline content instead of 404 route)
- Fix messages "Sent History" (add message.sent procedure, wire tab)
- Add missing fields to competition award form (criteriaText, maxRankedPicks)
- Wire LiveControlPanel buttons (cursor, voting, scores)
- Fix ResultLockControls empty snapshot (fetch actual data before lock)
- Fix SubmissionWindowManager losing fields on edit
Phase 2 — Backend fixes:
- Remove write-in-query from specialAward.get
- Fix award eligibility job overwriting manual shortlist overrides
- Fix filtering startJob deleting all prior results (defer cleanup to post-success)
- Tighten access control: protectedProcedure → adminProcedure on 8 procedures
- Add audit logging to deliberation mutations
- Add FINALIST/SEMIFINALIST delete guard on project.delete/bulkDelete
Phase 3 — Auto-refresh:
- Add refetchInterval to 15+ admin pages/components (10s–30s)
- Fix AI job polling: derive speed from job status for all viewers
Phase 4 — Dead code cleanup:
- Delete unused command-palette, pdf-report, admin-page-transition
- Remove dead subItems sidebar code, unused GripVertical import
- Replace redundant isGenerating state with mutation.isPending
- Add Role column to jury members table
- Remove misleading manual mentor assignment stub
Phase 5 — UX improvements:
- Fix rounds page single-competition assumption (add selector)
- Remove raw UUID fallback in deliberation config
- Fix programs page "Stage" → "Round" terminology
Phase 6 — Backend hardening:
- Complete logAudit calls (add prisma, ipAddress, userAgent)
- Batch analytics queries (fix N+1 in getCrossRoundComparison, getYearOverYear)
- Batch user.bulkCreate writes (assignments, jury memberships, intents)
- Remove any casts from deliberation service (typed PrismaClient + TransactionClient)
- Fix stale DeliberationStatus enum values blocking build
40 files changed, 1010 insertions(+), 612 deletions(-)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 08:20:13 +01:00
|
|
|
const result = await updateParticipantStatus(
|
Competition/Round architecture: full platform rewrite (Phases 1-9)
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>
2026-02-15 23:04:15 +01:00
|
|
|
input.sessionId,
|
|
|
|
|
input.userId,
|
|
|
|
|
input.status,
|
|
|
|
|
input.replacedById,
|
|
|
|
|
ctx.user.id,
|
|
|
|
|
ctx.prisma,
|
|
|
|
|
)
|
Admin platform audit: fix bugs, harden backend, add auto-refresh, clean dead code
Phase 1 — Critical bugs:
- Fix deliberation participant selection (wire jury group query)
- Fix reports "By Round" tab (inline content instead of 404 route)
- Fix messages "Sent History" (add message.sent procedure, wire tab)
- Add missing fields to competition award form (criteriaText, maxRankedPicks)
- Wire LiveControlPanel buttons (cursor, voting, scores)
- Fix ResultLockControls empty snapshot (fetch actual data before lock)
- Fix SubmissionWindowManager losing fields on edit
Phase 2 — Backend fixes:
- Remove write-in-query from specialAward.get
- Fix award eligibility job overwriting manual shortlist overrides
- Fix filtering startJob deleting all prior results (defer cleanup to post-success)
- Tighten access control: protectedProcedure → adminProcedure on 8 procedures
- Add audit logging to deliberation mutations
- Add FINALIST/SEMIFINALIST delete guard on project.delete/bulkDelete
Phase 3 — Auto-refresh:
- Add refetchInterval to 15+ admin pages/components (10s–30s)
- Fix AI job polling: derive speed from job status for all viewers
Phase 4 — Dead code cleanup:
- Delete unused command-palette, pdf-report, admin-page-transition
- Remove dead subItems sidebar code, unused GripVertical import
- Replace redundant isGenerating state with mutation.isPending
- Add Role column to jury members table
- Remove misleading manual mentor assignment stub
Phase 5 — UX improvements:
- Fix rounds page single-competition assumption (add selector)
- Remove raw UUID fallback in deliberation config
- Fix programs page "Stage" → "Round" terminology
Phase 6 — Backend hardening:
- Complete logAudit calls (add prisma, ipAddress, userAgent)
- Batch analytics queries (fix N+1 in getCrossRoundComparison, getYearOverYear)
- Batch user.bulkCreate writes (assignments, jury memberships, intents)
- Remove any casts from deliberation service (typed PrismaClient + TransactionClient)
- Fix stale DeliberationStatus enum values blocking build
40 files changed, 1010 insertions(+), 612 deletions(-)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 08:20:13 +01:00
|
|
|
|
|
|
|
|
await logAudit({
|
|
|
|
|
prisma: ctx.prisma,
|
|
|
|
|
userId: ctx.user.id,
|
|
|
|
|
action: 'UPDATE',
|
|
|
|
|
entityType: 'DeliberationParticipant',
|
|
|
|
|
entityId: input.sessionId,
|
|
|
|
|
detailsJson: {
|
|
|
|
|
sessionId: input.sessionId,
|
|
|
|
|
targetUserId: input.userId,
|
|
|
|
|
status: input.status,
|
|
|
|
|
replacedById: input.replacedById,
|
|
|
|
|
},
|
|
|
|
|
ipAddress: ctx.ip,
|
|
|
|
|
userAgent: ctx.userAgent,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return result
|
Competition/Round architecture: full platform rewrite (Phases 1-9)
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>
2026-02-15 23:04:15 +01:00
|
|
|
}),
|
|
|
|
|
})
|