import { type NextRequest, NextResponse } from 'next/server'; import { requireAdminApi } from '@/lib/auth/admin-guard'; import { getAuditList } from '@/lib/admin/list'; import { logAdminAction } from '@/lib/auth/audit'; import { toCsv, EXPORT_MAX_ROWS } from '@/lib/admin/csv'; export const runtime = 'nodejs'; export const dynamic = 'force-dynamic'; export async function GET(req: NextRequest) { const auth = await requireAdminApi(); if (!auth.ok) return auth.response; const url = new URL(req.url); const action = url.searchParams.get('action') ?? ''; const targetType = url.searchParams.get('target_type') ?? ''; const sort = url.searchParams.get('sort'); const order = url.searchParams.get('order'); try { // Respect action/target_type filters + sort, full set (capped). Audit // details already exclude secrets (no tokens are ever logged). const { entries } = await getAuditList({ page: 1, perPage: EXPORT_MAX_ROWS, action, targetType, sort, order, }); const header = [ 'created_at', 'actor_email', 'action', 'target_type', 'target_id', 'details', ]; const rows = entries.map((e) => [ e.created_at, e.actor_email ?? '', e.action, e.target_type ?? '', e.target_id ?? '', JSON.stringify(e.details ?? {}), ]); const csv = toCsv(header, rows); await logAdminAction(auth.user, { action: 'audit.export', target_type: 'audit', details: { count: rows.length, capped: rows.length >= EXPORT_MAX_ROWS }, }); return new NextResponse(csv, { status: 200, headers: { 'Content-Type': 'text/csv; charset=utf-8', 'Content-Disposition': 'attachment; filename="audit.csv"', 'Cache-Control': 'no-store', Pragma: 'no-cache', }, }); } catch (e) { console.error('admin audit export failed', e); return NextResponse.json( { error: 'internal error' }, { status: 500, headers: { 'Cache-Control': 'no-store' } }, ); } }