SPB Git forge

spb/rareindex

Public
54commits 1branches 0releases
7.1 MBsize
maindefault branch
10 days agolast push
TypeScript 61.9% HTML 37.2% SQL 0.7%
2.1 KB · 49 lines typescript
Raw Blame History
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