ba1614073b
- 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>
133 lines
5.7 KiB
TypeScript
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] }),
|
|
}));
|