← 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 });
  }
}