/** * WorthDoing.ai * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai * File: src/app/api/investigations/route.ts * Description: Create + start investigations (POST) and list them (GET), with basic rate limiting. */ import { NextRequest, NextResponse } from "next/server"; import { desc } from "drizzle-orm"; import { z } from "zod"; import { db } from "@/lib/db/client"; import { investigations } from "@/lib/db/schema"; import { createAndStartInvestigation } from "@/lib/agent/runner"; export const dynamic = "force-dynamic"; const createSchema = z.object({ objective: z.string().min(8).max(500), }); // Simple in-memory rate limit: max 4 new investigations per 10 minutes per IP. const globalForRl = globalThis as unknown as { __wdRate?: Map }; const rateMap = (globalForRl.__wdRate ??= new Map()); const RL_WINDOW_MS = 10 * 60 * 1000; const RL_MAX = 4; export async function POST(req: NextRequest) { const ip = req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? "local"; const now = Date.now(); const hits = (rateMap.get(ip) ?? []).filter((t) => now - t < RL_WINDOW_MS); if (hits.length >= RL_MAX) { return NextResponse.json( { error: "Rate limit exceeded — max 4 investigations per 10 minutes." }, { status: 429 }, ); } let body: unknown; try { body = await req.json(); } catch { return NextResponse.json({ error: "Invalid JSON body." }, { status: 400 }); } const parsed = createSchema.safeParse(body); if (!parsed.success) { return NextResponse.json( { error: "objective must be a string of 8–500 characters." }, { status: 400 }, ); } hits.push(now); rateMap.set(ip, hits); const inv = await createAndStartInvestigation(parsed.data.objective); return NextResponse.json( { id: inv.id, objective: inv.objective, status: inv.status, createdAt: inv.createdAt }, { status: 201 }, ); } export async function GET() { const rows = await db .select({ id: investigations.id, objective: investigations.objective, status: investigations.status, phase: investigations.phase, outcome: investigations.outcome, budgetUsed: investigations.budgetUsed, createdAt: investigations.createdAt, completedAt: investigations.completedAt, }) .from(investigations) .orderBy(desc(investigations.createdAt)) .limit(50); return NextResponse.json({ investigations: rows }); }