munch-ease-backend/meals/db.py
2024-05-02 21:20:52 +10:00

198 lines
6.5 KiB
Python

from typing import List, ClassVar, Optional
from pydantic import BaseModel
from ingredients import Ingredient, insert_ingredient, find_ingredients_by_meal_id, delete_ingredients_by_meal_id
from recipes import Recipe, row_to_recipe, load_recipe_ingredients
import persons
from persons import Person
import datetime
class Meal(BaseModel):
KEYS: ClassVar[List[str]] = ['id', 'meal_date']
id: int
meal_date: datetime.datetime
chefs: List[Person] = []
cleanup: List[Person] = []
consumers: List[Person] = []
recipes: List[Recipe] = []
extra_ingredients: List[Ingredient] = []
async def create(conn):
await conn.execute('''
CREATE TABLE IF NOT EXISTS Meal (
id INTEGER PRIMARY KEY,
meal_date TEXT
);''')
await conn.execute('''
CREATE TABLE IF NOT EXISTS MealParticipant (
meal_id INTEGER,
person_id INTEGER,
role TEXT,
FOREIGN KEY(meal_id) REFERENCES Meal(id),
FOREIGN KEY(person_id) REFERENCES Person(id)
);''')
await conn.execute('''
CREATE TABLE IF NOT EXISTS MealRecipe (
meal_id INTEGER,
recipe_id INTEGER,
FOREIGN KEY(meal_id) REFERENCES Meal(id),
FOREIGN KEY(recipe_id) REFERENCES Recipe(id)
);''')
async def insert_meal_participant(conn, meal_id: int, person_id: int, role: str):
await conn.execute('''
INSERT INTO MealParticipant (meal_id, person_id, role)
VALUES (?, ?, ?)
''', (meal_id, person_id, role))
async def sync_meal_participants(conn, meal_id: int, participants: List[Person], role: str):
await conn.execute('''
DELETE FROM MealParticipant
WHERE meal_id = ? AND role = ?
''', (meal_id, role))
for person in participants:
await insert_meal_participant(conn, meal_id, person.id, role)
async def insert_meal_recipe(conn, meal_id: int, recipe_id: int):
if not recipe_id:
raise ValueError('Recipe must be inserted before meal')
await conn.execute('''
INSERT INTO MealRecipe (meal_id, recipe_id)
VALUES (?, ?)
''', (meal_id, recipe_id))
async def insert_meal(conn, meal: Meal):
async with conn.execute('''
INSERT INTO Meal (meal_date)
VALUES (?)
''', (meal.meal_date,)) as cursor:
meal.id = cursor.lastrowid
await sync_meal_participants(conn, meal.id, meal.chefs, 'chef')
await sync_meal_participants(conn, meal.id, meal.cleanup, 'cleanup')
await sync_meal_participants(conn, meal.id, meal.consumers, 'consumer')
for recipe in meal.recipes:
await insert_meal_recipe(conn, meal.id, recipe.id)
await sync_extra_ingredients(conn, meal.id, meal.extra_ingredients)
async def find_meal_by_id(conn, meal_id: int) -> Meal:
async with conn.execute(f'''
SELECT {','.join(Meal.KEYS)} FROM Meal
WHERE id = ?
LIMIT 1
''', (meal_id,)) as cursor:
async for row in cursor:
return Meal(**{k:v for k,v in zip(Meal.KEYS, row)})
async def find_meal_by_date(conn, date: datetime) -> Meal:
async with conn.execute(f'''
SELECT {','.join(Meal.KEYS)} FROM Meal
WHERE meal_date = ?
LIMIT 1
''', (date,)) as cursor:
async for row in cursor:
return Meal(**{k:v for k,v in zip(Meal.KEYS, row)})
async def find_meals_by_date_range(conn, start: datetime, end: datetime) -> List[Meal]:
async with conn.execute(f'''
SELECT {','.join(Meal.KEYS)} FROM Meal
WHERE meal_date >= ? AND meal_date <= ?
''', (start, end)) as cursor:
result = []
async for row in cursor:
result.append(Meal(**{k:v for k,v in zip(Meal.KEYS, row)}))
return result
async def load_participants(conn, meal: Meal) -> None:
async with conn.execute(f'''
SELECT person_id, role FROM MealParticipant
WHERE meal_id = ?
''', (meal.id,)) as cursor:
async for row in cursor:
person = await persons.get_by_id(conn, row[0])
if row[1] == 'chef':
meal.chefs.append(person)
elif row[1] == 'cleanup':
meal.cleanup.append(person)
elif row[1] == 'consumer':
meal.consumers.append(person)
else:
raise Exception(f'Unknown role: {row[1]}')
async def load_recipes(conn, meal: Meal) -> None:
async with conn.execute(f'''
SELECT {','.join(Recipe.KEYS)} FROM Recipe
JOIN MealRecipe ON MealRecipe.recipe_id = Recipe.id
WHERE MealRecipe.meal_id = ?
''', (meal.id,)) as cursor:
async for row in cursor:
recipe = row_to_recipe(zip(Recipe.KEYS, row))
await load_recipe_ingredients(conn, recipe)
meal.recipes.append(recipe)
async def load_extra_ingredients(conn, meal: Meal) -> None:
async for ingredient in find_ingredients_by_meal_id(conn, meal.id):
meal.extra_ingredients.append(ingredient)
async def delete_meal(conn, meal_id: int) -> None:
await conn.execute('''
DELETE FROM MealParticipant
WHERE meal_id = ?
''', (meal_id,))
await conn.execute('''
DELETE FROM MealRecipe
WHERE meal_id = ?
''', (meal_id,))
await conn.execute('''
DELETE FROM Ingredient
WHERE meal_id = ?
''', (meal_id,))
await conn.execute('''
DELETE FROM Meal
WHERE id = ?
''', (meal_id,))
async def sync_extra_ingredients(conn, meal_id: int, ingredients: List[Ingredient]) -> None:
await delete_ingredients_by_meal_id(conn, meal_id)
for ingredient in ingredients:
ingredient.meal_id = meal_id
ingredient.recipe_id = None
await insert_ingredient(conn, ingredient)
async def sync_recipes(conn, meal_id: int, recipes: List[Recipe]) -> None:
await conn.execute('''
DELETE FROM MealRecipe
WHERE meal_id = ?
''', (meal_id,))
for recipe in recipes:
await insert_meal_recipe(conn, meal_id, recipe.id)
async def update_meal(conn, meal: Meal) -> None:
await conn.execute('''
UPDATE Meal
SET meal_date = ?
WHERE id = ?
''', (meal.meal_date, meal.id))
await sync_meal_participants(conn, meal.id, meal.chefs, 'chef')
await sync_meal_participants(conn, meal.id, meal.cleanup, 'cleanup')
await sync_meal_participants(conn, meal.id, meal.consumers, 'consumer')
await sync_extra_ingredients(conn, meal.id, meal.extra_ingredients)
await sync_recipes(conn, meal.id, meal.recipes)