from fastapi import APIRouter, Depends, HTTPException from sqlalchemy import select from sqlalchemy.orm import Session from app.api.deps import current_user, require_home_member, require_home_write from app.db.session import get_db from app.models.entities import Product, ShoppingItem, User from app.schemas.common import Message, ProductIn, ProductOut from app.services.audit import audit from app.services.products import OpenFoodFactsLookup, expiry_status router = APIRouter() def serialize(product: Product) -> ProductOut: return ProductOut.model_validate(product).model_copy(update={"status": expiry_status(product, product.home)}) @router.get("", response_model=list[ProductOut]) def list_products(home_id: str, user: User = Depends(current_user), db: Session = Depends(get_db)) -> list[ProductOut]: require_home_member(home_id, db, user) rows = db.scalars(select(Product).where(Product.home_id == home_id).order_by(Product.expires_at.nullslast(), Product.name)).all() return [serialize(product) for product in rows] @router.post("", response_model=ProductOut, status_code=201) def create_product(home_id: str, payload: ProductIn, user: User = Depends(current_user), db: Session = Depends(get_db)) -> ProductOut: require_home_write(home_id, db, user) product = Product(home_id=home_id, created_by_id=user.id, **payload.model_dump()) db.add(product) audit(db, user, "product.create", "product", product.id) db.commit() db.refresh(product) return serialize(product) @router.patch("/{product_id}", response_model=ProductOut) def update_product(home_id: str, product_id: str, payload: ProductIn, user: User = Depends(current_user), db: Session = Depends(get_db)) -> ProductOut: require_home_write(home_id, db, user) product = db.get(Product, product_id) if not product or product.home_id != home_id: raise HTTPException(status_code=404, detail="Product not found") for key, value in payload.model_dump().items(): setattr(product, key, value) audit(db, user, "product.update", "product", product.id) db.commit() return serialize(product) @router.delete("/{product_id}", response_model=Message) def delete_product(home_id: str, product_id: str, user: User = Depends(current_user), db: Session = Depends(get_db)) -> Message: require_home_write(home_id, db, user) product = db.get(Product, product_id) if not product or product.home_id != home_id: raise HTTPException(status_code=404, detail="Product not found") db.delete(product) audit(db, user, "product.delete", "product", product_id) db.commit() return Message(message="Product deleted") @router.post("/{product_id}/add-to-shopping", response_model=Message) def add_to_shopping(home_id: str, product_id: str, user: User = Depends(current_user), db: Session = Depends(get_db)) -> Message: require_home_write(home_id, db, user) product = db.get(Product, product_id) if not product or product.home_id != home_id: raise HTTPException(status_code=404, detail="Product not found") db.add(ShoppingItem(home_id=home_id, product_id=product.id, name=product.name, category=product.category, quantity=max(product.min_quantity - product.quantity, 1), unit=product.unit)) audit(db, user, "shopping.from_product", "product", product.id) db.commit() return Message(message="Added to shopping list") @router.get("/lookup/{barcode}") async def lookup_barcode(home_id: str, barcode: str, user: User = Depends(current_user), db: Session = Depends(get_db)) -> dict: require_home_member(home_id, db, user) result = await OpenFoodFactsLookup().by_barcode(barcode) return {"found": bool(result), "product": result}