← back to Norma

app/api/ai/generate/route.ts

72 lines

/**
 * POST /api/ai/generate
 * Server-side proxy for Gemini AI text generation.
 * Keeps the API key on the server -- never exposed to client-side code.
 *
 * Request body:
 *   { prompt: string, temperature?: number, maxOutputTokens?: number }
 *
 * Response:
 *   { text: string }
 */

import { NextRequest, NextResponse } from 'next/server';
import { requireRole } from '@/lib/require-role';
import { checkRateLimit } from '@/lib/rate-limit';

const GEMINI_API_KEY = process.env.GEMINI_API_KEY;
const GEMINI_URL = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent`;

export async function POST(request: NextRequest) {
  const auth = requireRole(request, 'admin', 'staff');
  if (auth instanceof NextResponse) return auth;

  // Rate limit: 10 AI generations per minute per user
  const rl = checkRateLimit(request, 10, 60_000);
  if (rl.limited) {
    return NextResponse.json(
      { error: 'Too many AI requests. Try again later.' },
      { status: 429 },
    );
  }

  try {
    const body = await request.json();
    const { prompt, temperature = 0.7, maxOutputTokens = 4096 } = body;

    if (!prompt || typeof prompt !== 'string') {
      return NextResponse.json({ error: 'prompt is required' }, { status: 400 });
    }

    if (!GEMINI_API_KEY) {
      return NextResponse.json({ error: 'Gemini API key not configured' }, { status: 500 });
    }

    const res = await fetch(`${GEMINI_URL}?key=${GEMINI_API_KEY}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        contents: [{ parts: [{ text: prompt }] }],
        generationConfig: { temperature, maxOutputTokens },
      }),
    });

    if (!res.ok) {
      const errText = await res.text();
      console.error('[api/ai/generate] Gemini error:', res.status, errText);
      return NextResponse.json(
        { error: `AI generation failed (${res.status})` },
        { status: 502 },
      );
    }

    const data = await res.json();
    const text = data?.candidates?.[0]?.content?.parts?.[0]?.text || '';

    return NextResponse.json({ text });
  } catch (err) {
    console.error('[api/ai/generate] error:', (err as Error).message);
    return NextResponse.json({ error: 'AI generation failed' }, { status: 500 });
  }
}