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:
@@ -36,26 +36,23 @@ export const juryGroupRouter = router({
|
||||
|
||||
const { defaultCategoryQuotas, ...rest } = input
|
||||
|
||||
const juryGroup = await ctx.prisma.$transaction(async (tx) => {
|
||||
const created = await tx.juryGroup.create({
|
||||
data: {
|
||||
...rest,
|
||||
defaultCategoryQuotas: defaultCategoryQuotas ?? undefined,
|
||||
},
|
||||
})
|
||||
const juryGroup = await ctx.prisma.juryGroup.create({
|
||||
data: {
|
||||
...rest,
|
||||
defaultCategoryQuotas: defaultCategoryQuotas ?? undefined,
|
||||
},
|
||||
})
|
||||
|
||||
await logAudit({
|
||||
prisma: tx,
|
||||
userId: ctx.user.id,
|
||||
action: 'CREATE',
|
||||
entityType: 'JuryGroup',
|
||||
entityId: created.id,
|
||||
detailsJson: { name: input.name, competitionId: input.competitionId },
|
||||
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: 'JuryGroup',
|
||||
entityId: juryGroup.id,
|
||||
detailsJson: { name: input.name, competitionId: input.competitionId },
|
||||
ipAddress: ctx.ip,
|
||||
userAgent: ctx.userAgent,
|
||||
})
|
||||
|
||||
return juryGroup
|
||||
@@ -187,39 +184,36 @@ export const juryGroupRouter = router({
|
||||
})
|
||||
}
|
||||
|
||||
const member = await ctx.prisma.$transaction(async (tx) => {
|
||||
const created = await tx.juryGroupMember.create({
|
||||
data: {
|
||||
juryGroupId: input.juryGroupId,
|
||||
userId: input.userId,
|
||||
role: input.role,
|
||||
maxAssignmentsOverride: input.maxAssignmentsOverride ?? undefined,
|
||||
capModeOverride: input.capModeOverride ?? undefined,
|
||||
categoryQuotasOverride: input.categoryQuotasOverride ?? undefined,
|
||||
preferredStartupRatio: input.preferredStartupRatio ?? undefined,
|
||||
availabilityNotes: input.availabilityNotes ?? undefined,
|
||||
},
|
||||
include: {
|
||||
user: { select: { id: true, name: true, email: true, role: true } },
|
||||
},
|
||||
})
|
||||
const member = await ctx.prisma.juryGroupMember.create({
|
||||
data: {
|
||||
juryGroupId: input.juryGroupId,
|
||||
userId: input.userId,
|
||||
role: input.role,
|
||||
maxAssignmentsOverride: input.maxAssignmentsOverride ?? undefined,
|
||||
capModeOverride: input.capModeOverride ?? undefined,
|
||||
categoryQuotasOverride: input.categoryQuotasOverride ?? undefined,
|
||||
preferredStartupRatio: input.preferredStartupRatio ?? undefined,
|
||||
availabilityNotes: input.availabilityNotes ?? undefined,
|
||||
},
|
||||
include: {
|
||||
user: { select: { id: true, name: true, email: true, role: true } },
|
||||
},
|
||||
})
|
||||
|
||||
await logAudit({
|
||||
prisma: tx,
|
||||
userId: ctx.user.id,
|
||||
action: 'CREATE',
|
||||
entityType: 'JuryGroupMember',
|
||||
entityId: created.id,
|
||||
detailsJson: {
|
||||
juryGroupId: input.juryGroupId,
|
||||
addedUserId: input.userId,
|
||||
role: input.role,
|
||||
},
|
||||
ipAddress: ctx.ip,
|
||||
userAgent: ctx.userAgent,
|
||||
})
|
||||
|
||||
return created
|
||||
// Audit outside transaction so failures don't roll back the member add
|
||||
await logAudit({
|
||||
prisma: ctx.prisma,
|
||||
userId: ctx.user.id,
|
||||
action: 'CREATE',
|
||||
entityType: 'JuryGroupMember',
|
||||
entityId: member.id,
|
||||
detailsJson: {
|
||||
juryGroupId: input.juryGroupId,
|
||||
addedUserId: input.userId,
|
||||
role: input.role,
|
||||
},
|
||||
ipAddress: ctx.ip,
|
||||
userAgent: ctx.userAgent,
|
||||
})
|
||||
|
||||
return member
|
||||
@@ -231,31 +225,28 @@ export const juryGroupRouter = router({
|
||||
removeMember: adminProcedure
|
||||
.input(z.object({ id: z.string() }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const member = await ctx.prisma.$transaction(async (tx) => {
|
||||
const existing = await tx.juryGroupMember.findUniqueOrThrow({
|
||||
where: { id: input.id },
|
||||
})
|
||||
|
||||
await tx.juryGroupMember.delete({ where: { id: input.id } })
|
||||
|
||||
await logAudit({
|
||||
prisma: tx,
|
||||
userId: ctx.user.id,
|
||||
action: 'DELETE',
|
||||
entityType: 'JuryGroupMember',
|
||||
entityId: input.id,
|
||||
detailsJson: {
|
||||
juryGroupId: existing.juryGroupId,
|
||||
removedUserId: existing.userId,
|
||||
},
|
||||
ipAddress: ctx.ip,
|
||||
userAgent: ctx.userAgent,
|
||||
})
|
||||
|
||||
return existing
|
||||
const existing = await ctx.prisma.juryGroupMember.findUniqueOrThrow({
|
||||
where: { id: input.id },
|
||||
})
|
||||
|
||||
return member
|
||||
await ctx.prisma.juryGroupMember.delete({ where: { id: input.id } })
|
||||
|
||||
// Audit outside transaction so failures don't roll back the member removal
|
||||
await logAudit({
|
||||
prisma: ctx.prisma,
|
||||
userId: ctx.user.id,
|
||||
action: 'DELETE',
|
||||
entityType: 'JuryGroupMember',
|
||||
entityId: input.id,
|
||||
detailsJson: {
|
||||
juryGroupId: existing.juryGroupId,
|
||||
removedUserId: existing.userId,
|
||||
},
|
||||
ipAddress: ctx.ip,
|
||||
userAgent: ctx.userAgent,
|
||||
})
|
||||
|
||||
return existing
|
||||
}),
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user