"""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()