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)
This commit is contained in:
parent
062b5d16a1
commit
f54127a8cc
2 changed files with 35 additions and 4 deletions
|
|
@ -92,12 +92,17 @@ async def create_plan(
|
||||||
return _plan_summary(plan, slots)
|
return _plan_summary(plan, slots)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/", response_model=list[dict])
|
@router.get("/", response_model=list[PlanSummary])
|
||||||
async def list_plans(
|
async def list_plans(
|
||||||
session: CloudUser = Depends(get_session),
|
session: CloudUser = Depends(get_session),
|
||||||
store: Store = Depends(get_store),
|
store: Store = Depends(get_store),
|
||||||
) -> list[dict]:
|
) -> list[PlanSummary]:
|
||||||
return await asyncio.to_thread(store.list_meal_plans)
|
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)
|
@router.get("/{plan_id}", response_model=PlanSummary)
|
||||||
|
|
@ -124,6 +129,8 @@ async def upsert_slot(
|
||||||
session: CloudUser = Depends(get_session),
|
session: CloudUser = Depends(get_session),
|
||||||
store: Store = Depends(get_store),
|
store: Store = Depends(get_store),
|
||||||
) -> SlotSummary:
|
) -> 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:
|
if meal_type not in VALID_MEAL_TYPES:
|
||||||
raise HTTPException(status_code=422, detail=f"Invalid meal_type '{meal_type}'.")
|
raise HTTPException(status_code=422, detail=f"Invalid meal_type '{meal_type}'.")
|
||||||
plan = await asyncio.to_thread(store.get_meal_plan, plan_id)
|
plan = await asyncio.to_thread(store.get_meal_plan, plan_id)
|
||||||
|
|
@ -197,6 +204,28 @@ async def get_shopping_list(
|
||||||
|
|
||||||
# ── prep session ──────────────────────────────────────────────────────────────
|
# ── 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)
|
@router.post("/{plan_id}/prep-session", response_model=PrepSessionSummary)
|
||||||
async def create_prep_session(
|
async def create_prep_session(
|
||||||
plan_id: int,
|
plan_id: int,
|
||||||
|
|
|
||||||
|
|
@ -1115,7 +1115,9 @@ class Store:
|
||||||
def update_prep_task(self, task_id: int, **kwargs: object) -> dict | None:
|
def update_prep_task(self, task_id: int, **kwargs: object) -> dict | None:
|
||||||
allowed = {"duration_minutes", "sequence_order", "notes", "equipment"}
|
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}
|
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:
|
if not updates:
|
||||||
return self._fetch_one("SELECT * FROM prep_tasks WHERE id = ?", (task_id,))
|
return self._fetch_one("SELECT * FROM prep_tasks WHERE id = ?", (task_id,))
|
||||||
set_clause = ", ".join(f"{k} = ?" for k in updates)
|
set_clause = ", ".join(f"{k} = ?" for k in updates)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue