munch-ease-backend/main.py

226 lines
8.1 KiB
Python
Raw Normal View History

2024-01-08 00:45:22 +00:00
import sqlite3
2024-04-25 04:57:39 +00:00
import products, recipes, db as db, meals, persons, ingredients
2024-01-13 07:42:23 +00:00
import datetime
2024-01-08 00:45:22 +00:00
2024-01-13 01:54:04 +00:00
from pydantic import BaseModel
from typing import List, Annotated
2024-04-25 02:03:30 +00:00
from fastapi import FastAPI, Depends, Query, Cookie
2024-01-13 05:40:10 +00:00
from fastapi.responses import JSONResponse
2024-04-25 02:03:30 +00:00
from fastapi.encoders import jsonable_encoder
2024-01-08 00:38:17 +00:00
from fastapi.middleware.cors import CORSMiddleware
2024-01-07 00:33:40 +00:00
app = FastAPI()
2024-01-08 00:38:17 +00:00
# Add CORS middleware
app.add_middleware(
CORSMiddleware,
2024-04-25 04:57:39 +00:00
allow_origins=["*", "http://localhost:8080", "https://localhost:8080"],
2024-01-08 00:38:17 +00:00
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"]
)
2024-01-07 00:33:40 +00:00
2024-01-08 00:38:17 +00:00
# Dependency to create SQLite connection
async def get_db():
sql_db = await db.connect()
try:
yield sql_db
finally:
2024-01-13 01:54:04 +00:00
await sql_db.close()
2024-01-08 00:38:17 +00:00
2024-04-25 02:03:30 +00:00
async def cookie_person(user_id: Annotated[int, Cookie(alias='user_id')], conn: sqlite3.Connection = Depends(get_db)) -> persons.Person:
2024-04-25 04:57:39 +00:00
return await persons.get_by_id(conn, user_id)
2024-04-25 02:03:30 +00:00
2024-01-08 00:38:17 +00:00
@app.get("/recipes/parse")
2024-01-13 07:44:48 +00:00
async def parse_recipe_handler(url: str, conn: sqlite3.Connection = Depends(get_db)) -> recipes.Recipe:
parsed = await recipes.parse_recipe(conn, url)
2024-01-13 05:40:10 +00:00
if not parsed:
return JSONResponse(status_code=400, content={'message': 'Recipe not found'})
return parsed
2024-01-08 00:45:22 +00:00
@app.get("/recipes/ingredients/parse")
2024-01-13 01:54:04 +00:00
async def parse_ingredients(lines: Annotated[
List[str],
Query(alias="ingredients",
title="Array of ingredients to parse")],
2024-01-17 07:21:16 +00:00
conn: sqlite3.Connection = Depends(get_db)) -> List[ingredients.Ingredient]:
2024-01-17 08:36:54 +00:00
result = ingredients.parse_ingredient_from_nlp(lines)
await ingredients.match_existing_products(conn, result)
return result
2024-01-08 00:38:17 +00:00
2024-01-13 01:54:04 +00:00
class ProductUrl(BaseModel):
url: str
tags: List[str] = []
@app.post("/products/")
2024-01-13 07:44:48 +00:00
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)
2024-01-13 05:40:10 +00:00
2024-01-13 07:44:48 +00:00
async def load_full_recipe(conn: sqlite3.Connection, id: int) -> recipes.Recipe:
r = await recipes.find_recipe_by_id(conn, id)
2024-01-13 06:59:35 +00:00
if not r:
2024-01-13 07:42:23 +00:00
return None
2024-01-13 06:59:35 +00:00
r.ingredients = []
2024-01-17 07:21:16 +00:00
async for ingredient in ingredients.find_ingredients_by_recipe_id(conn, id):
2024-01-13 06:59:35 +00:00
r.ingredients.append(ingredient)
2024-04-25 04:57:39 +00:00
r.created_by = await persons.get_by_id(conn, r.created_by_id)
2024-01-13 06:59:35 +00:00
return r
2024-01-13 08:44:07 +00:00
@app.get("/recipes/")
async def get_recipes(q: str | None = None, conn: sqlite3.Connection = Depends(get_db)) -> List[recipes.Recipe]:
2024-01-13 08:44:07 +00:00
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)
2024-01-13 08:44:07 +00:00
for recipe in result:
recipe.ingredients = []
2024-01-17 07:21:16 +00:00
async for ingredient in ingredients.find_ingredients_by_recipe_id(conn, recipe.id):
recipe.ingredients.append(ingredient)
2024-01-13 08:44:07 +00:00
return result
2024-01-13 07:42:23 +00:00
@app.get("/recipes/{recipe_id}")
2024-01-13 07:44:48 +00:00
async def get_recipe(recipe_id: int, conn: sqlite3.Connection = Depends(get_db)) -> recipes.Recipe:
2024-01-13 07:42:23 +00:00
r = await load_full_recipe(conn, recipe_id)
if not r:
return JSONResponse(status_code=404, content={'message': 'Recipe not found'})
return r
2024-01-13 05:40:10 +00:00
@app.post('/recipes/')
2024-04-25 02:03:30 +00:00
async def create_recipe(item: recipes.Recipe, conn: sqlite3.Connection = Depends(get_db), user: persons.Person = Depends(cookie_person)) -> recipes.Recipe:
2024-01-13 05:40:10 +00:00
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'})
2024-04-28 03:57:02 +00:00
if item.id:
await recipes.hide_recipe(conn, item.id, user)
item.based_on_recipe = item.id
item.id = 0
2024-04-25 02:03:30 +00:00
item.created_by_id = user.id
2024-01-13 07:44:48 +00:00
await recipes.insert_recipe(conn, item)
2024-01-13 05:40:10 +00:00
for ingredient in item.ingredients:
ingredient.recipe_id = item.id
ingredient.product_id = ingredient.product.id
2024-01-17 08:36:54 +00:00
await ingredients.insert_ingredient(conn, ingredient)
2024-04-28 03:57:02 +00:00
2024-01-13 05:40:10 +00:00
await conn.commit()
2024-01-13 07:42:23 +00:00
return item
2024-05-02 12:25:11 +00:00
@app.delete('/recipes/{recipe_id}')
async def delete_recipe(recipe_id: int, conn: sqlite3.Connection = Depends(get_db), user: persons.Person = Depends(cookie_person)) -> recipes.Recipe:
recipe = await recipes.find_recipe_by_id(conn, recipe_id)
if not recipe:
return JSONResponse(status_code=404, content={'message': 'Recipe not found'})
await recipes.hide_recipe(conn, recipe_id, user)
await conn.commit()
return recipe
2024-01-13 07:42:23 +00:00
@app.get("/meals/")
2024-01-17 11:09:55 +00:00
async def get_meals(date_from: Annotated[datetime.datetime, Query(alias='from')], to: datetime.datetime, conn: sqlite3.Connection = Depends(get_db)) -> List[meals.Meal]:
2024-01-13 07:42:23 +00:00
result = []
2024-01-17 11:09:55 +00:00
for meal in await meals.find_meals_by_date_range(conn, date_from, to):
2024-01-17 11:43:16 +00:00
await meals.load_recipes(conn, meal)
await meals.load_extra_ingredients(conn, meal)
2024-04-30 11:21:59 +00:00
await meals.load_participants(conn, meal)
2024-01-13 07:42:23 +00:00
result.append(meal)
return result
2024-01-17 10:39:48 +00:00
@app.get("/meals/{meal_id}")
2024-01-17 11:09:55 +00:00
async def get_meal(meal_id: int, conn: sqlite3.Connection = Depends(get_db)) -> meals.Meal:
2024-01-17 10:39:48 +00:00
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)
2024-01-17 11:09:55 +00:00
2024-01-17 10:39:48 +00:00
return meal
2024-01-13 07:42:23 +00:00
@app.post("/meals/")
async def create_meal(meal: meals.Meal, conn: sqlite3.Connection = Depends(get_db)) -> meals.Meal:
2024-04-25 04:57:39 +00:00
if not meal.chefs:
2024-01-13 07:42:23 +00:00
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'})
2024-01-17 07:21:16 +00:00
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'})
2024-01-13 07:42:23 +00:00
await meals.insert_meal(conn, meal)
await conn.commit()
2024-01-13 08:44:07 +00:00
return meal
2024-05-02 11:20:52 +00:00
@app.put("/meals/{meal_id}")
async def update_meal(meal_id: int, meal: meals.Meal, conn: sqlite3.Connection = Depends(get_db)) -> meals.Meal:
if meal.id != meal_id:
return JSONResponse(status_code=400, content={'message': 'Meal ID in URL does not match meal ID in body'})
existing = await meals.find_meal_by_id(conn, meal_id)
if not existing:
return JSONResponse(status_code=404, content={'message': 'Meal not found'})
await meals.update_meal(conn, meal)
await conn.commit()
return await get_meal(meal_id, conn)
2024-01-17 11:43:16 +00:00
@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
2024-01-17 10:39:48 +00:00
2024-01-13 08:44:07 +00:00
@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:
2024-04-28 03:37:01 +00:00
await persons.insert_person(conn, person)
2024-01-13 08:44:07 +00:00
await conn.commit()
2024-04-25 02:03:30 +00:00
return person
class LoginBody(BaseModel):
username: str
@app.post('/auth/login')
async def login(data: LoginBody, conn: sqlite3.Connection = Depends(get_db)) -> persons.Person:
person = await persons.get_by_name(conn, data.username)
if not person:
return JSONResponse(status_code=404, content={'message': 'Person not found'})
response = JSONResponse(content=jsonable_encoder(person))
response.set_cookie(key='user_id', value=str(person.id))
2024-04-28 02:15:19 +00:00
return response
2024-04-30 11:21:59 +00:00
@app.post('/auth/refresh')
2024-04-28 02:15:19 +00:00
async def current_user(user: persons.Person = Depends(cookie_person)) -> persons.Person:
return user