Added consumed functionality

This commit is contained in:
jableader 2024-05-25 12:33:41 +10:00
parent d6b667463b
commit dc83b0da9b
4 changed files with 81 additions and 12 deletions

22
main.py
View file

@ -3,7 +3,7 @@ import products, recipes, db, meals, persons, ingredients, shopping
import datetime import datetime
from pydantic import BaseModel 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 import FastAPI, Depends, Query, Cookie
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from fastapi.encoders import jsonable_encoder 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() await conn.commit()
return recipe return recipe
@app.get("/meals/") @app.get("/meals/upcoming")
async def get_meals(date_from: Annotated[datetime.datetime, Query(alias='from')], to: datetime.datetime, conn: sqlite3.Connection = Depends(get_db)) -> List[meals.Meal]: 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 = [] 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_recipes(conn, meal)
await meals.load_extra_ingredients(conn, meal) await meals.load_extra_ingredients(conn, meal)
await meals.load_participants(conn, meal) await meals.load_participants(conn, meal)
@ -229,6 +229,20 @@ async def update_meal(meal_id: int, meal: meals.Meal, conn: sqlite3.Connection =
return await get_meal(meal_id, conn) return await get_meal(meal_id, conn)
class ConsumedWrapper(BaseModel):
meal_id: int
consumed_date: Optional[datetime.datetime] = None
@app.post("/meals/{meal_id}/consumed")
async def mark_consumed(body: ConsumedWrapper, conn: sqlite3.Connection = Depends(get_db), person: persons.Person = Depends(cookie_person)) -> meals.Meal:
meal = await meals.find_meal_by_id(conn, body.meal_id)
if not meal:
return JSONResponse(status_code=404, content={'message': 'Meal not found'})
await meals.mark_consumed(conn, meal, body.consumed_date or datetime.datetime.now())
await conn.commit()
return meal
@app.delete("/meals/{meal_id}") @app.delete("/meals/{meal_id}")
async def delete_meal(meal_id: int, conn: sqlite3.Connection = Depends(get_db)) -> meals.Meal: 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) meal = await meals.find_meal_by_id(conn, meal_id)

View file

@ -10,10 +10,10 @@ from persons import Person
import datetime import datetime
class Meal(BaseModel): class Meal(BaseModel):
KEYS: ClassVar[List[str]] = ['id', 'suggested_date'] KEYS: ClassVar[List[str]] = ['id', 'suggested_date', 'consumed_date']
id: int = -1 id: int = -1
suggested_date: datetime.datetime suggested_date: datetime.datetime
purchase_date: Optional[datetime.datetime] = None consumed_date: Optional[datetime.datetime] = None
chefs: List[Person] = [] chefs: List[Person] = []
cleanup: List[Person] = [] cleanup: List[Person] = []
@ -21,11 +21,15 @@ class Meal(BaseModel):
recipes: List[Recipe] = [] recipes: List[Recipe] = []
extra_ingredients: List[Ingredient] = [] extra_ingredients: List[Ingredient] = []
# Set from shopping list
purchase_date: Optional[datetime.datetime] = None
async def create(conn): async def create(conn):
await conn.execute(''' await conn.execute('''
CREATE TABLE IF NOT EXISTS Meal ( CREATE TABLE IF NOT EXISTS Meal (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
suggested_date TEXT suggested_date TEXT,
consumed_date TEXT
);''') );''')
await conn.execute(''' await conn.execute('''
@ -99,10 +103,10 @@ async def find_meal_by_id(conn, meal_id: int) -> Meal:
await load_extra_ingredients(conn, meal) await load_extra_ingredients(conn, meal)
return 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''' async with conn.execute(f'''
SELECT {','.join(Meal.KEYS)} FROM Meal SELECT {','.join(Meal.KEYS)} FROM Meal
WHERE suggested_date >= ? AND suggested_date <= ? WHERE suggested_date >= ? AND suggested_date <= ? AND consumed_date IS NULL
''', (start, end)) as cursor: ''', (start, end)) as cursor:
async for row in cursor: async for row in cursor:
yield await with_purchase_date(conn, Meal(**{k:v for k,v in zip(Meal.KEYS, row)})) yield await with_purchase_date(conn, Meal(**{k:v for k,v in zip(Meal.KEYS, row)}))
@ -192,6 +196,15 @@ async def update_meal(conn, meal: Meal) -> None:
await sync_extra_ingredients(conn, meal.id, meal.extra_ingredients) await sync_extra_ingredients(conn, meal.id, meal.extra_ingredients)
await sync_recipes(conn, meal.id, meal.recipes) 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 def with_purchase_date(conn, meal: Meal) -> Meal:
async with conn.execute(''' async with conn.execute('''
SELECT purchased_date FROM ShoppingList 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 ingredients import Ingredient, insert_ingredient
from persons import Person from persons import Person
from products import Product from products import Product
@ -245,7 +245,7 @@ async def load_shopping_list(conn, id: int) -> ShoppingList:
async def _upcoming_meals(conn) -> AsyncIterator[Meal]: async def _upcoming_meals(conn) -> AsyncIterator[Meal]:
start = datetime.now() start = datetime.now()
end = start + timedelta(days=7) 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: if meal.purchase_date is None:
yield meal yield meal

View file

@ -214,7 +214,7 @@ class TestMeals(unittest.IsolatedAsyncioTestCase):
self.assertEqual(meal_by_id.extra_ingredients[0].id, extra_ingredient.id) self.assertEqual(meal_by_id.extra_ingredients[0].id, extra_ingredient.id)
self.assertEqual(meal_by_id.extra_ingredients[0].line, extra_ingredient.line) self.assertEqual(meal_by_id.extra_ingredients[0].line, extra_ingredient.line)
meals_by_date_range = await main.get_meals(meal.suggested_date, meal.suggested_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.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.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(len(meals_by_date_range), 1)
@ -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].id, recipe.id)
self.assertEqual(meals_by_date_range[0].recipes[0].name, recipe.name) 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(main.ConsumedWrapper(meal_id=meal.id), 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: async def testUpdate(self) -> None:
meal = test_data.Meals.broccoli_soup_for_jacob meal = test_data.Meals.broccoli_soup_for_jacob