← back to Norma
app/api/grants/[id]/proposals/[pid]/send/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';
import { getBrand } from '@/lib/brand';
import { getOrgId } from '@/lib/orgId';
type Params = Promise<{ id: string; pid: string }>;
/**
* POST /api/grants/[id]/proposals/[pid]/send
* Send the proposal via email (through George Gmail proxy or direct SMTP).
*/
export async function POST(request: NextRequest, { params }: { params: Params }) {
const auth = requireRole(request, 'admin', 'staff');
if (auth instanceof NextResponse) return auth;
const { pid } = await params;
try {
// Fetch proposal
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 });
}
const proposal = result.rows[0];
if (!proposal.recipient_email) {
return NextResponse.json({ error: 'Recipient email is required before sending' }, { status: 400 });
}
const orgId = auth.role === 'admin' ? getOrgId(request) : auth.orgId;
const brand = await getBrand(orgId ?? undefined);
// Try George Gmail agent first
const georgeUrl = process.env.GEORGE_URL;
const georgeAuth = process.env.GEORGE_AUTH;
let sent = false;
if (georgeUrl && georgeAuth) {
try {
const authHeader = 'Basic ' + Buffer.from(georgeAuth).toString('base64');
const georgeRes = await fetch(`${georgeUrl}/api/send`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: authHeader,
},
body: JSON.stringify({
to: proposal.recipient_email,
subject: proposal.subject,
html: proposal.body_html,
from_name: brand.name,
}),
});
if (georgeRes.ok) {
sent = true;
} else {
console.warn('[proposal/send] George agent failed:', georgeRes.status);
}
} catch (err) {
console.warn('[proposal/send] George agent unreachable:', (err as Error).message);
}
}
// Fallback: SMTP if configured
if (!sent && process.env.SMTP_HOST) {
// Nodemailer would go here — for now, flag as needing Gmail integration
return NextResponse.json({
error: 'Email sending requires Gmail integration. Use Export PDF to submit manually.',
}, { status: 503 });
}
if (!sent) {
return NextResponse.json({
error: 'Email sending is not configured yet. Gmail integration is needed. Use Export PDF instead.',
}, { status: 503 });
}
// Update proposal status
await query(
`UPDATE grant_proposals SET status = 'sent', sent_at = NOW() WHERE id = $1`,
[pid],
);
const ip = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || undefined;
await auditLog('proposal.sent', 'grant_proposal', pid, {
to: proposal.recipient_email,
subject: proposal.subject,
}, ip);
return NextResponse.json({ success: true, message: 'Proposal sent successfully' });
} catch (err) {
console.error('[proposal/send] error:', (err as Error).message);
return NextResponse.json({ error: 'Failed to send proposal' }, { status: 500 });
}
}