2024-05-13 03:59:46 +00:00
|
|
|
from typing import AsyncIterator, List, ClassVar, Optional
|
2024-01-13 07:18:25 +00:00
|
|
|
from pydantic import BaseModel
|
2024-05-02 11:20:52 +00:00
|
|
|
from ingredients import Ingredient, insert_ingredient, find_ingredients_by_meal_id, delete_ingredients_by_meal_id
|
2024-04-25 04:57:39 +00:00
|
|
|
|
|
|
|
|
from recipes import Recipe, row_to_recipe, load_recipe_ingredients
|
|
|
|
|
|
|
|
|
|
import persons
|
|
|
|
|
from persons import Person
|
2024-01-13 07:18:25 +00:00
|
|
|
|
|
|
|
|
import datetime
|
|
|
|
|
|
|
|
|
|
class Meal(BaseModel):
|
2024-05-25 02:09:32 +00:00
|
|
|
KEYS: ClassVar[List[str]] = ['id', 'suggested_date']
|
2024-05-20 10:09:57 +00:00
|
|
|
id: int = -1
|
2024-05-25 02:09:32 +00:00
|
|
|
suggested_date: datetime.datetime
|
2024-05-20 23:57:56 +00:00
|
|
|
purchase_date: Optional[datetime.datetime] = None
|
2024-04-25 04:57:39 +00:00
|
|
|
|
2024-01-17 11:43:16 +00:00
|
|
|
chefs: List[Person] = []
|
2024-01-13 07:18:25 +00:00
|
|
|
cleanup: List[Person] = []
|
|
|
|
|
consumers: List[Person] = []
|
2024-01-17 11:09:55 +00:00
|
|
|
recipes: List[Recipe] = []
|
2024-01-17 07:21:16 +00:00
|
|
|
extra_ingredients: List[Ingredient] = []
|
2024-01-13 07:18:25 +00:00
|
|
|
|
|
|
|
|
async def create(conn):
|
|
|
|
|
await conn.execute('''
|
|
|
|
|
CREATE TABLE IF NOT EXISTS Meal (
|
|
|
|
|
id INTEGER PRIMARY KEY,
|
2024-05-25 02:09:32 +00:00
|
|
|
suggested_date TEXT
|
2024-01-13 07:18:25 +00:00
|
|
|
);''')
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
);''')
|
|
|
|
|
|
2024-01-13 07:42:23 +00:00
|
|
|
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))
|
|
|
|
|
|
2024-05-02 11:20:52 +00:00
|
|
|
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)
|
|
|
|
|
|
2024-01-17 10:39:48 +00:00
|
|
|
async def insert_meal_recipe(conn, meal_id: int, recipe_id: int):
|
2024-05-20 10:09:57 +00:00
|
|
|
if recipe_id < 0:
|
2024-04-25 04:57:39 +00:00
|
|
|
raise ValueError('Recipe must be inserted before meal')
|
|
|
|
|
|
2024-01-17 10:39:48 +00:00
|
|
|
await conn.execute('''
|
|
|
|
|
INSERT INTO MealRecipe (meal_id, recipe_id)
|
|
|
|
|
VALUES (?, ?)
|
|
|
|
|
''', (meal_id, recipe_id))
|
|
|
|
|
|
2024-01-13 07:18:25 +00:00
|
|
|
async def insert_meal(conn, meal: Meal):
|
|
|
|
|
async with conn.execute('''
|
2024-05-25 02:09:32 +00:00
|
|
|
INSERT INTO Meal (suggested_date)
|
2024-01-13 07:18:25 +00:00
|
|
|
VALUES (?)
|
2024-05-25 02:09:32 +00:00
|
|
|
''', (meal.suggested_date,)) as cursor:
|
2024-01-13 07:42:23 +00:00
|
|
|
meal.id = cursor.lastrowid
|
|
|
|
|
|
2024-05-02 11:20:52 +00:00
|
|
|
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')
|
2024-01-13 07:18:25 +00:00
|
|
|
|
2024-01-17 10:39:48 +00:00
|
|
|
for recipe in meal.recipes:
|
|
|
|
|
await insert_meal_recipe(conn, meal.id, recipe.id)
|
|
|
|
|
|
2024-05-02 11:20:52 +00:00
|
|
|
await sync_extra_ingredients(conn, meal.id, meal.extra_ingredients)
|
2024-01-17 10:39:48 +00:00
|
|
|
|
2024-01-13 07:18:25 +00:00
|
|
|
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:
|
2024-05-20 23:57:56 +00:00
|
|
|
meal = await with_purchase_date(conn, Meal(**{k:v for k,v in zip(Meal.KEYS, row)}))
|
|
|
|
|
|
2024-05-18 07:05:01 +00:00
|
|
|
await load_participants(conn, meal)
|
|
|
|
|
await load_recipes(conn, meal)
|
|
|
|
|
await load_extra_ingredients(conn, meal)
|
|
|
|
|
return meal
|
2024-01-13 07:18:25 +00:00
|
|
|
|
2024-05-13 03:59:46 +00:00
|
|
|
async def find_meals_by_date_range(conn, start: datetime, end: datetime) -> AsyncIterator[Meal]:
|
2024-01-13 07:42:23 +00:00
|
|
|
async with conn.execute(f'''
|
|
|
|
|
SELECT {','.join(Meal.KEYS)} FROM Meal
|
2024-05-25 02:09:32 +00:00
|
|
|
WHERE suggested_date >= ? AND suggested_date <= ?
|
2024-01-13 07:42:23 +00:00
|
|
|
''', (start, end)) as cursor:
|
|
|
|
|
async for row in cursor:
|
2024-05-20 23:57:56 +00:00
|
|
|
yield await with_purchase_date(conn, Meal(**{k:v for k,v in zip(Meal.KEYS, row)}))
|
2024-01-13 07:42:23 +00:00
|
|
|
|
|
|
|
|
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:
|
2024-04-25 04:57:39 +00:00
|
|
|
person = await persons.get_by_id(conn, row[0])
|
2024-01-13 07:42:23 +00:00
|
|
|
if row[1] == 'chef':
|
2024-01-17 11:43:16 +00:00
|
|
|
meal.chefs.append(person)
|
2024-01-13 07:42:23 +00:00
|
|
|
elif row[1] == 'cleanup':
|
|
|
|
|
meal.cleanup.append(person)
|
|
|
|
|
elif row[1] == 'consumer':
|
|
|
|
|
meal.consumers.append(person)
|
|
|
|
|
else:
|
2024-01-17 10:39:48 +00:00
|
|
|
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:
|
2024-04-25 04:57:39 +00:00
|
|
|
recipe = row_to_recipe(zip(Recipe.KEYS, row))
|
|
|
|
|
await load_recipe_ingredients(conn, recipe)
|
|
|
|
|
|
|
|
|
|
meal.recipes.append(recipe)
|
2024-01-17 10:39:48 +00:00
|
|
|
|
|
|
|
|
async def load_extra_ingredients(conn, meal: Meal) -> None:
|
2024-01-17 12:51:07 +00:00
|
|
|
async for ingredient in find_ingredients_by_meal_id(conn, meal.id):
|
|
|
|
|
meal.extra_ingredients.append(ingredient)
|
2024-01-17 11:43:16 +00:00
|
|
|
|
|
|
|
|
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 = ?
|
2024-05-02 11:20:52 +00:00
|
|
|
''', (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
|
2024-05-25 02:09:32 +00:00
|
|
|
SET suggested_date = ?
|
2024-05-02 11:20:52 +00:00
|
|
|
WHERE id = ?
|
2024-05-25 02:09:32 +00:00
|
|
|
''', (meal.suggested_date, meal.id))
|
2024-05-02 11:20:52 +00:00
|
|
|
|
|
|
|
|
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)
|
2024-05-20 23:57:56 +00:00
|
|
|
|
|
|
|
|
async def with_purchase_date(conn, meal: Meal) -> Meal:
|
|
|
|
|
async with conn.execute('''
|
|
|
|
|
SELECT purchased_date FROM ShoppingList
|
|
|
|
|
WHERE id = (
|
|
|
|
|
SELECT list_id FROM ShoppingListRequest
|
|
|
|
|
WHERE meal_id = ?
|
|
|
|
|
LIMIT 1
|
|
|
|
|
)
|
|
|
|
|
''', (meal.id,)) as cursor:
|
|
|
|
|
async for row in cursor:
|
|
|
|
|
meal.purchase_date = row[0]
|
|
|
|
|
|
|
|
|
|
return meal
|