From ffb34c9c6279406dce0c7bfc65fa5829a28ce3ea Mon Sep 17 00:00:00 2001 From: pyr0ball Date: Sun, 12 Apr 2026 13:13:18 -0700 Subject: [PATCH] feat(store): add meal plan, slot, prep session, and prep task CRUD methods --- app/db/store.py | 116 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 115 insertions(+), 1 deletion(-) diff --git a/app/db/store.py b/app/db/store.py index d931b5e..0701b3a 100644 --- a/app/db/store.py +++ b/app/db/store.py @@ -44,7 +44,9 @@ class Store: "ingredients", "ingredient_names", "directions", "keywords", "element_coverage", # saved recipe columns - "style_tags"): + "style_tags", + # meal plan columns + "meal_types"): if key in d and isinstance(d[key], str): try: d[key] = json.loads(d[key]) @@ -1011,3 +1013,115 @@ class Store: (domain, category, page, result_count), ) self.conn.commit() + + # ── meal plans ──────────────────────────────────────────────────────── + + def create_meal_plan(self, week_start: str, meal_types: list[str]) -> dict: + return self._insert_returning( + "INSERT INTO meal_plans (week_start, meal_types) VALUES (?, ?) RETURNING *", + (week_start, json.dumps(meal_types)), + ) + + def get_meal_plan(self, plan_id: int) -> dict | None: + return self._fetch_one("SELECT * FROM meal_plans WHERE id = ?", (plan_id,)) + + def list_meal_plans(self) -> list[dict]: + return self._fetch_all("SELECT * FROM meal_plans ORDER BY week_start DESC") + + def upsert_slot( + self, + plan_id: int, + day_of_week: int, + meal_type: str, + recipe_id: int | None, + servings: float, + custom_label: str | None, + ) -> dict: + return self._insert_returning( + """INSERT INTO meal_plan_slots + (plan_id, day_of_week, meal_type, recipe_id, servings, custom_label) + VALUES (?, ?, ?, ?, ?, ?) + ON CONFLICT(plan_id, day_of_week, meal_type) DO UPDATE SET + recipe_id = excluded.recipe_id, + servings = excluded.servings, + custom_label = excluded.custom_label + RETURNING *""", + (plan_id, day_of_week, meal_type, recipe_id, servings, custom_label), + ) + + def delete_slot(self, slot_id: int) -> None: + self.conn.execute("DELETE FROM meal_plan_slots WHERE id = ?", (slot_id,)) + self.conn.commit() + + def get_plan_slots(self, plan_id: int) -> list[dict]: + return self._fetch_all( + """SELECT s.*, r.name AS recipe_title + FROM meal_plan_slots s + LEFT JOIN recipes r ON r.id = s.recipe_id + WHERE s.plan_id = ? + ORDER BY s.day_of_week, s.meal_type""", + (plan_id,), + ) + + def get_plan_recipes(self, plan_id: int) -> list[dict]: + """Return full recipe rows for all recipes assigned to a plan.""" + return self._fetch_all( + """SELECT DISTINCT r.* + FROM meal_plan_slots s + JOIN recipes r ON r.id = s.recipe_id + WHERE s.plan_id = ? AND s.recipe_id IS NOT NULL""", + (plan_id,), + ) + + # ── prep sessions ───────────────────────────────────────────────────── + + def create_prep_session(self, plan_id: int, scheduled_date: str) -> dict: + return self._insert_returning( + "INSERT INTO prep_sessions (plan_id, scheduled_date) VALUES (?, ?) RETURNING *", + (plan_id, scheduled_date), + ) + + def get_prep_session_for_plan(self, plan_id: int) -> dict | None: + return self._fetch_one( + "SELECT * FROM prep_sessions WHERE plan_id = ? ORDER BY id DESC LIMIT 1", + (plan_id,), + ) + + def bulk_insert_prep_tasks(self, session_id: int, tasks: list[dict]) -> list[dict]: + """Insert multiple prep tasks and return them all.""" + inserted = [] + for t in tasks: + row = self._insert_returning( + """INSERT INTO prep_tasks + (session_id, recipe_id, slot_id, task_label, duration_minutes, + sequence_order, equipment, is_parallel, notes) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *""", + ( + session_id, t.get("recipe_id"), t.get("slot_id"), + t["task_label"], t.get("duration_minutes"), + t["sequence_order"], t.get("equipment"), + int(t.get("is_parallel", False)), t.get("notes"), + ), + ) + inserted.append(row) + return inserted + + def get_prep_tasks(self, session_id: int) -> list[dict]: + return self._fetch_all( + "SELECT * FROM prep_tasks WHERE session_id = ? ORDER BY sequence_order", + (session_id,), + ) + + def update_prep_task(self, task_id: int, **kwargs: object) -> dict | None: + allowed = {"duration_minutes", "sequence_order", "notes", "equipment"} + updates = {k: v for k, v in kwargs.items() if k in allowed and v is not None} + if not updates: + return self._fetch_one("SELECT * FROM prep_tasks WHERE id = ?", (task_id,)) + set_clause = ", ".join(f"{k} = ?" for k in updates) + values = list(updates.values()) + [1, task_id] + self.conn.execute( + f"UPDATE prep_tasks SET {set_clause}, user_edited = ? WHERE id = ?", + values, + ) + self.conn.commit() + return self._fetch_one("SELECT * FROM prep_tasks WHERE id = ?", (task_id,))