Files
Epicure/packages/db/src/schema/meal-planning.ts
T
Arnaud ba1614073b feat: adapt recipe surfaces for batch-cook sessions
- Recipes page action buttons reordered/restyled to push AI generation
  and batch cooking ahead of manual recipe creation.
- Recipe detail page hides meal/drink pairing (doesn't make sense for
  a multi-dish batch session).
- Print pages force light mode regardless of app theme (dark bg +
  hardcoded dark text was unreadable) and render sectioned steps +
  dishes/storage for batch-cook recipes.
- Markdown export mirrors the same sectioned structure.
- Cooking mode tags each step with which dish(es) it belongs to.
- Meal planner: mealPlanEntries.batchDishId lets a slot point at one
  specific dish within a batch session; picking a batch-cook recipe
  now prompts for which dish, and the grid shows the dish name.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 02:32:57 +02:00

133 lines
5.7 KiB
TypeScript

import {
pgTable,
text,
timestamp,
integer,
boolean,
date,
decimal,
pgEnum,
index,
uniqueIndex,
} from "drizzle-orm/pg-core";
import { relations } from "drizzle-orm";
import { users } from "./users";
import { recipes, recipeBatchDishes } from "./recipes";
import { ingredients } from "./recipes";
import { collectionMemberRoleEnum } from "./social";
export const mealTypeEnum = pgEnum("meal_type", ["breakfast", "lunch", "dinner", "snack"]);
export const weekdayEnum = pgEnum("weekday", ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]);
export const mealPlans = pgTable("meal_plans", {
id: text("id").primaryKey(),
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
weekStart: date("week_start").notNull(),
createdAt: timestamp("created_at").notNull().defaultNow(),
}, (t) => [
index("meal_plans_user_idx").on(t.userId),
uniqueIndex("meal_plans_user_week_idx").on(t.userId, t.weekStart),
]);
export const mealPlanEntries = pgTable("meal_plan_entries", {
id: text("id").primaryKey(),
mealPlanId: text("meal_plan_id").notNull().references(() => mealPlans.id, { onDelete: "cascade" }),
day: weekdayEnum("day").notNull(),
mealType: mealTypeEnum("meal_type").notNull(),
recipeId: text("recipe_id").references(() => recipes.id, { onDelete: "set null" }),
batchDishId: text("batch_dish_id").references(() => recipeBatchDishes.id, { onDelete: "set null" }),
servings: integer("servings").notNull().default(2),
note: text("note"),
}, (t) => [
index("meal_plan_entries_plan_idx").on(t.mealPlanId),
uniqueIndex("meal_plan_entries_plan_day_meal_idx").on(t.mealPlanId, t.day, t.mealType),
]);
export const pantryItems = pgTable("pantry_items", {
id: text("id").primaryKey(),
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
ingredientId: text("ingredient_id").references(() => ingredients.id, { onDelete: "set null" }),
rawName: text("raw_name").notNull(),
quantity: decimal("quantity", { precision: 10, scale: 4 }),
unit: text("unit"),
expiresAt: timestamp("expires_at"),
createdAt: timestamp("created_at").notNull().defaultNow(),
}, (t) => [
index("pantry_items_user_idx").on(t.userId),
]);
export const shoppingLists = pgTable("shopping_lists", {
id: text("id").primaryKey(),
userId: text("user_id").references(() => users.id, { onDelete: "cascade" }),
name: text("name").notNull(),
generatedAt: timestamp("generated_at"),
createdAt: timestamp("created_at").notNull().defaultNow(),
});
export const shoppingListItems = pgTable("shopping_list_items", {
id: text("id").primaryKey(),
listId: text("list_id").notNull().references(() => shoppingLists.id, { onDelete: "cascade" }),
ingredientId: text("ingredient_id").references(() => ingredients.id, { onDelete: "set null" }),
rawName: text("raw_name").notNull(),
quantity: decimal("quantity", { precision: 10, scale: 4 }),
unit: text("unit"),
aisle: text("aisle"),
checked: boolean("checked").notNull().default(false),
inPantry: boolean("in_pantry").notNull().default(false),
sortOrder: integer("sort_order").notNull().default(0),
});
export const shoppingListMembers = pgTable("shopping_list_members", {
id: text("id").primaryKey(),
listId: text("list_id").notNull().references(() => shoppingLists.id, { onDelete: "cascade" }),
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
role: collectionMemberRoleEnum("role").notNull().default("viewer"),
createdAt: timestamp("created_at").notNull().defaultNow(),
}, (t) => [
index("shopping_list_members_list_idx").on(t.listId),
index("shopping_list_members_user_idx").on(t.userId),
]);
export const mealPlanMembers = pgTable("meal_plan_members", {
id: text("id").primaryKey(),
mealPlanId: text("meal_plan_id").notNull().references(() => mealPlans.id, { onDelete: "cascade" }),
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
role: collectionMemberRoleEnum("role").notNull().default("viewer"),
createdAt: timestamp("created_at").notNull().defaultNow(),
}, (t) => [
index("meal_plan_members_plan_idx").on(t.mealPlanId),
index("meal_plan_members_user_idx").on(t.userId),
]);
export const mealPlansRelations = relations(mealPlans, ({ one, many }) => ({
user: one(users, { fields: [mealPlans.userId], references: [users.id] }),
entries: many(mealPlanEntries),
members: many(mealPlanMembers),
}));
export const mealPlanEntriesRelations = relations(mealPlanEntries, ({ one }) => ({
mealPlan: one(mealPlans, { fields: [mealPlanEntries.mealPlanId], references: [mealPlans.id] }),
recipe: one(recipes, { fields: [mealPlanEntries.recipeId], references: [recipes.id] }),
batchDish: one(recipeBatchDishes, { fields: [mealPlanEntries.batchDishId], references: [recipeBatchDishes.id] }),
}));
export const mealPlanMembersRelations = relations(mealPlanMembers, ({ one }) => ({
mealPlan: one(mealPlans, { fields: [mealPlanMembers.mealPlanId], references: [mealPlans.id] }),
user: one(users, { fields: [mealPlanMembers.userId], references: [users.id] }),
}));
export const shoppingListsRelations = relations(shoppingLists, ({ one, many }) => ({
user: one(users, { fields: [shoppingLists.userId], references: [users.id] }),
items: many(shoppingListItems),
members: many(shoppingListMembers),
}));
export const shoppingListItemsRelations = relations(shoppingListItems, ({ one }) => ({
list: one(shoppingLists, { fields: [shoppingListItems.listId], references: [shoppingLists.id] }),
}));
export const shoppingListMembersRelations = relations(shoppingListMembers, ({ one }) => ({
list: one(shoppingLists, { fields: [shoppingListMembers.listId], references: [shoppingLists.id] }),
user: one(users, { fields: [shoppingListMembers.userId], references: [users.id] }),
}));