fix(audit): wrap admin + capa mutations with their audit writes in single transactions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-24 06:44:05 +08:00
co-authored by Claude Sonnet 4.6
parent 8acca41ca1
commit 9c67014bb9
5 changed files with 145 additions and 114 deletions
+16 -14
View File
@@ -2,7 +2,7 @@ export const dynamic = 'force-dynamic'
import { NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth/get-session'
import { withUser, asAdmin } from '@/lib/db/with-user'
import { withUser } from '@/lib/db/with-user'
import { writeAuditLog } from '@/lib/db/audit'
import { capaActions, incidents, users } from '@/lib/db/schema'
import { aliasedTable, eq } from 'drizzle-orm'
@@ -124,16 +124,6 @@ export async function PATCH(
if ('owner_notes' in body && allowed.includes('owner_notes')) updateSet.ownerNotes = body.owner_notes
if (body.status === 'pending_verification') updateSet.completedAt = new Date()
let oldNotes: string | null = null
if ('owner_notes' in body && allowed.includes('owner_notes')) {
const [current] = await withUser(session.sub, async tx =>
tx.select({ ownerNotes: capaActions.ownerNotes }).from(capaActions).where(eq(capaActions.id, id)).limit(1)
)
oldNotes = current?.ownerNotes ?? null
}
await asAdmin(db => db.update(capaActions).set(updateSet).where(eq(capaActions.id, id)))
// Build audit new/old value from original snake_case body keys for consistency
const auditNew: Record<string, unknown> = {}
for (const key of allowed) {
@@ -141,8 +131,20 @@ export async function PATCH(
}
if (body.status === 'pending_verification') auditNew.completed_at = updateSet.completedAt?.toISOString()
await withUser(session.sub, async tx =>
writeAuditLog(
await withUser(session.sub, async tx => {
let oldNotes: string | null = null
if ('owner_notes' in body && allowed.includes('owner_notes')) {
const [current] = await tx
.select({ ownerNotes: capaActions.ownerNotes })
.from(capaActions)
.where(eq(capaActions.id, id))
.limit(1)
oldNotes = current?.ownerNotes ?? null
}
await tx.update(capaActions).set(updateSet).where(eq(capaActions.id, id))
await writeAuditLog(
tx,
'capa_actions',
id,
@@ -150,7 +152,7 @@ export async function PATCH(
auditNew,
oldNotes !== null ? { owner_notes: oldNotes } : undefined,
)
)
})
return NextResponse.json({ ok: true })
}