chore: initial project setup with backend, frontend, and infrastructure
Add complete NexaPantry application structure including: - Docker Compose configuration with PostgreSQL, Redis, FastAPI backend, worker, frontend and Caddy - Environment configuration template with database, auth, and service settings - GitHub Actions CI workflow for backend/frontend linting, testing, auditing and Docker builds - AGPL-3.0 license and comprehensive README with setup, development, and security documentation - Backend
This commit is contained in:
51
backend/app/services/products.py
Normal file
51
backend/app/services/products.py
Normal file
@@ -0,0 +1,51 @@
|
||||
from datetime import date
|
||||
from typing import Protocol
|
||||
|
||||
import httpx
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models.entities import Home, Product
|
||||
|
||||
|
||||
def expiry_status(product: Product, home: Home) -> str:
|
||||
if not product.expires_at:
|
||||
return "ok"
|
||||
today = date.today()
|
||||
if product.expires_at <= today:
|
||||
return "expired"
|
||||
if (product.expires_at - today).days <= home.expiry_warning_days:
|
||||
return "soon"
|
||||
return "ok"
|
||||
|
||||
|
||||
class ProductLookup(Protocol):
|
||||
async def by_barcode(self, barcode: str) -> dict | None:
|
||||
...
|
||||
|
||||
|
||||
class OpenFoodFactsLookup(ProductLookup):
|
||||
async def by_barcode(self, barcode: str) -> dict | None:
|
||||
url = f"https://world.openfoodfacts.org/api/v2/product/{barcode}.json"
|
||||
async with httpx.AsyncClient(timeout=8) as client:
|
||||
response = await client.get(url)
|
||||
if response.status_code != 200:
|
||||
return None
|
||||
data = response.json()
|
||||
product = data.get("product")
|
||||
if not product:
|
||||
return None
|
||||
return {
|
||||
"name": product.get("product_name") or product.get("generic_name") or "",
|
||||
"brand": product.get("brands"),
|
||||
"category": (product.get("categories_tags") or ["Other"])[0].replace("en:", ""),
|
||||
"image_url": product.get("image_front_small_url") or product.get("image_url"),
|
||||
"barcode": barcode,
|
||||
}
|
||||
|
||||
|
||||
def low_stock_products(db: Session, home_id: str) -> list[Product]:
|
||||
return [
|
||||
product
|
||||
for product in db.query(Product).filter(Product.home_id == home_id).all()
|
||||
if product.quantity <= product.min_quantity
|
||||
]
|
||||
Reference in New Issue
Block a user