feat: add batch-cooking sessions (single "mega recipe" with dish sections)
New AI-generated batch-cooking flow: pick N dinners/lunches + servings, get one unified recipe covering a full prep session — merged shopping list, steps tagged per-dish (a step can advance several dishes at once, e.g. a shared oven bake), and per-dish storage (fridge/freezer) + exact day-of reheat/finishing instructions. Schema: recipes.isBatchCook, recipeSteps.appliesTo (dish names), new recipeBatchDishes table. Batch recipes are excluded from the normal /recipes list and live under their own /batch-cooking section. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -45,12 +45,14 @@ export const recipes = pgTable("recipes", {
|
||||
prepMins: integer("prep_mins"),
|
||||
cookMins: integer("cook_mins"),
|
||||
tags: text("tags").array().notNull().default([]),
|
||||
isBatchCook: boolean("is_batch_cook").notNull().default(false),
|
||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at").notNull().defaultNow(),
|
||||
}, (t) => [
|
||||
index("recipes_author_idx").on(t.authorId),
|
||||
index("recipes_visibility_idx").on(t.visibility),
|
||||
index("recipes_dietary_tags_gin").using("gin", t.dietaryTags),
|
||||
index("recipes_batch_cook_idx").on(t.isBatchCook),
|
||||
]);
|
||||
|
||||
export const ingredients = pgTable("ingredients", {
|
||||
@@ -82,10 +84,25 @@ export const recipeSteps = pgTable("recipe_steps", {
|
||||
instruction: text("instruction").notNull(),
|
||||
timerSeconds: integer("timer_seconds"),
|
||||
photoUrl: text("photo_url"),
|
||||
appliesTo: text("applies_to").array().notNull().default([]),
|
||||
}, (t) => [
|
||||
index("recipe_steps_recipe_idx").on(t.recipeId),
|
||||
]);
|
||||
|
||||
export const recipeBatchDishes = pgTable("recipe_batch_dishes", {
|
||||
id: text("id").primaryKey(),
|
||||
recipeId: text("recipe_id").notNull().references(() => recipes.id, { onDelete: "cascade" }),
|
||||
name: text("name").notNull(),
|
||||
description: text("description"),
|
||||
order: integer("order").notNull().default(0),
|
||||
fridgeDays: integer("fridge_days").notNull(),
|
||||
freezerFriendly: boolean("freezer_friendly").notNull().default(false),
|
||||
freezerNote: text("freezer_note"),
|
||||
dayOfInstructions: text("day_of_instructions").notNull(),
|
||||
}, (t) => [
|
||||
index("recipe_batch_dishes_recipe_idx").on(t.recipeId),
|
||||
]);
|
||||
|
||||
export const recipePhotos = pgTable("recipe_photos", {
|
||||
id: text("id").primaryKey(),
|
||||
recipeId: text("recipe_id").notNull().references(() => recipes.id, { onDelete: "cascade" }),
|
||||
@@ -128,6 +145,11 @@ export const recipesRelations = relations(recipes, ({ one, many }) => ({
|
||||
notes: many(recipeNotes),
|
||||
childVariations: many(recipeVariations, { relationName: "parent" }),
|
||||
parentVariations: many(recipeVariations, { relationName: "child" }),
|
||||
batchDishes: many(recipeBatchDishes),
|
||||
}));
|
||||
|
||||
export const recipeBatchDishesRelations = relations(recipeBatchDishes, ({ one }) => ({
|
||||
recipe: one(recipes, { fields: [recipeBatchDishes.recipeId], references: [recipes.id] }),
|
||||
}));
|
||||
|
||||
export const recipeIngredientsRelations = relations(recipeIngredients, ({ one }) => ({
|
||||
|
||||
Reference in New Issue
Block a user