feat: autoayuno-api (FastAPI) — alimentos (OFF) y recetas
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
141
main.py
Normal file
141
main.py
Normal file
@@ -0,0 +1,141 @@
|
||||
"""API de autoayuno: catálogo compartido de alimentos (alimentado de Open Food
|
||||
Facts bajo demanda) y recetas publicables. Sin cuentas: publicar es anónimo con
|
||||
un deviceToken."""
|
||||
import uuid
|
||||
|
||||
from fastapi import Depends, FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
import off
|
||||
from db import Base, engine, get_db
|
||||
from models import Food, Recipe
|
||||
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
app = FastAPI(title="autoayuno-api")
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
def health():
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Alimentos
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class FoodIn(BaseModel):
|
||||
name: str
|
||||
kcalPer100g: float
|
||||
zone: str = "otros"
|
||||
portions: list = []
|
||||
barcode: str | None = None
|
||||
deviceToken: str | None = None
|
||||
|
||||
|
||||
@app.get("/foods")
|
||||
def search_foods(q: str = "", limit: int = 20, db: Session = Depends(get_db)):
|
||||
q = (q or "").strip()
|
||||
if len(q) < 2:
|
||||
return []
|
||||
local = (
|
||||
db.query(Food)
|
||||
.filter(func.lower(Food.name).like(f"%{q.lower()}%"))
|
||||
.limit(limit)
|
||||
.all()
|
||||
)
|
||||
results = [f.to_app() for f in local]
|
||||
|
||||
# Si hay pocos, enriquecemos con Open Food Facts y guardamos.
|
||||
if len(local) < 5:
|
||||
seen_off = {f.off_id for f in local if f.off_id}
|
||||
seen_names = {f.name.lower() for f in local}
|
||||
for item in off.search(q):
|
||||
if item["off_id"] and item["off_id"] in seen_off:
|
||||
continue
|
||||
if item["name"].lower() in seen_names:
|
||||
continue
|
||||
# Dedupe contra la BD por off_id.
|
||||
if item["off_id"] and db.query(Food).filter(Food.off_id == item["off_id"]).first():
|
||||
continue
|
||||
food = Food(
|
||||
id=str(uuid.uuid4()),
|
||||
name=item["name"],
|
||||
kcal_100g=item["kcal_100g"],
|
||||
zone="otros",
|
||||
portions=[],
|
||||
barcode=item.get("barcode"),
|
||||
source="off",
|
||||
off_id=item.get("off_id"),
|
||||
)
|
||||
db.add(food)
|
||||
seen_names.add(item["name"].lower())
|
||||
results.append(food.to_app())
|
||||
db.commit()
|
||||
return results[:limit]
|
||||
|
||||
|
||||
@app.post("/foods")
|
||||
def add_food(body: FoodIn, db: Session = Depends(get_db)):
|
||||
food = Food(
|
||||
id=str(uuid.uuid4()),
|
||||
name=body.name.strip()[:120],
|
||||
kcal_100g=body.kcalPer100g,
|
||||
zone=body.zone,
|
||||
portions=body.portions or [],
|
||||
barcode=body.barcode,
|
||||
source="user",
|
||||
author_token=body.deviceToken,
|
||||
)
|
||||
db.add(food)
|
||||
db.commit()
|
||||
return food.to_app()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Recetas
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class RecipeIn(BaseModel):
|
||||
name: str
|
||||
servings: int = 1
|
||||
items: list = []
|
||||
deviceToken: str | None = None
|
||||
|
||||
|
||||
@app.get("/recipes")
|
||||
def search_recipes(q: str = "", limit: int = 20, db: Session = Depends(get_db)):
|
||||
query = db.query(Recipe)
|
||||
q = (q or "").strip()
|
||||
if q:
|
||||
query = query.filter(func.lower(Recipe.name).like(f"%{q.lower()}%"))
|
||||
return [r.to_app() for r in query.limit(limit).all()]
|
||||
|
||||
|
||||
@app.get("/recipes/{recipe_id}")
|
||||
def get_recipe(recipe_id: str, db: Session = Depends(get_db)):
|
||||
r = db.query(Recipe).filter(Recipe.id == recipe_id).first()
|
||||
return r.to_app() if r else {}
|
||||
|
||||
|
||||
@app.post("/recipes")
|
||||
def add_recipe(body: RecipeIn, db: Session = Depends(get_db)):
|
||||
r = Recipe(
|
||||
id=str(uuid.uuid4()),
|
||||
name=body.name.strip()[:140],
|
||||
servings=body.servings,
|
||||
items=body.items or [],
|
||||
author_token=body.deviceToken,
|
||||
)
|
||||
db.add(r)
|
||||
db.commit()
|
||||
return r.to_app()
|
||||
Reference in New Issue
Block a user