export const dynamic = 'force-dynamic' import { NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth/get-session' 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' export async function GET( _: NextRequest, { params }: { params: Promise<{ id: string }> } ) { const { id } = await params const session = await getSession() if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) const ownerAlias = aliasedTable(users, 'owner') const [row] = await withUser(session.sub, async tx => tx .select({ id: capaActions.id, incidentId: capaActions.incidentId, rootCauseRef: capaActions.rootCauseRef, description: capaActions.description, ownerUserId: capaActions.ownerUserId, department: capaActions.department, dueDate: capaActions.dueDate, priority: capaActions.priority, status: capaActions.status, completedAt: capaActions.completedAt, verifiedBy: capaActions.verifiedBy, verifiedAt: capaActions.verifiedAt, createdAt: capaActions.createdAt, ownerNotes: capaActions.ownerNotes, incidentReferenceNo: incidents.referenceNo, incidentType: incidents.incidentType, ownerName: ownerAlias.name, ownerEmail: ownerAlias.email, }) .from(capaActions) .leftJoin(ownerAlias, eq(capaActions.ownerUserId, ownerAlias.id)) .leftJoin(incidents, eq(capaActions.incidentId, incidents.id)) .where(eq(capaActions.id, id)) .limit(1) ) if (!row) return NextResponse.json({ error: 'Not found' }, { status: 404 }) const role = session.role const isOwner = row.ownerUserId === session.sub const canRead = ['hse', 'admin', 'supervisor'].includes(role) || isOwner if (!canRead) return NextResponse.json({ error: 'Forbidden' }, { status: 403 }) const ownerField = ['hse', 'admin'].includes(role) ? { name: row.ownerName, email: row.ownerEmail } : { name: row.ownerName ?? '' } const result = { id: row.id, incident_id: row.incidentId, root_cause_ref: row.rootCauseRef, description: row.description, owner_user_id: row.ownerUserId, department: row.department, due_date: row.dueDate, priority: row.priority, status: row.status, completed_at: row.completedAt, verified_by: row.verifiedBy, verified_at: row.verifiedAt, created_at: row.createdAt, owner_notes: row.ownerNotes, incidents: { reference_no: row.incidentReferenceNo, incident_type: row.incidentType }, owner: ownerField, } return NextResponse.json(result) } export async function PATCH( request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { const { id } = await params const session = await getSession() if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) const role = session.role const [capa] = await withUser(session.sub, async tx => tx.select({ ownerUserId: capaActions.ownerUserId, status: capaActions.status }) .from(capaActions).where(eq(capaActions.id, id)).limit(1) ) const isOwner = capa?.ownerUserId === session.sub const canEdit = ['hse', 'admin'].includes(role) || isOwner if (!canEdit) return NextResponse.json({ error: 'Forbidden' }, { status: 403 }) const terminalStatuses = ['verified', 'closed'] if (terminalStatuses.includes(capa?.status ?? '') && !['hse', 'admin'].includes(role)) { return NextResponse.json({ error: 'Forbidden' }, { status: 403 }) } const body = await request.json() const privilegedStatuses = ['verified', 'reopened', 'closed'] if (body.status && privilegedStatuses.includes(body.status) && !['hse', 'admin'].includes(role)) { return NextResponse.json({ error: 'Forbidden' }, { status: 403 }) } const allowed = ['hse', 'admin'].includes(role) ? ['description', 'due_date', 'priority', 'status', 'department', 'root_cause_ref', 'owner_notes'] : ['status', 'owner_notes'] const updateSet: Partial = {} if ('description' in body && allowed.includes('description')) updateSet.description = body.description if ('due_date' in body && allowed.includes('due_date')) updateSet.dueDate = body.due_date if ('priority' in body && allowed.includes('priority')) updateSet.priority = body.priority if ('status' in body && allowed.includes('status')) updateSet.status = body.status if ('department' in body && allowed.includes('department')) updateSet.department = body.department if ('root_cause_ref' in body && allowed.includes('root_cause_ref')) updateSet.rootCauseRef = body.root_cause_ref if ('owner_notes' in body && allowed.includes('owner_notes')) updateSet.ownerNotes = body.owner_notes if (body.status === 'pending_verification') updateSet.completedAt = new Date() // Build audit new/old value from original snake_case body keys for consistency const auditNew: Record = {} for (const key of allowed) { if (key in body) auditNew[key] = body[key] } if (body.status === 'pending_verification') auditNew.completed_at = updateSet.completedAt?.toISOString() 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, 'updated', auditNew, oldNotes !== null ? { owner_notes: oldNotes } : undefined, ) }) return NextResponse.json({ ok: true }) }