munch-ease-backend/main.py

175 lines
No EOL
6.1 KiB
Python

import sqlite3
import products, recipes, db, meals, persons, ingredients
import datetime
from pydantic import BaseModel
from typing import List, Annotated
from fastapi import FastAPI, Depends, Query
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# Add CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"]
)
# Dependency to create SQLite connection
async def get_db():
sql_db = await db.connect()
try:
yield sql_db
finally:
await sql_db.close()
@app.get("/recipes/parse")
async def parse_recipe_handler(url: str, conn: sqlite3.Connection = Depends(get_db)) -> recipes.Recipe:
parsed = await recipes.parse_recipe(conn, url)
if not parsed:
return JSONResponse(status_code=400, content={'message': 'Recipe not found'})
return parsed
@app.get("/recipes/ingredients/parse")
async def parse_ingredients(lines: Annotated[
List[str],
Query(alias="ingredients",
title="Array of ingredients to parse")],
conn: sqlite3.Connection = Depends(get_db)) -> List[ingredients.Ingredient]:
result = ingredients.parse_ingredient_from_nlp(lines)
await ingredients.match_existing_products(conn, result)
return result
class ProductUrl(BaseModel):
url: str
tags: List[str] = []
@app.post("/products/")
async def create_product(url: ProductUrl, conn: sqlite3.Connection = Depends(get_db)) -> products.Product:
return await products.get_or_create(conn, url.url, url.tags)
async def load_full_recipe(conn: sqlite3.Connection, id: int) -> recipes.Recipe:
r = await recipes.find_recipe_by_id(conn, id)
if not r:
return None
r.ingredients = []
async for ingredient in ingredients.find_ingredients_by_recipe_id(conn, id):
ingredient.product = await products.find_product_by_id(conn, ingredient.product_id)
r.ingredients.append(ingredient)
return r
@app.get("/recipes/")
async def get_recipes(q: str | None = None, conn: sqlite3.Connection = Depends(get_db)) -> List[recipes.Recipe]:
result = []
if q:
async for recipe in recipes.find_recipes_by_name(conn, q):
result.append(recipe)
else:
async for recipe in recipes.get_all(conn):
result.append(recipe)
for recipe in result:
recipe.ingredients = []
async for ingredient in ingredients.find_ingredients_by_recipe_id(conn, recipe.id):
ingredient.product = await products.find_product_by_id(conn, ingredient.product_id)
recipe.ingredients.append(ingredient)
return result
@app.get("/recipes/{recipe_id}")
async def get_recipe(recipe_id: int, conn: sqlite3.Connection = Depends(get_db)) -> recipes.Recipe:
r = await load_full_recipe(conn, recipe_id)
if not r:
return JSONResponse(status_code=404, content={'message': 'Recipe not found'})
return r
@app.post('/recipes/')
async def create_recipe(item: recipes.Recipe, conn: sqlite3.Connection = Depends(get_db)) -> recipes.Recipe:
if not item.ingredients:
return JSONResponse(status_code=400, content={'message': 'Recipe must have at least one ingredient'})
for ingredient in item.ingredients:
if not ingredient.product:
return JSONResponse(status_code=400, content={'message': 'Ingredient must have a product'})
await recipes.insert_recipe(conn, item)
for ingredient in item.ingredients:
ingredient.recipe_id = item.id
ingredient.product_id = ingredient.product.id
await ingredients.insert_ingredient(conn, ingredient)
await conn.commit()
return item
@app.get("/meals/")
async def get_meals(date_from: Annotated[datetime.datetime, Query(alias='from')], to: datetime.datetime, conn: sqlite3.Connection = Depends(get_db)) -> List[meals.Meal]:
result = []
for meal in await meals.find_meals_by_date_range(conn, date_from, to):
await meals.load_recipes(conn, meal)
await meals.load_extra_ingredients(conn, meal)
result.append(meal)
return result
@app.get("/meals/{meal_id}")
async def get_meal(meal_id: int, conn: sqlite3.Connection = Depends(get_db)) -> meals.Meal:
meal = await meals.find_meal_by_id(conn, meal_id)
if not meal:
return JSONResponse(status_code=404, content={'message': 'Meal not found'})
await meals.load_participants(conn, meal)
await meals.load_recipes(conn, meal)
await meals.load_extra_ingredients(conn, meal)
return meal
@app.post("/meals/")
async def create_meal(meal: meals.Meal, conn: sqlite3.Connection = Depends(get_db)) -> meals.Meal:
"""
if not meal.chef:
return JSONResponse(status_code=400, content={'message': 'Meal must have at least one chef'})
if not meal.cleanup:
return JSONResponse(status_code=400, content={'message': 'Meal must have at least one cleanup person'})
if not meal.consumers:
return JSONResponse(status_code=400, content={'message': 'Meal must have at least one consumer'})
"""
if len(meal.recipes) == 0 and len(meal.extra_ingredients) == 0:
return JSONResponse(status_code=400, content={'message': 'Meal must have at least one recipe or ingredient'})
await meals.insert_meal(conn, meal)
await conn.commit()
return meal
@app.delete("/meals/{meal_id}")
async def delete_meal(meal_id: int, conn: sqlite3.Connection = Depends(get_db)) -> meals.Meal:
meal = await meals.find_meal_by_id(conn, meal_id)
if not meal:
return JSONResponse(status_code=404, content={'message': 'Meal not found'})
await meals.delete_meal(conn, meal.id)
await conn.commit()
return meal
@app.get("/persons/")
async def get_persons(conn: sqlite3.Connection = Depends(get_db)) -> List[meals.Person]:
result = []
async for person in persons.get_all(conn):
result.append(person)
return result
@app.post("/persons/")
async def create_person(person: persons.Person, conn: sqlite3.Connection = Depends(get_db)) -> persons.Person:
await persons.insert(conn, person)
await conn.commit()
return person