d2faf98ac1
Fixes the 13-item codebase health scan backlog: wraps meal-plan generation in a transaction, adds missing userId/GIN indexes, fixes an IPv6-parsing gap in the webhook SSRF guard (and an identical duplicated bug in the AI URL-import path, now consolidated onto one implementation), paginates the collections list, dedupes the AI recipe Zod schemas, wires up Stripe tier sync, rate-limits AI key rotation, gets `pnpm typecheck` actually working, and adds test coverage for the previously-untested admin/webhooks routes. Two flagged issues (collection removeRecipeId IDOR, tier-limit race) turned out to already be fixed/non-issues on inspection — noted in TODO.md rather than silently dropped. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
40 lines
2.1 KiB
TypeScript
40 lines
2.1 KiB
TypeScript
import { generateObject } from "ai";
|
|
import { z } from "zod";
|
|
import { resolveModel, type AiConfig } from "../factory";
|
|
import { dietaryTagsSchema, ingredientSchema, stepSchema } from "./recipe-schema";
|
|
|
|
const RecipeOutputSchema = z.object({
|
|
title: z.string(),
|
|
description: z.string(),
|
|
baseServings: z.number().int().min(1).max(100),
|
|
prepMins: z.number().int().min(0).optional(),
|
|
cookMins: z.number().int().min(0).optional(),
|
|
difficulty: z.enum(["easy", "medium", "hard"]),
|
|
dietaryTags: dietaryTagsSchema,
|
|
ingredients: z.array(ingredientSchema(z.number())),
|
|
steps: z.array(stepSchema),
|
|
});
|
|
|
|
export type GeneratedRecipe = z.infer<typeof RecipeOutputSchema>;
|
|
|
|
export async function generateRecipe(
|
|
prompt: string,
|
|
config?: AiConfig & { language?: string; difficulty?: "easy" | "medium" | "hard"; userContext?: string }
|
|
): Promise<GeneratedRecipe> {
|
|
const model = resolveModel(config);
|
|
const lang = config?.language ?? "en";
|
|
const langInstruction = lang !== "en" ? ` Write the entire recipe (title, description, ingredient names, step instructions) in ${lang}.` : "";
|
|
const difficultyInstruction = config?.difficulty ? ` The recipe must be ${config.difficulty} difficulty: ${{ easy: "simple techniques, minimal steps, common ingredients", medium: "moderate skill required, standard techniques", hard: "advanced techniques, multiple components, skilled cook required" }[config.difficulty]}.` : "";
|
|
const userInstruction = config?.userContext ? `\n\nUser preferences and context:\n${config.userContext}` : "";
|
|
|
|
const { object } = await generateObject({
|
|
model,
|
|
schema: RecipeOutputSchema,
|
|
system:
|
|
`You are a professional chef and culinary writer. Generate detailed, accurate, and delicious recipes. For ingredients: quantity must be a number only (e.g. 0.25, 1.5, 2), unit is a separate string (e.g. 'cup', 'tbsp', 'g', 'ml'). Never combine quantity and unit into one string. Include helpful notes for ingredients when relevant.${langInstruction}${difficultyInstruction}${userInstruction}`,
|
|
prompt: `Create a recipe for: ${prompt}`,
|
|
});
|
|
|
|
return object;
|
|
}
|