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