Platform review round 2: audit logging migration, nav unification, DB indexes, and UI polish
- Migrate ~41 inline audit log calls to shared logAudit() utility across all routers - Add transaction-aware prisma parameter to logAudit() for atomic operations - Unify jury/mentor/observer navigation into shared RoleNav component - Add composite DB indexes (Evaluation, GracePeriod, AuditLog) for query performance - Fix profile page: consolidate dual save buttons, proper useEffect initialization - Enhance auth error page with MOPC branding and navigation - Improve observer dashboard with prominent read-only badge - Fix DI-3: fetch projects before bulk status update for accurate notifications - Remove unused aiBoost field from smart-assignment scoring - Add shared image-upload utility and structured logger module Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -97,26 +97,32 @@ export const specialAwardRouter = router({
|
||||
_max: { sortOrder: true },
|
||||
})
|
||||
|
||||
const award = await ctx.prisma.specialAward.create({
|
||||
data: {
|
||||
programId: input.programId,
|
||||
name: input.name,
|
||||
description: input.description,
|
||||
criteriaText: input.criteriaText,
|
||||
useAiEligibility: input.useAiEligibility ?? true,
|
||||
scoringMode: input.scoringMode,
|
||||
maxRankedPicks: input.maxRankedPicks,
|
||||
autoTagRulesJson: input.autoTagRulesJson as Prisma.InputJsonValue ?? undefined,
|
||||
sortOrder: (maxOrder._max.sortOrder || 0) + 1,
|
||||
},
|
||||
})
|
||||
const award = await ctx.prisma.$transaction(async (tx) => {
|
||||
const created = await tx.specialAward.create({
|
||||
data: {
|
||||
programId: input.programId,
|
||||
name: input.name,
|
||||
description: input.description,
|
||||
criteriaText: input.criteriaText,
|
||||
useAiEligibility: input.useAiEligibility ?? true,
|
||||
scoringMode: input.scoringMode,
|
||||
maxRankedPicks: input.maxRankedPicks,
|
||||
autoTagRulesJson: input.autoTagRulesJson as Prisma.InputJsonValue ?? undefined,
|
||||
sortOrder: (maxOrder._max.sortOrder || 0) + 1,
|
||||
},
|
||||
})
|
||||
|
||||
await logAudit({
|
||||
userId: ctx.user.id,
|
||||
action: 'CREATE',
|
||||
entityType: 'SpecialAward',
|
||||
entityId: award.id,
|
||||
detailsJson: { name: input.name, scoringMode: input.scoringMode },
|
||||
await tx.auditLog.create({
|
||||
data: {
|
||||
userId: ctx.user.id,
|
||||
action: 'CREATE',
|
||||
entityType: 'SpecialAward',
|
||||
entityId: created.id,
|
||||
detailsJson: { name: input.name, scoringMode: input.scoringMode } as Prisma.InputJsonValue,
|
||||
},
|
||||
})
|
||||
|
||||
return created
|
||||
})
|
||||
|
||||
return award
|
||||
@@ -166,13 +172,17 @@ export const specialAwardRouter = router({
|
||||
delete: adminProcedure
|
||||
.input(z.object({ id: z.string() }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
await ctx.prisma.specialAward.delete({ where: { id: input.id } })
|
||||
await ctx.prisma.$transaction(async (tx) => {
|
||||
await tx.auditLog.create({
|
||||
data: {
|
||||
userId: ctx.user.id,
|
||||
action: 'DELETE',
|
||||
entityType: 'SpecialAward',
|
||||
entityId: input.id,
|
||||
},
|
||||
})
|
||||
|
||||
await logAudit({
|
||||
userId: ctx.user.id,
|
||||
action: 'DELETE',
|
||||
entityType: 'SpecialAward',
|
||||
entityId: input.id,
|
||||
await tx.specialAward.delete({ where: { id: input.id } })
|
||||
})
|
||||
}),
|
||||
|
||||
@@ -216,25 +226,31 @@ export const specialAwardRouter = router({
|
||||
}
|
||||
}
|
||||
|
||||
const award = await ctx.prisma.specialAward.update({
|
||||
where: { id: input.id },
|
||||
data: updateData,
|
||||
})
|
||||
const award = await ctx.prisma.$transaction(async (tx) => {
|
||||
const updated = await tx.specialAward.update({
|
||||
where: { id: input.id },
|
||||
data: updateData,
|
||||
})
|
||||
|
||||
await logAudit({
|
||||
userId: ctx.user.id,
|
||||
action: 'UPDATE_STATUS',
|
||||
entityType: 'SpecialAward',
|
||||
entityId: input.id,
|
||||
detailsJson: {
|
||||
previousStatus: current.status,
|
||||
newStatus: input.status,
|
||||
...(votingStartAtUpdated && {
|
||||
votingStartAtUpdated: true,
|
||||
previousVotingStartAt: current.votingStartAt,
|
||||
newVotingStartAt: now,
|
||||
}),
|
||||
},
|
||||
await tx.auditLog.create({
|
||||
data: {
|
||||
userId: ctx.user.id,
|
||||
action: 'UPDATE_STATUS',
|
||||
entityType: 'SpecialAward',
|
||||
entityId: input.id,
|
||||
detailsJson: {
|
||||
previousStatus: current.status,
|
||||
newStatus: input.status,
|
||||
...(votingStartAtUpdated && {
|
||||
votingStartAtUpdated: true,
|
||||
previousVotingStartAt: current.votingStartAt,
|
||||
newVotingStartAt: now,
|
||||
}),
|
||||
} as Prisma.InputJsonValue,
|
||||
},
|
||||
})
|
||||
|
||||
return updated
|
||||
})
|
||||
|
||||
return award
|
||||
@@ -780,26 +796,32 @@ export const specialAwardRouter = router({
|
||||
select: { winnerProjectId: true },
|
||||
})
|
||||
|
||||
const award = await ctx.prisma.specialAward.update({
|
||||
where: { id: input.awardId },
|
||||
data: {
|
||||
winnerProjectId: input.projectId,
|
||||
winnerOverridden: input.overridden,
|
||||
winnerOverriddenBy: input.overridden ? ctx.user.id : null,
|
||||
},
|
||||
})
|
||||
const award = await ctx.prisma.$transaction(async (tx) => {
|
||||
const updated = await tx.specialAward.update({
|
||||
where: { id: input.awardId },
|
||||
data: {
|
||||
winnerProjectId: input.projectId,
|
||||
winnerOverridden: input.overridden,
|
||||
winnerOverriddenBy: input.overridden ? ctx.user.id : null,
|
||||
},
|
||||
})
|
||||
|
||||
await logAudit({
|
||||
userId: ctx.user.id,
|
||||
action: 'UPDATE',
|
||||
entityType: 'SpecialAward',
|
||||
entityId: input.awardId,
|
||||
detailsJson: {
|
||||
action: 'SET_AWARD_WINNER',
|
||||
previousWinner: previous.winnerProjectId,
|
||||
newWinner: input.projectId,
|
||||
overridden: input.overridden,
|
||||
},
|
||||
await tx.auditLog.create({
|
||||
data: {
|
||||
userId: ctx.user.id,
|
||||
action: 'UPDATE',
|
||||
entityType: 'SpecialAward',
|
||||
entityId: input.awardId,
|
||||
detailsJson: {
|
||||
action: 'SET_AWARD_WINNER',
|
||||
previousWinner: previous.winnerProjectId,
|
||||
newWinner: input.projectId,
|
||||
overridden: input.overridden,
|
||||
} as Prisma.InputJsonValue,
|
||||
},
|
||||
})
|
||||
|
||||
return updated
|
||||
})
|
||||
|
||||
return award
|
||||
|
||||
Reference in New Issue
Block a user