← back to Norma
app/api/slack/notify/route.ts
69 lines
import { NextRequest, NextResponse } from 'next/server';
import { requireRole } from '@/lib/require-role';
import { getBrand } from '@/lib/brand';
import {
sendSlackNotification,
formatGrantNotification,
formatDraftNotification,
} from '@/lib/slack';
export async function POST(request: NextRequest) {
// Verify session token signature, not just cookie existence
const auth = requireRole(request, 'admin', 'staff');
if (auth instanceof NextResponse) return auth;
let body: { type: string; data: Record<string, unknown> };
try {
body = await request.json();
} catch {
return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 });
}
const { type, data } = body as {
type: 'grant' | 'draft' | 'custom';
data: Record<string, unknown>;
};
let payload: { text: string; blocks?: Record<string, unknown>[] };
switch (type) {
case 'grant':
payload = formatGrantNotification(
data as {
title: string;
funder?: string;
status: string;
amount_min?: number;
amount_max?: number;
},
);
break;
case 'draft':
payload = formatDraftNotification(
data as {
subject: string;
lane: string;
status: string;
lane_label?: string;
},
);
break;
case 'custom': {
const brand = await getBrand();
payload = {
text: (data.text as string) || `${brand.shortName} notification`,
blocks: data.blocks as Record<string, unknown>[] | undefined,
};
break;
}
default:
return NextResponse.json(
{ error: `Invalid notification type: ${type}. Expected: grant, draft, custom` },
{ status: 400 },
);
}
const result = await sendSlackNotification(payload);
return NextResponse.json(result);
}