← back to Norma
app/api/grants/[id]/proposals/route.ts
96 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';
/**
* GET /api/grants/[id]/proposals
* List all proposals for a grant.
*/
export async function GET(
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;
const { id: grantId } = await params;
try {
// Verify the grant belongs to this org
if (orgId) {
const grantCheck = await query(
`SELECT id FROM grants WHERE id = $1 AND org_id = $2`,
[grantId, orgId],
);
if (grantCheck.rowCount === 0) {
return NextResponse.json({ error: 'Grant not found' }, { status: 404 });
}
}
const result = await query(
`SELECT * FROM grant_proposals WHERE grant_id = $1 ORDER BY created_at DESC`,
[grantId],
);
return NextResponse.json({ proposals: result.rows });
} catch (err) {
console.error('[api/grants/proposals] GET error:', (err as Error).message);
return NextResponse.json({ error: 'Failed to fetch proposals' }, { status: 500 });
}
}
/**
* POST /api/grants/[id]/proposals
* Create a new proposal for a grant.
*/
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;
const { id: grantId } = await params;
try {
// Verify the grant belongs to this org
if (orgId) {
const grantCheck = await query(
`SELECT id FROM grants WHERE id = $1 AND org_id = $2`,
[grantId, orgId],
);
if (grantCheck.rowCount === 0) {
return NextResponse.json({ error: 'Grant not found' }, { status: 404 });
}
}
const body = await request.json();
const result = await query(
`INSERT INTO grant_proposals (grant_id, subject, recipient_email, recipient_name, body_html, body_text, created_by)
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING *`,
[
grantId,
body.subject || 'Grant Proposal',
body.recipient_email || null,
body.recipient_name || null,
body.body_html || '',
body.body_text || null, auth.username,
],
);
const proposal = result.rows[0];
const ip = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || undefined;
await auditLog('proposal.created', 'grant_proposal', proposal.id, { grant_id: grantId }, ip);
return NextResponse.json({ proposal }, { status: 201 });
} catch (err) {
console.error('[api/grants/proposals] POST error:', (err as Error).message);
return NextResponse.json({ error: 'Failed to create proposal' }, { status: 500 });
}
}