mirror of
https://github.com/RichieCahill/dotfiles.git
synced 2026-04-17 04:58:19 -04:00
added bound checking to van invintory
This commit is contained in:
@@ -5,7 +5,7 @@ from __future__ import annotations
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
@@ -22,7 +22,7 @@ class ItemCreate(BaseModel):
|
||||
"""Schema for creating an item."""
|
||||
|
||||
name: str
|
||||
quantity: float = 0
|
||||
quantity: float = Field(default=0, ge=0)
|
||||
unit: str
|
||||
category: str | None = None
|
||||
|
||||
@@ -31,7 +31,7 @@ class ItemUpdate(BaseModel):
|
||||
"""Schema for updating an item."""
|
||||
|
||||
name: str | None = None
|
||||
quantity: float | None = None
|
||||
quantity: float | None = Field(default=None, ge=0)
|
||||
unit: str | None = None
|
||||
category: str | None = None
|
||||
|
||||
@@ -52,7 +52,7 @@ class IngredientCreate(BaseModel):
|
||||
"""Schema for adding an ingredient to a meal."""
|
||||
|
||||
item_id: int
|
||||
quantity_needed: float
|
||||
quantity_needed: float = Field(gt=0)
|
||||
|
||||
|
||||
class MealCreate(BaseModel):
|
||||
@@ -192,6 +192,9 @@ def delete_item(item_id: int, db: DbSession) -> dict[str, bool]:
|
||||
@router.post("/meals", response_model=MealResponse)
|
||||
def create_meal(meal: MealCreate, db: DbSession) -> MealResponse:
|
||||
"""Create a new meal with optional ingredients."""
|
||||
for ing in meal.ingredients:
|
||||
if not db.get(Item, ing.item_id):
|
||||
raise HTTPException(status_code=422, detail=f"Item {ing.item_id} not found")
|
||||
db_meal = Meal(name=meal.name, instructions=meal.instructions)
|
||||
db.add(db_meal)
|
||||
db.flush()
|
||||
@@ -217,6 +220,15 @@ def list_meals(db: DbSession) -> list[MealResponse]:
|
||||
return [MealResponse.from_meal(m) for m in meals]
|
||||
|
||||
|
||||
@router.get("/meals/availability", response_model=list[MealAvailability])
|
||||
def check_all_meals(db: DbSession) -> list[MealAvailability]:
|
||||
"""Check which meals can be made with current inventory."""
|
||||
meals = list(
|
||||
db.scalars(select(Meal).options(selectinload(Meal.ingredients).selectinload(MealIngredient.item))).all()
|
||||
)
|
||||
return [_check_meal(m) for m in meals]
|
||||
|
||||
|
||||
@router.get("/meals/{meal_id}", response_model=MealResponse)
|
||||
def get_meal(meal_id: int, db: DbSession) -> MealResponse:
|
||||
"""Get a meal by ID with ingredients."""
|
||||
@@ -245,6 +257,13 @@ def add_ingredient(meal_id: int, ingredient: IngredientCreate, db: DbSession) ->
|
||||
meal = db.get(Meal, meal_id)
|
||||
if not meal:
|
||||
raise HTTPException(status_code=404, detail="Meal not found")
|
||||
if not db.get(Item, ingredient.item_id):
|
||||
raise HTTPException(status_code=422, detail="Item not found")
|
||||
existing = db.scalar(
|
||||
select(MealIngredient).where(MealIngredient.meal_id == meal_id, MealIngredient.item_id == ingredient.item_id)
|
||||
)
|
||||
if existing:
|
||||
raise HTTPException(status_code=409, detail="Ingredient already exists for this meal")
|
||||
db.add(MealIngredient(meal_id=meal_id, item_id=ingredient.item_id, quantity_needed=ingredient.quantity_needed))
|
||||
db.commit()
|
||||
meal = db.scalar(
|
||||
@@ -264,18 +283,6 @@ def remove_ingredient(meal_id: int, item_id: int, db: DbSession) -> dict[str, bo
|
||||
return {"deleted": True}
|
||||
|
||||
|
||||
# What can I make / what do I need
|
||||
|
||||
|
||||
@router.get("/meals/availability", response_model=list[MealAvailability])
|
||||
def check_all_meals(db: DbSession) -> list[MealAvailability]:
|
||||
"""Check which meals can be made with current inventory."""
|
||||
meals = list(
|
||||
db.scalars(select(Meal).options(selectinload(Meal.ingredients).selectinload(MealIngredient.item))).all()
|
||||
)
|
||||
return [_check_meal(m) for m in meals]
|
||||
|
||||
|
||||
@router.get("/meals/{meal_id}/availability", response_model=MealAvailability)
|
||||
def check_meal(meal_id: int, db: DbSession) -> MealAvailability:
|
||||
"""Check if a specific meal can be made and what's missing."""
|
||||
|
||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
||||
from pathlib import Path
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Form, Request
|
||||
from fastapi import APIRouter, Form, HTTPException, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from sqlalchemy import select
|
||||
@@ -43,6 +43,8 @@ def htmx_create_item(
|
||||
category: Annotated[str | None, Form()] = None,
|
||||
) -> HTMLResponse:
|
||||
"""Create an item and return updated item rows."""
|
||||
if quantity < 0:
|
||||
raise HTTPException(status_code=422, detail="Quantity must not be negative")
|
||||
db.add(Item(name=name, quantity=quantity, unit=unit, category=category or None))
|
||||
db.commit()
|
||||
items = list(db.scalars(select(Item).order_by(Item.name)).all())
|
||||
@@ -57,6 +59,8 @@ def htmx_update_item(
|
||||
quantity: Annotated[float, Form()],
|
||||
) -> HTMLResponse:
|
||||
"""Update an item's quantity and return updated item rows."""
|
||||
if quantity < 0:
|
||||
raise HTTPException(status_code=422, detail="Quantity must not be negative")
|
||||
item = db.get(Item, item_id)
|
||||
if item:
|
||||
item.quantity = quantity
|
||||
@@ -132,6 +136,8 @@ def _load_meal(db: DbSession, meal_id: int) -> Meal | None:
|
||||
def meal_detail_page(request: Request, meal_id: int, db: DbSession) -> HTMLResponse:
|
||||
"""Render the meal detail page."""
|
||||
meal = _load_meal(db, meal_id)
|
||||
if not meal:
|
||||
raise HTTPException(status_code=404, detail="Meal not found")
|
||||
items = list(db.scalars(select(Item).order_by(Item.name)).all())
|
||||
return templates.TemplateResponse(request, "meal_detail.html", {"meal": meal, "items": items})
|
||||
|
||||
@@ -145,6 +151,18 @@ def htmx_add_ingredient(
|
||||
quantity_needed: Annotated[float, Form()],
|
||||
) -> HTMLResponse:
|
||||
"""Add an ingredient to a meal and return updated ingredient rows."""
|
||||
if quantity_needed <= 0:
|
||||
raise HTTPException(status_code=422, detail="Quantity must be positive")
|
||||
meal = db.get(Meal, meal_id)
|
||||
if not meal:
|
||||
raise HTTPException(status_code=404, detail="Meal not found")
|
||||
if not db.get(Item, item_id):
|
||||
raise HTTPException(status_code=422, detail="Item not found")
|
||||
existing = db.scalar(
|
||||
select(MealIngredient).where(MealIngredient.meal_id == meal_id, MealIngredient.item_id == item_id)
|
||||
)
|
||||
if existing:
|
||||
raise HTTPException(status_code=409, detail="Ingredient already exists for this meal")
|
||||
db.add(MealIngredient(meal_id=meal_id, item_id=item_id, quantity_needed=quantity_needed))
|
||||
db.commit()
|
||||
meal = _load_meal(db, meal_id)
|
||||
|
||||
Reference in New Issue
Block a user