← back to Norma
app/api/pipeline/[id]/approve/route.ts
91 lines
import { NextRequest, NextResponse } from 'next/server';
import { query } from '@/lib/db';
import { requireRole } from '@/lib/require-role';
import { auditLog } from '@/lib/audit';
import { getOrgId } from '@/lib/orgId';
/**
* POST /api/pipeline/[id]/approve
* Approve a pipeline item for posting.
* Sets status='approved', reviewed_by, reviewed_at.
* Optionally accepts { review_notes } in body.
*/
export async function POST(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const auth = requireRole(request, 'admin', 'staff');
if (auth instanceof NextResponse) return auth;
const orgId = auth.role === 'admin' ? getOrgId(request) : auth.orgId;
try {
const { id } = await params;
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
if (!uuidRegex.test(id)) {
return NextResponse.json({ error: 'Invalid ID format' }, { status: 400 });
}
// Check current status
const existing = await query(
`SELECT id, status FROM petition_pipeline WHERE id = $1 AND ($2::text IS NULL OR org_id = $2)`,
[id, orgId]
);
if (existing.rows.length === 0) {
return NextResponse.json({ error: 'Pipeline item not found' }, { status: 404 });
}
const currentStatus = existing.rows[0].status;
if (currentStatus !== 'draft') {
return NextResponse.json(
{ error: `Cannot approve item with status '${currentStatus}'. Only draft items can be approved.` },
{ status: 409 }
);
}
// Parse optional review notes
let reviewNotes: string | null = null;
try {
const body = await request.json();
if (body.review_notes && typeof body.review_notes === 'string') {
reviewNotes = body.review_notes.trim() || null;
}
} catch {
// Body is optional for this endpoint
}
const result = await query(
`UPDATE petition_pipeline
SET status = 'approved',
reviewed_by = $1,
reviewed_at = NOW(),
review_notes = COALESCE($2, review_notes),
updated_at = NOW()
WHERE id = $3 AND ($4::text IS NULL OR org_id = $4)
RETURNING *`,
[auth.username, reviewNotes, id, orgId]
);
const item = result.rows[0];
const ip =
request.headers.get('x-forwarded-for') ||
request.headers.get('x-real-ip') ||
undefined;
await auditLog(
'pipeline.approved',
'petition_pipeline',
item.id,
{ reviewed_by: auth.username, title: item.title },
ip
);
return NextResponse.json({ item });
} catch (err) {
console.error('[api/pipeline/[id]/approve] error:', (err as Error).message);
return NextResponse.json({ error: 'Failed to approve pipeline item' }, { status: 500 });
}
}