profileShare

rasmusjy / roundtable

Read-only snapshot

No repository description.

main default branch 181 files Expires Sep 13, 2026, 9:06 AM
route.ts 2,013 bytes
1 import { NextResponse } from 'next/server';
2 import { currentUserId } from '@/auth';
3 import { resolveDebateConfig } from '@/core/config';
4 import { debateConfigInputSchema } from '@/core/schemas';
5 import { KeyResolutionError, startDebateStream } from '@/lib/debate-runner';
6 import { env } from '@/lib/env';
7 import { rateLimit } from '@/lib/rate-limit';
8
9 export const runtime = 'nodejs';
10 export const dynamic = 'force-dynamic';
11 // Debate streams run for minutes; on a self-hosted node server there is no cap,
12 // but declare a generous ceiling for platforms that honor it.
13 export const maxDuration = 800;
14
15 function clientIp(request: Request): string {
16 const fwd = request.headers.get('x-forwarded-for');
17 return fwd?.split(',')[0]?.trim() || request.headers.get('x-real-ip') || 'unknown';
18 }
19
20 export async function POST(request: Request) {
21 const body = await request.json().catch(() => null);
22 const parsed = debateConfigInputSchema.safeParse(body);
23 if (!parsed.success) {
24 return NextResponse.json({ error: 'Invalid debate configuration', issues: parsed.error.flatten() }, { status: 400 });
25 }
26 const config = resolveDebateConfig(parsed.data);
27 const userId = await currentUserId();
28
29 const rlKey = userId ? `user:${userId}` : `ip:${clientIp(request)}`;
30 const rl = rateLimit(`debate:${rlKey}`, env.RATE_LIMIT_DEBATES_PER_HOUR, 60 * 60 * 1000);
31 if (!rl.allowed) {
32 return NextResponse.json(
33 { error: `Rate limit reached (${rl.limit}/hour). Try again in ${Math.ceil(rl.resetMs / 60000)} min.` },
34 { status: 429, headers: { 'Retry-After': String(Math.ceil(rl.resetMs / 1000)) } },
35 );
36 }
37
38 try {
39 const { response } = await startDebateStream({ config, userId });
40 return response;
41 } catch (err) {
42 if (err instanceof KeyResolutionError) {
43 return NextResponse.json({ error: err.message, code: 'NO_KEY' }, { status: 401 });
44 }
45 console.error('debate start failed', err);
46 return NextResponse.json({ error: 'Failed to start debate' }, { status: 500 });
47 }
48 }
49