TypeScript 61.9%
HTML 37.2%
SQL 0.7%
1import { NextResponse } from 'next/server';2import { z } from 'zod';3import { runScan } from '@/lib/ai/scanner';4import { aiErrorMessage, clientIpHash, consumeQuota, currentUserId, getAnonId } from '@/lib/ai/request';56export const runtime = 'nodejs';7export const maxDuration = 120;89const Body = z.object({10 mode: z.enum(['photo', 'url', 'text']),11 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(),12 thumbnails: z.array(z.string().max(80_000)).max(3).optional(),13 url: z.string().url().max(2000).optional(),14 text: z.string().max(4000).optional(),15});1617export async function POST(req: Request) {18 let body: z.infer<typeof Body>;19 try {20 body = Body.parse(await req.json());21 } catch (err) {22 return NextResponse.json({ error: 'Invalid request', detail: err instanceof Error ? err.message : String(err) }, { status: 400 });23 }24 if (body.mode === 'photo' && !body.images?.length) return NextResponse.json({ error: 'Add at least one photo' }, { status: 400 });25 if (body.mode === 'url' && !body.url) return NextResponse.json({ error: 'Paste a listing URL' }, { status: 400 });26 if (body.mode === 'text' && !body.text?.trim()) return NextResponse.json({ error: 'Describe the item' }, { status: 400 });2728 const [userId, ipHash, anonId] = await Promise.all([currentUserId(), clientIpHash(), getAnonId()]);29 const quota = await consumeQuota('scanner', { userId, ipHash });30 if (!quota.ok) return NextResponse.json({ error: `Daily scanner limit reached (${quota.limit}). Sign in for a higher limit.` }, { status: 429 });3132 try {33 const result = await runScan({34 mode: body.mode,35 images: body.images?.map((i) => ({ data: i.data, mediaType: i.mediaType })),36 thumbnails: body.thumbnails,37 url: body.url,38 text: body.text,39 userId,40 anonId,41 ipHash,42 });43 return NextResponse.json({ ...result, quota: { used: quota.used, limit: quota.limit } });44 } catch (err) {45 const e = aiErrorMessage(err);46 return NextResponse.json({ error: e.message }, { status: e.status });47 }48}49