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
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,20 @@ async def update_meal(meal_id: int, meal: meals.Meal, conn: sqlite3.Connection =
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}")
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', 'suggested_date']
KEYS: ClassVar[List[str]] = ['id', 'suggested_date', 'consumed_date']
id: int = -1
suggested_date: datetime.datetime
purchase_date: Optional[datetime.datetime] = None
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,
suggested_date TEXT
suggested_date TEXT,
consumed_date TEXT
);''')
await conn.execute('''
@ -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 suggested_date >= ? AND suggested_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)}))
@ -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

@ -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].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.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)
@ -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(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:
meal = test_data.Meals.broccoli_soup_for_jacob