From f54127a8cc06c51c9c7f15d6c6931539ca41eff3 Mon Sep 17 00:00:00 2001 From: pyr0ball Date: Sun, 12 Apr 2026 14:04:53 -0700 Subject: [PATCH] fix(meal-planner): add GET prep-session endpoint, fix list_plans schema, replace assert with ValueError - Add GET /{plan_id}/prep-session endpoint so frontend can retrieve existing sessions without creating - Fix list_plans response_model from list[dict] to list[PlanSummary] with proper _plan_summary() mapping - Replace assert in store.update_prep_task with ValueError (assert is stripped under python -O) - Add day_of_week 0-6 validation to upsert_slot endpoint - Remove MagicMock sqlite artifact files left by pytest (already in .gitignore) --- app/api/endpoints/meal_plans.py | 35 ++++++++++++++++++++++++++++++--- app/db/store.py | 4 +++- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/app/api/endpoints/meal_plans.py b/app/api/endpoints/meal_plans.py index eaed30f..e1d6cbc 100644 --- a/app/api/endpoints/meal_plans.py +++ b/app/api/endpoints/meal_plans.py @@ -92,12 +92,17 @@ async def create_plan( return _plan_summary(plan, slots) -@router.get("/", response_model=list[dict]) +@router.get("/", response_model=list[PlanSummary]) async def list_plans( session: CloudUser = Depends(get_session), store: Store = Depends(get_store), -) -> list[dict]: - return await asyncio.to_thread(store.list_meal_plans) +) -> list[PlanSummary]: + plans = await asyncio.to_thread(store.list_meal_plans) + result = [] + for p in plans: + slots = await asyncio.to_thread(store.get_plan_slots, p["id"]) + result.append(_plan_summary(p, slots)) + return result @router.get("/{plan_id}", response_model=PlanSummary) @@ -124,6 +129,8 @@ async def upsert_slot( session: CloudUser = Depends(get_session), store: Store = Depends(get_store), ) -> SlotSummary: + if day_of_week < 0 or day_of_week > 6: + raise HTTPException(status_code=422, detail="day_of_week must be 0-6.") if meal_type not in VALID_MEAL_TYPES: raise HTTPException(status_code=422, detail=f"Invalid meal_type '{meal_type}'.") plan = await asyncio.to_thread(store.get_meal_plan, plan_id) @@ -197,6 +204,28 @@ async def get_shopping_list( # ── prep session ────────────────────────────────────────────────────────────── +@router.get("/{plan_id}/prep-session", response_model=PrepSessionSummary) +async def get_prep_session( + plan_id: int, + session: CloudUser = Depends(get_session), + store: Store = Depends(get_store), +) -> PrepSessionSummary: + plan = await asyncio.to_thread(store.get_meal_plan, plan_id) + if plan is None: + raise HTTPException(status_code=404, detail="Plan not found.") + prep_session = await asyncio.to_thread(store.get_prep_session_for_plan, plan_id) + if prep_session is None: + raise HTTPException(status_code=404, detail="No prep session for this plan.") + raw_tasks = await asyncio.to_thread(store.get_prep_tasks, prep_session["id"]) + return PrepSessionSummary( + id=prep_session["id"], + plan_id=plan_id, + scheduled_date=prep_session["scheduled_date"], + status=prep_session["status"], + tasks=[_prep_task_summary(t) for t in raw_tasks], + ) + + @router.post("/{plan_id}/prep-session", response_model=PrepSessionSummary) async def create_prep_session( plan_id: int, diff --git a/app/db/store.py b/app/db/store.py index 83dd49a..a095c55 100644 --- a/app/db/store.py +++ b/app/db/store.py @@ -1115,7 +1115,9 @@ class Store: 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} - assert all(k in allowed for k in updates), f"Unexpected column(s): {set(updates) - allowed}" + invalid = set(updates) - allowed + if invalid: + raise ValueError(f"Unexpected column(s) in update_prep_task: {invalid}") if not updates: return self._fetch_one("SELECT * FROM prep_tasks WHERE id = ?", (task_id,)) set_clause = ", ".join(f"{k} = ?" for k in updates)