Admin platform audit: fix bugs, harden backend, add auto-refresh, clean dead code
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m23s
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m23s
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>
This commit is contained in:
@@ -66,9 +66,9 @@ export async function createSession(
|
||||
showPriorJuryData?: boolean
|
||||
participantUserIds: string[] // JuryGroupMember IDs
|
||||
},
|
||||
prisma: PrismaClient | any,
|
||||
prisma: PrismaClient,
|
||||
) {
|
||||
return prisma.$transaction(async (tx: any) => {
|
||||
return prisma.$transaction(async (tx: Prisma.TransactionClient) => {
|
||||
const session = await tx.deliberationSession.create({
|
||||
data: {
|
||||
competitionId: params.competitionId,
|
||||
@@ -120,7 +120,7 @@ export async function createSession(
|
||||
export async function openVoting(
|
||||
sessionId: string,
|
||||
actorId: string,
|
||||
prisma: PrismaClient | any,
|
||||
prisma: PrismaClient,
|
||||
): Promise<SessionTransitionResult> {
|
||||
return transitionSession(sessionId, 'DELIB_OPEN', 'VOTING', actorId, prisma)
|
||||
}
|
||||
@@ -132,7 +132,7 @@ export async function openVoting(
|
||||
export async function closeVoting(
|
||||
sessionId: string,
|
||||
actorId: string,
|
||||
prisma: PrismaClient | any,
|
||||
prisma: PrismaClient,
|
||||
): Promise<SessionTransitionResult> {
|
||||
return transitionSession(sessionId, 'VOTING', 'TALLYING', actorId, prisma)
|
||||
}
|
||||
@@ -152,7 +152,7 @@ export async function submitVote(
|
||||
isWinnerPick?: boolean
|
||||
runoffRound?: number
|
||||
},
|
||||
prisma: PrismaClient | any,
|
||||
prisma: PrismaClient,
|
||||
) {
|
||||
const session = await prisma.deliberationSession.findUnique({
|
||||
where: { id: params.sessionId },
|
||||
@@ -219,7 +219,7 @@ export async function submitVote(
|
||||
*/
|
||||
export async function aggregateVotes(
|
||||
sessionId: string,
|
||||
prisma: PrismaClient | any,
|
||||
prisma: PrismaClient,
|
||||
): Promise<AggregationResult> {
|
||||
const session = await prisma.deliberationSession.findUnique({
|
||||
where: { id: sessionId },
|
||||
@@ -313,7 +313,7 @@ export async function initRunoff(
|
||||
sessionId: string,
|
||||
tiedProjectIds: string[],
|
||||
actorId: string,
|
||||
prisma: PrismaClient | any,
|
||||
prisma: PrismaClient,
|
||||
): Promise<SessionTransitionResult> {
|
||||
const session = await prisma.deliberationSession.findUnique({
|
||||
where: { id: sessionId },
|
||||
@@ -339,7 +339,7 @@ export async function initRunoff(
|
||||
return { success: false, errors: [`Maximum runoff rounds (${MAX_RUNOFF_ROUNDS}) exceeded`] }
|
||||
}
|
||||
|
||||
return prisma.$transaction(async (tx: any) => {
|
||||
return prisma.$transaction(async (tx: Prisma.TransactionClient) => {
|
||||
const updated = await tx.deliberationSession.update({
|
||||
where: { id: sessionId },
|
||||
data: { status: 'RUNOFF' },
|
||||
@@ -374,7 +374,7 @@ export async function adminDecide(
|
||||
rankings: Array<{ projectId: string; rank: number }>,
|
||||
reason: string,
|
||||
actorId: string,
|
||||
prisma: PrismaClient | any,
|
||||
prisma: PrismaClient,
|
||||
): Promise<SessionTransitionResult> {
|
||||
const session = await prisma.deliberationSession.findUnique({
|
||||
where: { id: sessionId },
|
||||
@@ -388,7 +388,7 @@ export async function adminDecide(
|
||||
return { success: false, errors: [`Cannot admin-decide: status is ${session.status}`] }
|
||||
}
|
||||
|
||||
return prisma.$transaction(async (tx: any) => {
|
||||
return prisma.$transaction(async (tx: Prisma.TransactionClient) => {
|
||||
const updated = await tx.deliberationSession.update({
|
||||
where: { id: sessionId },
|
||||
data: {
|
||||
@@ -431,7 +431,7 @@ export async function adminDecide(
|
||||
export async function finalizeResults(
|
||||
sessionId: string,
|
||||
actorId: string,
|
||||
prisma: PrismaClient | any,
|
||||
prisma: PrismaClient,
|
||||
): Promise<SessionTransitionResult> {
|
||||
const session = await prisma.deliberationSession.findUnique({
|
||||
where: { id: sessionId },
|
||||
@@ -470,7 +470,7 @@ export async function finalizeResults(
|
||||
}))
|
||||
}
|
||||
|
||||
return prisma.$transaction(async (tx: any) => {
|
||||
return prisma.$transaction(async (tx: Prisma.TransactionClient) => {
|
||||
// Create result records
|
||||
for (const ranking of finalRankings) {
|
||||
await tx.deliberationResult.upsert({
|
||||
@@ -487,7 +487,7 @@ export async function finalizeResults(
|
||||
voteCount: ranking.voteCount,
|
||||
isAdminOverridden: ranking.isAdminOverridden,
|
||||
overrideReason: ranking.isAdminOverridden
|
||||
? (session.adminOverrideResult as any)?.reason ?? null
|
||||
? (session.adminOverrideResult as Record<string, unknown> | null)?.reason as string ?? null
|
||||
: null,
|
||||
},
|
||||
update: {
|
||||
@@ -549,11 +549,11 @@ export async function updateParticipantStatus(
|
||||
status: DeliberationParticipantStatus,
|
||||
replacedById?: string,
|
||||
actorId?: string,
|
||||
prisma?: PrismaClient | any,
|
||||
prisma?: PrismaClient,
|
||||
) {
|
||||
const db = prisma ?? (await import('@/lib/prisma')).prisma
|
||||
|
||||
return db.$transaction(async (tx: any) => {
|
||||
return db.$transaction(async (tx: Prisma.TransactionClient) => {
|
||||
const updated = await tx.deliberationParticipant.update({
|
||||
where: { sessionId_userId: { sessionId, userId } },
|
||||
data: {
|
||||
@@ -601,7 +601,7 @@ export async function updateParticipantStatus(
|
||||
*/
|
||||
export async function getSessionWithVotes(
|
||||
sessionId: string,
|
||||
prisma: PrismaClient | any,
|
||||
prisma: PrismaClient,
|
||||
) {
|
||||
return prisma.deliberationSession.findUnique({
|
||||
where: { id: sessionId },
|
||||
@@ -645,7 +645,7 @@ async function transitionSession(
|
||||
expectedStatus: DeliberationStatus,
|
||||
newStatus: DeliberationStatus,
|
||||
actorId: string,
|
||||
prisma: PrismaClient | any,
|
||||
prisma: PrismaClient,
|
||||
): Promise<SessionTransitionResult> {
|
||||
try {
|
||||
const session = await prisma.deliberationSession.findUnique({
|
||||
@@ -671,7 +671,7 @@ async function transitionSession(
|
||||
}
|
||||
}
|
||||
|
||||
const updated = await prisma.$transaction(async (tx: any) => {
|
||||
const updated = await prisma.$transaction(async (tx: Prisma.TransactionClient) => {
|
||||
const result = await tx.deliberationSession.update({
|
||||
where: { id: sessionId },
|
||||
data: { status: newStatus },
|
||||
|
||||
Reference in New Issue
Block a user