← back to Norma
app/api/grants/[id]/proposals/[pid]/route.ts
99 lines
import { NextRequest, NextResponse } from 'next/server';
import { query } from '@/lib/db';
import { requireRole } from '@/lib/require-role';
import { auditLog } from '@/lib/audit';
type Params = Promise<{ id: string; pid: string }>;
/**
* GET /api/grants/[id]/proposals/[pid]
*/
export async function GET(request: NextRequest, { params }: { params: Params }) {
const auth = requireRole(request, 'admin', 'staff');
if (auth instanceof NextResponse) return auth;
const { pid } = await params;
try {
const result = await query(`SELECT * FROM grant_proposals WHERE id = $1`, [pid]);
if (result.rows.length === 0) {
return NextResponse.json({ error: 'Proposal not found' }, { status: 404 });
}
return NextResponse.json({ proposal: result.rows[0] });
} catch (err) {
console.error('[api/grants/proposals/pid] GET error:', (err as Error).message);
return NextResponse.json({ error: 'Failed to fetch proposal' }, { status: 500 });
}
}
/**
* PATCH /api/grants/[id]/proposals/[pid]
* Update a proposal (subject, body, recipient, status).
*/
export async function PATCH(request: NextRequest, { params }: { params: Params }) {
const auth = requireRole(request, 'admin', 'staff');
if (auth instanceof NextResponse) return auth;
const { pid } = await params;
try {
const body = await request.json();
const sets: string[] = [];
const values: unknown[] = [];
let idx = 1;
const allowed = ['subject', 'recipient_email', 'recipient_name', 'body_html', 'body_text', 'status'];
for (const key of allowed) {
if (body[key] !== undefined) {
sets.push(`${key} = $${idx}`);
values.push(body[key]);
idx++;
}
}
if (sets.length === 0) {
return NextResponse.json({ error: 'No fields to update' }, { status: 400 });
}
values.push(pid);
const result = await query(
`UPDATE grant_proposals SET ${sets.join(', ')} WHERE id = $${idx} RETURNING *`,
values,
);
if (result.rows.length === 0) {
return NextResponse.json({ error: 'Proposal not found' }, { status: 404 });
}
return NextResponse.json({ proposal: result.rows[0] });
} catch (err) {
console.error('[api/grants/proposals/pid] PATCH error:', (err as Error).message);
return NextResponse.json({ error: 'Failed to update proposal' }, { status: 500 });
}
}
/**
* DELETE /api/grants/[id]/proposals/[pid]
*/
export async function DELETE(request: NextRequest, { params }: { params: Params }) {
const auth = requireRole(request, 'admin', 'staff');
if (auth instanceof NextResponse) return auth;
const { pid } = await params;
try {
const result = await query(`DELETE FROM grant_proposals WHERE id = $1 RETURNING id`, [pid]);
if (result.rows.length === 0) {
return NextResponse.json({ error: 'Proposal not found' }, { status: 404 });
}
const ip = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || undefined;
await auditLog('proposal.deleted', 'grant_proposal', pid, {}, ip);
return NextResponse.json({ success: true });
} catch (err) {
console.error('[api/grants/proposals/pid] DELETE error:', (err as Error).message);
return NextResponse.json({ error: 'Failed to delete proposal' }, { status: 500 });
}
}