Compare commits

...

3 commits

Author SHA1 Message Date
407d649d99 Consumed meal fixes 2024-05-27 21:55:47 +10:00
dc83b0da9b Added consumed functionality 2024-05-25 12:33:41 +10:00
d6b667463b meal_date to suggested_date 2024-05-25 12:09:32 +10:00
6 changed files with 88 additions and 23 deletions

18
main.py
View file

@ -3,7 +3,7 @@ import products, recipes, db, meals, persons, ingredients, shopping
import datetime
from pydantic import BaseModel
from typing import List, Annotated, Union
from typing import List, Annotated, Optional, Union
from fastapi import FastAPI, Depends, Query, Cookie
from fastapi.responses import JSONResponse
from fastapi.encoders import jsonable_encoder
@ -146,10 +146,10 @@ async def delete_recipe(recipe_id: int, conn: sqlite3.Connection = Depends(get_d
await conn.commit()
return recipe
@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]:
@app.get("/meals/upcoming")
async def get_upcoming_meals(date_from: Annotated[datetime.datetime, Query(alias='from')], to: datetime.datetime, conn: sqlite3.Connection = Depends(get_db)) -> List[meals.Meal]:
result = []
async for meal in meals.find_meals_by_date_range(conn, date_from, to):
async for meal in meals.find_upcoming_meals_by_date_range(conn, date_from, to):
await meals.load_recipes(conn, meal)
await meals.load_extra_ingredients(conn, meal)
await meals.load_participants(conn, meal)
@ -229,6 +229,16 @@ async def update_meal(meal_id: int, meal: meals.Meal, conn: sqlite3.Connection =
return await get_meal(meal_id, conn)
@app.post("/meals/{meal_id}/consumed")
async def mark_consumed(meal_id: int, consumed_date: Optional[datetime.datetime] = None, conn: sqlite3.Connection = Depends(get_db), person: persons.Person = Depends(cookie_person)) -> 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.mark_consumed(conn, meal, consumed_date or datetime.datetime.now())
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)

View file

@ -10,10 +10,10 @@ from persons import Person
import datetime
class Meal(BaseModel):
KEYS: ClassVar[List[str]] = ['id', 'meal_date']
KEYS: ClassVar[List[str]] = ['id', 'suggested_date', 'consumed_date']
id: int = -1
meal_date: datetime.datetime
purchase_date: Optional[datetime.datetime] = None
suggested_date: datetime.datetime
consumed_date: Optional[datetime.datetime] = None
chefs: List[Person] = []
cleanup: List[Person] = []
@ -21,11 +21,15 @@ class Meal(BaseModel):
recipes: List[Recipe] = []
extra_ingredients: List[Ingredient] = []
# Set from shopping list
purchase_date: Optional[datetime.datetime] = None
async def create(conn):
await conn.execute('''
CREATE TABLE IF NOT EXISTS Meal (
id INTEGER PRIMARY KEY,
meal_date TEXT
suggested_date TEXT,
consumed_date TEXT
);''')
await conn.execute('''
@ -71,9 +75,9 @@ async def insert_meal_recipe(conn, meal_id: int, recipe_id: int):
async def insert_meal(conn, meal: Meal):
async with conn.execute('''
INSERT INTO Meal (meal_date)
INSERT INTO Meal (suggested_date)
VALUES (?)
''', (meal.meal_date,)) as cursor:
''', (meal.suggested_date,)) as cursor:
meal.id = cursor.lastrowid
await sync_meal_participants(conn, meal.id, meal.chefs, 'chef')
@ -99,10 +103,10 @@ async def find_meal_by_id(conn, meal_id: int) -> Meal:
await load_extra_ingredients(conn, meal)
return meal
async def find_meals_by_date_range(conn, start: datetime, end: datetime) -> AsyncIterator[Meal]:
async def find_upcoming_meals_by_date_range(conn, start: datetime, end: datetime) -> AsyncIterator[Meal]:
async with conn.execute(f'''
SELECT {','.join(Meal.KEYS)} FROM Meal
WHERE meal_date >= ? AND meal_date <= ?
WHERE suggested_date >= ? AND suggested_date <= ? AND consumed_date IS NULL
''', (start, end)) as cursor:
async for row in cursor:
yield await with_purchase_date(conn, Meal(**{k:v for k,v in zip(Meal.KEYS, row)}))
@ -181,9 +185,9 @@ async def sync_recipes(conn, meal_id: int, recipes: List[Recipe]) -> None:
async def update_meal(conn, meal: Meal) -> None:
await conn.execute('''
UPDATE Meal
SET meal_date = ?
SET suggested_date = ?
WHERE id = ?
''', (meal.meal_date, meal.id))
''', (meal.suggested_date, meal.id))
await sync_meal_participants(conn, meal.id, meal.chefs, 'chef')
await sync_meal_participants(conn, meal.id, meal.cleanup, 'cleanup')
@ -192,6 +196,15 @@ async def update_meal(conn, meal: Meal) -> None:
await sync_extra_ingredients(conn, meal.id, meal.extra_ingredients)
await sync_recipes(conn, meal.id, meal.recipes)
async def mark_consumed(conn, meal: Meal, date: datetime.datetime = datetime.datetime.now()) -> None:
meal.consumed_date = date
await conn.execute('''
UPDATE Meal
SET consumed_date = ?
WHERE id = ?
''', (date, meal.id))
async def with_purchase_date(conn, meal: Meal) -> Meal:
async with conn.execute('''
SELECT purchased_date FROM ShoppingList

View file

@ -1,4 +1,4 @@
from meals import Meal, find_meals_by_date_range, find_meal_by_id
from meals import Meal, find_upcoming_meals_by_date_range, find_meal_by_id
from ingredients import Ingredient, insert_ingredient
from persons import Person
from products import Product
@ -245,7 +245,7 @@ async def load_shopping_list(conn, id: int) -> ShoppingList:
async def _upcoming_meals(conn) -> AsyncIterator[Meal]:
start = datetime.now()
end = start + timedelta(days=7)
async for meal in find_meals_by_date_range(conn, start, end):
async for meal in find_upcoming_meals_by_date_range(conn, start, end):
if meal.purchase_date is None:
yield meal

View file

@ -231,7 +231,7 @@ class Meals:
id=0,
create_date=datetime(2021, 12, 25),
created_by=Persons.jacob,
meal_date=datetime(2021, 12, 25),
suggested_date=datetime(2021, 12, 25),
chefs=[Persons.jacob],
cleanup=[Persons.ryan],
consumers=[Persons.ellie, Persons.chris],

View file

@ -191,7 +191,7 @@ class TestMeals(unittest.IsolatedAsyncioTestCase):
self.assertIsNotNone(meal_by_id)
self.assertEqual(meal_by_id.id, meal.id)
self.assertEqual(meal_by_id.meal_date, meal.meal_date)
self.assertEqual(meal_by_id.suggested_date, meal.suggested_date)
self.assertEqual(len(meal_by_id.chefs), 1)
self.assertEqual(meal_by_id.chefs[0].id, meal.chefs[0].id)
self.assertEqual(meal_by_id.chefs[0].name, meal.chefs[0].name)
@ -214,12 +214,12 @@ class TestMeals(unittest.IsolatedAsyncioTestCase):
self.assertEqual(meal_by_id.extra_ingredients[0].id, extra_ingredient.id)
self.assertEqual(meal_by_id.extra_ingredients[0].line, extra_ingredient.line)
meals_by_date_range = await main.get_meals(meal.meal_date, meal.meal_date, self.conn)
meals_by_date_range = await main.get_upcoming_meals(meal.suggested_date, meal.suggested_date, self.conn)
self.assertIsNotNone(meals_by_date_range)
self.assertIsInstance(meals_by_date_range, list, msg=meals_by_date_range.body if hasattr(meals_by_date_range, 'body') else meals_by_date_range)
self.assertEqual(len(meals_by_date_range), 1)
self.assertEqual(meals_by_date_range[0].id, meal.id)
self.assertEqual(meals_by_date_range[0].meal_date, meal.meal_date)
self.assertEqual(meals_by_date_range[0].suggested_date, meal.suggested_date)
self.assertEqual(len(meals_by_date_range[0].chefs), 1)
self.assertEqual(meals_by_date_range[0].chefs[0].id, meal.chefs[0].id)
self.assertEqual(meals_by_date_range[0].chefs[0].name, meal.chefs[0].name)
@ -230,6 +230,48 @@ class TestMeals(unittest.IsolatedAsyncioTestCase):
self.assertEqual(meals_by_date_range[0].recipes[0].id, recipe.id)
self.assertEqual(meals_by_date_range[0].recipes[0].name, recipe.name)
async def testMarkConsumed(self) -> None:
meal = test_data.Meals.broccoli_soup_for_jacob
recipe = meal.recipes[0]
recipe_ingredient = recipe.ingredients[0]
recipe_product = recipe_ingredient.product
extra_ingredient = meal.extra_ingredients[0]
extra_product = extra_ingredient.product
person = test_data.Persons.jacob
await products_db.insert_product(self.conn, recipe_product, {})
await products_db.insert_product(self.conn, extra_product, {})
created_recipe = await main.create_recipe(recipe, self.conn, person)
recipe.id = created_recipe.id
create_response = await main.create_meal(meal, self.conn)
self.assertIsNotNone(create_response)
self.assertIsInstance(create_response, meals.Meal, msg=create_response.body if hasattr(create_response, 'body') else create_response)
meal_by_id = await main.get_meal(meal.id, self.conn)
self.assertIsNotNone(meal_by_id)
self.assertIsInstance(meal_by_id, meals.Meal, msg=meal_by_id.body if hasattr(meal_by_id, 'body') else meal_by_id)
self.assertIsNone(meal_by_id.consumed_date)
updated_meal = await main.mark_consumed(meal_id=meal.id, conn=self.conn)
self.assertIsNotNone(updated_meal)
self.assertIsInstance(updated_meal, meals.Meal, msg=updated_meal.body if hasattr(updated_meal, 'body') else updated_meal)
self.assertIsNotNone(updated_meal.consumed_date)
self.assertLessEqual(datetime.datetime.now() - updated_meal.consumed_date, datetime.timedelta(seconds=1))
upcoming_meals = await main.get_upcoming_meals(meal.suggested_date, meal.suggested_date, self.conn)
self.assertIsNotNone(upcoming_meals)
self.assertIsInstance(upcoming_meals, list, msg=upcoming_meals.body if hasattr(upcoming_meals, 'body') else upcoming_meals)
self.assertEqual(len(upcoming_meals), 0)
meal_by_id = await main.get_meal(meal.id, self.conn)
self.assertIsNotNone(meal_by_id)
self.assertIsInstance(meal_by_id, meals.Meal, msg=meal_by_id.body if hasattr(meal_by_id, 'body') else meal_by_id)
self.assertIsNotNone(meal_by_id.consumed_date)
self.assertLessEqual(datetime.datetime.now() - meal_by_id.consumed_date, datetime.timedelta(seconds=1))
async def testUpdate(self) -> None:
meal = test_data.Meals.broccoli_soup_for_jacob
@ -251,7 +293,7 @@ class TestMeals(unittest.IsolatedAsyncioTestCase):
self.assertIsNotNone(saved_meal)
self.assertIsInstance(saved_meal, meals.Meal, msg=saved_meal.body if hasattr(saved_meal, 'body') else saved_meal)
saved_meal.meal_date = datetime.datetime.fromisoformat('2021-01-01T12:00:00')
saved_meal.suggested_date = datetime.datetime.fromisoformat('2021-01-01T12:00:00')
saved_meal.recipes = [test_data.Recipes.how_to_steam_green_beans]
saved_meal.extra_ingredients.append(test_data.Ingredients.butter)
saved_meal.chefs.append(test_data.Persons.ryan)
@ -263,7 +305,7 @@ class TestMeals(unittest.IsolatedAsyncioTestCase):
self.assertIsInstance(updated_meal, meals.Meal, msg=updated_meal.body if hasattr(updated_meal, 'body') else updated_meal)
self.assertEqual(updated_meal.id, saved_meal.id)
self.assertEqual(updated_meal.meal_date, saved_meal.meal_date)
self.assertEqual(updated_meal.suggested_date, saved_meal.suggested_date)
self.assertPersons(updated_meal.chefs, saved_meal.chefs)
self.assertPersons(updated_meal.cleanup, saved_meal.cleanup)
self.assertPersons(updated_meal.consumers, saved_meal.consumers)

View file

@ -45,7 +45,7 @@ class TestShopping(unittest.IsolatedAsyncioTestCase):
for recipe in meal.recipes:
await recipes.insert_recipe(self.conn, recipe)
meal.meal_date = datetime.now() + timedelta(days=1)
meal.suggested_date = datetime.now() + timedelta(days=1)
await meals.insert_meal(self.conn, meal)
shopping_list = await shopping.current_shopping_list(self.conn)