import { NextResponse } from 'next/server'; import { z } from 'zod'; import { runScan } from '@/lib/ai/scanner'; import { aiErrorMessage, clientIpHash, consumeQuota, currentUserId, getAnonId } from '@/lib/ai/request'; export const runtime = 'nodejs'; export const maxDuration = 120; const Body = z.object({ mode: z.enum(['photo', 'url', 'text']), images: z.array(z.object({ data: z.string().min(100).max(2_800_000), mediaType: z.enum(['image/jpeg', 'image/png', 'image/webp']) })).max(5).optional(), thumbnails: z.array(z.string().max(80_000)).max(3).optional(), url: z.string().url().max(2000).optional(), text: z.string().max(4000).optional(), }); export async function POST(req: Request) { let body: z.infer; try { body = Body.parse(await req.json()); } catch (err) { return NextResponse.json({ error: 'Invalid request', detail: err instanceof Error ? err.message : String(err) }, { status: 400 }); } if (body.mode === 'photo' && !body.images?.length) return NextResponse.json({ error: 'Add at least one photo' }, { status: 400 }); if (body.mode === 'url' && !body.url) return NextResponse.json({ error: 'Paste a listing URL' }, { status: 400 }); if (body.mode === 'text' && !body.text?.trim()) return NextResponse.json({ error: 'Describe the item' }, { status: 400 }); const [userId, ipHash, anonId] = await Promise.all([currentUserId(), clientIpHash(), getAnonId()]); const quota = await consumeQuota('scanner', { userId, ipHash }); if (!quota.ok) return NextResponse.json({ error: `Daily scanner limit reached (${quota.limit}). Sign in for a higher limit.` }, { status: 429 }); try { const result = await runScan({ mode: body.mode, images: body.images?.map((i) => ({ data: i.data, mediaType: i.mediaType })), thumbnails: body.thumbnails, url: body.url, text: body.text, userId, anonId, ipHash, }); return NextResponse.json({ ...result, quota: { used: quota.used, limit: quota.limit } }); } catch (err) { const e = aiErrorMessage(err); return NextResponse.json({ error: e.message }, { status: e.status }); } }