Round detail overhaul, file requirements, project management, audit log fix
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m32s
All checks were successful
Build and Push Docker Image / build (push) Successful in 7m32s
- Redesign round detail page with 6 tabs (overview, projects, filtering, assignments, config, documents) - Add jury group assignment selector in round stats bar - Add FileRequirementsEditor component replacing SubmissionWindowManager - Add FilteringDashboard component for AI-powered project screening - Add project removal from rounds (single + bulk) with cascading to subsequent rounds - Add project add/remove UI in ProjectStatesTable with confirmation dialogs - Fix logAudit inside $transaction pattern across all 12 router files (PostgreSQL aborted-transaction state caused silent operation failures) - Fix special awards creation, deletion, status update, and winner assignment Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -33,33 +33,30 @@ export const cohortRouter = router({
|
||||
}
|
||||
}
|
||||
|
||||
const cohort = await ctx.prisma.$transaction(async (tx) => {
|
||||
const created = await tx.cohort.create({
|
||||
data: {
|
||||
roundId: input.roundId,
|
||||
name: input.name,
|
||||
votingMode: input.votingMode,
|
||||
windowOpenAt: input.windowOpenAt ?? null,
|
||||
windowCloseAt: input.windowCloseAt ?? null,
|
||||
},
|
||||
})
|
||||
const cohort = await ctx.prisma.cohort.create({
|
||||
data: {
|
||||
roundId: input.roundId,
|
||||
name: input.name,
|
||||
votingMode: input.votingMode,
|
||||
windowOpenAt: input.windowOpenAt ?? null,
|
||||
windowCloseAt: input.windowCloseAt ?? null,
|
||||
},
|
||||
})
|
||||
|
||||
await logAudit({
|
||||
prisma: tx,
|
||||
userId: ctx.user.id,
|
||||
action: 'CREATE',
|
||||
entityType: 'Cohort',
|
||||
entityId: created.id,
|
||||
detailsJson: {
|
||||
roundId: input.roundId,
|
||||
name: input.name,
|
||||
votingMode: input.votingMode,
|
||||
},
|
||||
ipAddress: ctx.ip,
|
||||
userAgent: ctx.userAgent,
|
||||
})
|
||||
|
||||
return created
|
||||
// Audit outside transaction so failures don't roll back the create
|
||||
await logAudit({
|
||||
prisma: ctx.prisma,
|
||||
userId: ctx.user.id,
|
||||
action: 'CREATE',
|
||||
entityType: 'Cohort',
|
||||
entityId: cohort.id,
|
||||
detailsJson: {
|
||||
roundId: input.roundId,
|
||||
name: input.name,
|
||||
votingMode: input.votingMode,
|
||||
},
|
||||
ipAddress: ctx.ip,
|
||||
userAgent: ctx.userAgent,
|
||||
})
|
||||
|
||||
return cohort
|
||||
@@ -157,32 +154,29 @@ export const cohortRouter = router({
|
||||
? new Date(now.getTime() + input.durationMinutes * 60 * 1000)
|
||||
: cohort.windowCloseAt
|
||||
|
||||
const updated = await ctx.prisma.$transaction(async (tx) => {
|
||||
const result = await tx.cohort.update({
|
||||
where: { id: input.cohortId },
|
||||
data: {
|
||||
isOpen: true,
|
||||
windowOpenAt: now,
|
||||
windowCloseAt: closeAt,
|
||||
},
|
||||
})
|
||||
const updated = await ctx.prisma.cohort.update({
|
||||
where: { id: input.cohortId },
|
||||
data: {
|
||||
isOpen: true,
|
||||
windowOpenAt: now,
|
||||
windowCloseAt: closeAt,
|
||||
},
|
||||
})
|
||||
|
||||
await logAudit({
|
||||
prisma: tx,
|
||||
userId: ctx.user.id,
|
||||
action: 'COHORT_VOTING_OPENED',
|
||||
entityType: 'Cohort',
|
||||
entityId: input.cohortId,
|
||||
detailsJson: {
|
||||
openedAt: now.toISOString(),
|
||||
closesAt: closeAt?.toISOString() ?? null,
|
||||
projectCount: cohort._count.projects,
|
||||
},
|
||||
ipAddress: ctx.ip,
|
||||
userAgent: ctx.userAgent,
|
||||
})
|
||||
|
||||
return result
|
||||
// Audit outside transaction so failures don't roll back the voting open
|
||||
await logAudit({
|
||||
prisma: ctx.prisma,
|
||||
userId: ctx.user.id,
|
||||
action: 'COHORT_VOTING_OPENED',
|
||||
entityType: 'Cohort',
|
||||
entityId: input.cohortId,
|
||||
detailsJson: {
|
||||
openedAt: now.toISOString(),
|
||||
closesAt: closeAt?.toISOString() ?? null,
|
||||
projectCount: cohort._count.projects,
|
||||
},
|
||||
ipAddress: ctx.ip,
|
||||
userAgent: ctx.userAgent,
|
||||
})
|
||||
|
||||
return updated
|
||||
@@ -207,30 +201,27 @@ export const cohortRouter = router({
|
||||
|
||||
const now = new Date()
|
||||
|
||||
const updated = await ctx.prisma.$transaction(async (tx) => {
|
||||
const result = await tx.cohort.update({
|
||||
where: { id: input.cohortId },
|
||||
data: {
|
||||
isOpen: false,
|
||||
windowCloseAt: now,
|
||||
},
|
||||
})
|
||||
const updated = await ctx.prisma.cohort.update({
|
||||
where: { id: input.cohortId },
|
||||
data: {
|
||||
isOpen: false,
|
||||
windowCloseAt: now,
|
||||
},
|
||||
})
|
||||
|
||||
await logAudit({
|
||||
prisma: tx,
|
||||
userId: ctx.user.id,
|
||||
action: 'COHORT_VOTING_CLOSED',
|
||||
entityType: 'Cohort',
|
||||
entityId: input.cohortId,
|
||||
detailsJson: {
|
||||
closedAt: now.toISOString(),
|
||||
wasOpenSince: cohort.windowOpenAt?.toISOString(),
|
||||
},
|
||||
ipAddress: ctx.ip,
|
||||
userAgent: ctx.userAgent,
|
||||
})
|
||||
|
||||
return result
|
||||
// Audit outside transaction so failures don't roll back the voting close
|
||||
await logAudit({
|
||||
prisma: ctx.prisma,
|
||||
userId: ctx.user.id,
|
||||
action: 'COHORT_VOTING_CLOSED',
|
||||
entityType: 'Cohort',
|
||||
entityId: input.cohortId,
|
||||
detailsJson: {
|
||||
closedAt: now.toISOString(),
|
||||
wasOpenSince: cohort.windowOpenAt?.toISOString(),
|
||||
},
|
||||
ipAddress: ctx.ip,
|
||||
userAgent: ctx.userAgent,
|
||||
})
|
||||
|
||||
return updated
|
||||
|
||||
Reference in New Issue
Block a user