From ec9fbc071d0db83275cc0ea0676e22323c6f6f1e Mon Sep 17 00:00:00 2001 From: jableader Date: Sun, 2 Nov 2025 18:14:03 +1100 Subject: [PATCH] Disallow empty ingredients --- api/meals.py | 13 +++++++++++++ api/recipes.py | 7 +++++++ ingredients/models.py | 10 ++++++++++ ingredients/repository.py | 8 ++++++++ 4 files changed, 38 insertions(+) diff --git a/api/meals.py b/api/meals.py index 01519fb..fc05eca 100644 --- a/api/meals.py +++ b/api/meals.py @@ -242,6 +242,13 @@ async def create_meal_scoped( ) if not has_product and name == "" and line == "": return error_response(request, 400, "Ingredient must include a name or line or a product") + # quantity must be > 0 + try: + q = float(getattr(ing, "quantity", 0)) + except Exception: + q = getattr(ing, "quantity", 0) + if isinstance(q, (int, float)) and q <= 0: + return error_response(request, 400, "Ingredient quantity must be greater than 0") # Proactive validation via repositories hid = household["id"] # Validate members exist as users (do not require household membership here to preserve existing behavior/tests) @@ -386,6 +393,12 @@ async def update_meal_scoped( ) if not has_product and name == "" and line == "": return error_response(request, 400, "Ingredient must include a name or line or a product") + try: + q = float(getattr(ing, "quantity", 0)) + except Exception: + q = getattr(ing, "quantity", 0) + if isinstance(q, (int, float)) and q <= 0: + return error_response(request, 400, "Ingredient quantity must be greater than 0") await meals.update_meal(conn, domain_meal) # Return updated state diff --git a/api/recipes.py b/api/recipes.py index 9c2d6ec..6fab118 100644 --- a/api/recipes.py +++ b/api/recipes.py @@ -198,6 +198,13 @@ async def create_recipe( ) if not has_product and name == "" and line == "": return error_response(None, 400, "Ingredient must include a name or line or a product") + # quantity must be > 0 + try: + q = float(getattr(ing, "quantity", 0)) + except Exception: + q = getattr(ing, "quantity", 0) + if isinstance(q, (int, float)) and q <= 0: + return error_response(None, 400, "Ingredient quantity must be greater than 0") hid = household["id"] # Build domain model and insert r = recipes.Recipe( diff --git a/ingredients/models.py b/ingredients/models.py index 0c9bac9..60a9250 100644 --- a/ingredients/models.py +++ b/ingredients/models.py @@ -47,6 +47,16 @@ class Ingredient(ApiModel): return v return v + # Quantity must be > 0 + @field_validator("quantity") + @classmethod + def _positive_quantity(cls, v: float) -> float: + if v is None: + return v + if isinstance(v, (int, float)) and v <= 0: + raise ValueError("Ingredient quantity must be greater than 0") + return v + # Backward compatibility: DB may contain NULL preparation; normalize to empty string @field_validator("preparation", mode="before") @classmethod diff --git a/ingredients/repository.py b/ingredients/repository.py index bb120d0..851e18f 100644 --- a/ingredients/repository.py +++ b/ingredients/repository.py @@ -44,6 +44,14 @@ async def insert_ingredient(conn, ingredient: Ingredient): if not has_product and name == "" and line == "": raise ValueError("Ingredient must include a name or line or a product") + # Enforce positive quantity at repository layer as well + try: + q = float(ingredient.quantity) + except Exception: + q = ingredient.quantity + if isinstance(q, (int, float)) and q <= 0: + raise ValueError("Ingredient quantity must be greater than 0") + async with conn.execute( """ INSERT INTO Ingredient (name, line, preparation, unit, quantity, product_id, recipe_id, meal_id)