import { NextRequest, NextResponse } from "next/server"; import { z } from "zod"; import { db, recipes, eq, and, or } from "@epicure/db"; import { requireSessionOrApiKey } from "@/lib/api-auth"; import { applyRateLimit } from "@/lib/rate-limit"; import { getDefaultProviderWithKey } from "@/lib/ai/resolve-user-key"; import { withAiQuota, resolveAiConfigOrError } from "@/lib/ai/ai-error"; import { scaleRecipe } from "@/lib/ai/features/scale-recipe"; const Schema = z.object({ recipeId: z.string(), targetServings: z.number().int().min(1).max(100), }); export async function POST(req: NextRequest) { const { session, response } = await requireSessionOrApiKey(req); if (response) return response; const limited = await applyRateLimit(`rl:ai:scale:${session!.user.id}`, 20, 60); if (limited) return limited; const body = await req.json() as unknown; const parsed = Schema.safeParse(body); if (!parsed.success) return NextResponse.json({ error: "Validation error" }, { status: 400 }); const { recipeId, targetServings } = parsed.data; const recipe = await db.query.recipes.findFirst({ where: and( eq(recipes.id, recipeId), or( eq(recipes.authorId, session!.user.id), eq(recipes.visibility, "public") ) ), with: { ingredients: true }, }); if (!recipe) { return NextResponse.json({ error: "Not found" }, { status: 404 }); } const configResult = await resolveAiConfigOrError(() => getDefaultProviderWithKey(session!.user.id, "text")); if (!configResult.ok) return configResult.response; const aiConfig = configResult.data; const result = await withAiQuota(session!.user.id, session!.user.tier as "free" | "pro", () => scaleRecipe( { title: recipe.title, baseServings: recipe.baseServings, ingredients: recipe.ingredients, }, targetServings, recipe.baseServings, aiConfig, (session!.user as { locale?: string }).locale ?? "en" ), { skipQuota: aiConfig.isByok } ); if (!result.ok) return result.response; return NextResponse.json({ ingredients: result.data }); }