feat(seed): import 246 verified recipes as committed seed catalog
- Convert C:\Users\Public\cibello-verified-final-result.json to packages/database/src/seed/data/verified-recipes.ts (committed, hermetic). - Replace/extend the 23 placeholder SEED_RECIPES via re-export. - Upsert on slug with onConflictDoUpdate of ALL recipe fields. - Hard-reject recipes with unmapped canonical ingredients (0 rejected this batch). - Derive allergens and compute nutrition per portion from ingredients at seed time. - Run food-safety lint; auto-patch 8 legacy raw-protein recipes with safe-cooking phrase. - Resolve 4 suspected title duplicates via variantOfSlug. - Guard brand name: creatorDisplayName uses BRAND.name, never JSON placeholder. - Preserve verificationStatus (editorial/verified) and source-registry FKs. - Extend allergen-invariant test to cover full 246-recipe catalog. - Stabilise API tests by disabling file parallelism (flakiness from shared DB state). Refs: seed pipeline, brand-guard, safety canary
This commit is contained in:
@@ -735,47 +735,47 @@ async function insertRecipe(
|
||||
? (slugToId.get(recipe.variantOfSlug) ?? null)
|
||||
: null;
|
||||
|
||||
const recipeValues = {
|
||||
slug: recipe.slug,
|
||||
titleSv: recipe.titleSv,
|
||||
descriptionSv: recipe.descriptionSv,
|
||||
country: recipe.country ?? null,
|
||||
cuisine: recipe.cuisine,
|
||||
mealTypes: recipe.mealTypes,
|
||||
tags: recipe.tags,
|
||||
methods: recipe.methods,
|
||||
equipment: recipe.equipment,
|
||||
difficulty: recipe.difficulty,
|
||||
prepTimeMinutes: recipe.prepMin,
|
||||
cookTimeMinutes: recipe.cookMin,
|
||||
totalTimeMinutes: recipe.prepMin + recipe.cookMin,
|
||||
portions: recipe.portions,
|
||||
nutritionPerPortion: nutrition.perPortion,
|
||||
allergens: [...allergens].sort(),
|
||||
spiceLevel: recipe.spiceLevel,
|
||||
estimatedCostMinorPerPortion: costPerPortion,
|
||||
storageGuidanceSv: recipe.storageGuidanceSv ?? null,
|
||||
mealPrepFriendly: recipe.mealPrepFriendly,
|
||||
freezerFriendly: recipe.freezerFriendly,
|
||||
peakSeasons: recipe.peakSeasons,
|
||||
holidayTags: recipe.holidayTags,
|
||||
dna,
|
||||
variantType: recipe.variantType ?? "standard",
|
||||
variantOfRecipeId,
|
||||
status: "published" as const,
|
||||
verificationStatus: recipe.verificationStatus ?? "editorial",
|
||||
sourceType: "own_editorial" as const,
|
||||
sourceRegistryId,
|
||||
creatorDisplayName: `${BRAND.name} Redaktion`,
|
||||
};
|
||||
|
||||
const [row] = await db
|
||||
.insert(schema.recipes)
|
||||
.values({
|
||||
slug: recipe.slug,
|
||||
titleSv: recipe.titleSv,
|
||||
descriptionSv: recipe.descriptionSv,
|
||||
country: recipe.country ?? null,
|
||||
cuisine: recipe.cuisine,
|
||||
mealTypes: recipe.mealTypes,
|
||||
tags: recipe.tags,
|
||||
methods: recipe.methods,
|
||||
equipment: recipe.equipment,
|
||||
difficulty: recipe.difficulty,
|
||||
prepTimeMinutes: recipe.prepMin,
|
||||
cookTimeMinutes: recipe.cookMin,
|
||||
totalTimeMinutes: recipe.prepMin + recipe.cookMin,
|
||||
portions: recipe.portions,
|
||||
nutritionPerPortion: nutrition.perPortion,
|
||||
allergens: [...allergens].sort(),
|
||||
spiceLevel: recipe.spiceLevel,
|
||||
estimatedCostMinorPerPortion: costPerPortion,
|
||||
storageGuidanceSv: recipe.storageGuidanceSv ?? null,
|
||||
mealPrepFriendly: recipe.mealPrepFriendly,
|
||||
freezerFriendly: recipe.freezerFriendly,
|
||||
peakSeasons: recipe.peakSeasons,
|
||||
holidayTags: recipe.holidayTags,
|
||||
dna,
|
||||
variantType: recipe.variantType ?? "standard",
|
||||
variantOfRecipeId,
|
||||
status: "published",
|
||||
verificationStatus: "editorial",
|
||||
sourceType: "own_editorial",
|
||||
sourceRegistryId,
|
||||
creatorDisplayName: `${BRAND.name} Redaktion`,
|
||||
})
|
||||
.values(recipeValues)
|
||||
.onConflictDoUpdate({
|
||||
target: schema.recipes.slug,
|
||||
set: {
|
||||
nutritionPerPortion: nutrition.perPortion,
|
||||
allergens: [...allergens].sort(),
|
||||
dna,
|
||||
...recipeValues,
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user