Files

96 lines
3.3 KiB
Python

"""Foods router (spec §3.1). Thin: validate → service → schema (spec §8.1 rule 2)."""
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from database import get_db
from schemas import FoodCreate, FoodRead, FoodUpdate, MealRead
from services.foods import (
BarcodeConflictError,
create_food,
delete_food,
get_food,
get_recent_foods,
list_foods,
restore_food,
update_food,
)
router = APIRouter(prefix="/api/foods", tags=["foods"])
@router.get("/recent", response_model=list[FoodRead])
def _recent_foods(
limit: int = Query(default=10, le=50),
db: Session = Depends(get_db),
):
"""Foods ordered by most recent appearance in daily_log, deduplicated.
Soft-deleted foods excluded. Ordered by most-recently-LOGGED, not by
foods.created_at."""
return get_recent_foods(db, limit=limit)
@router.get("", response_model=list[FoodRead])
def _list_foods(
q: str | None = None,
barcode: str | None = None,
limit: int = Query(default=50, le=200),
offset: int = 0,
include_deleted: bool = False,
db: Session = Depends(get_db),
):
"""Search foods. q matches name AND brand. Soft-deleted excluded by default."""
return list_foods(db, q=q, barcode=barcode, limit=limit, offset=offset,
include_deleted=include_deleted)
@router.get("/{food_id}")
def _get_food(food_id: int, db: Session = Depends(get_db)):
"""Get a single food, including soft-deleted ones (for historical logs).
Returns MealRead (with components + computed nutrition) for meals,
plain FoodRead for non-meals."""
food = get_food(db, food_id)
if food is None:
raise HTTPException(status_code=404, detail="Food not found")
return food
@router.post("", response_model=FoodRead, status_code=201)
def _create_food(data: FoodCreate, db: Session = Depends(get_db)):
"""Create a food. Barcode uniqueness enforced on live foods → 409."""
try:
return create_food(db, data)
except BarcodeConflictError as e:
raise HTTPException(status_code=409, detail=str(e))
@router.put("/{food_id}", response_model=FoodRead)
def _update_food(food_id: int, data: FoodUpdate, db: Session = Depends(get_db)):
"""Update editable fields. Only supplied fields are changed."""
try:
result = update_food(db, food_id, data)
except BarcodeConflictError as e:
raise HTTPException(status_code=409, detail=str(e))
if result is None:
raise HTTPException(status_code=404, detail="Food not found")
return result
@router.post("/{food_id}/restore", response_model=FoodRead)
def _restore_food(food_id: int, db: Session = Depends(get_db)):
"""Restore a soft-deleted food: clears deleted_at (spec §3.1).
Idempotent — restoring a live food returns it unchanged."""
result = restore_food(db, food_id)
if result is None:
raise HTTPException(status_code=404, detail="Food not found")
return result
@router.delete("/{food_id}", response_model=FoodRead)
def _delete_food(food_id: int, db: Session = Depends(get_db)):
"""Soft-delete: sets deleted_at. Food hidden from search, still visible by id."""
result = delete_food(db, food_id)
if result is None:
raise HTTPException(status_code=404, detail="Food not found")
return result