diff --git a/app/api/cron.py b/app/api/cron.py index ef64260..8ab4c4d 100644 --- a/app/api/cron.py +++ b/app/api/cron.py @@ -1,10 +1,8 @@ """Cron related API endpoints.""" -from enum import Enum - from fastapi import APIRouter, BackgroundTasks, Depends -from ..cron import backup_database, check_frozen_meals, update_notion_meals +from ..core.cron import CRON_MAP, ValidCron from ..deps.security import token_middleware from ..utils.responses import gen_responses @@ -15,21 +13,6 @@ ) -class ValidCron(Enum): - """Defined crons that can be launched from the API.""" - - BACKUP_DATABASE = "backup-database" - CHECK_FROZEN_MEALS = "check-frozen-meals" - UPDATE_NOTION_MEALS = "update-notion-meals" - - -CRON_MAP = { - ValidCron.BACKUP_DATABASE: backup_database, - ValidCron.CHECK_FROZEN_MEALS: check_frozen_meals, - ValidCron.UPDATE_NOTION_MEALS: update_notion_meals, -} - - @router.post( "/{cron}", response_model_exclude_unset=True, diff --git a/app/api/meals.py b/app/api/meals.py index 7ebf0be..304c201 100644 --- a/app/api/meals.py +++ b/app/api/meals.py @@ -1,14 +1,13 @@ """Meal related API endpoints.""" import datetime -from enum import Enum -from typing import Any, List, Union +from typing import List, Union -from fastapi import APIRouter, BackgroundTasks, Depends, Query -from pydantic import parse_obj_as +from fastapi import APIRouter, BackgroundTasks, Depends from starlette.responses import Response from .. import crud +from ..core.meals import SwapMode, paginate, simplify, simplify_asked from ..cron.update_notion_meals import update_notion_meals from ..deps.database import get_db from ..deps.security import token_middleware @@ -22,30 +21,6 @@ ) -class OutputEnum(Enum): - """Output types for meals.""" - - SIMPLE = "simple" - NORMAL = "normal" - - -def simplify_asked(output: OutputEnum = Query(None, description="Simplify output")): - """Returns true if the output must be simplified.""" - return output == OutputEnum.SIMPLE - - -def paginate(skip: int = 0, limit: int = 100): - """Returns the pagination.""" - return {"skip": skip, "limit": limit} - - -def simplify(input_data: Any, simplified_model: Any, simplify_flag: bool): - """Simplifies output if needed.""" - if not simplify_flag: - return input_data - return parse_obj_as(simplified_model, input_data) - - @router.get( "", response_model_exclude_unset=True, @@ -173,6 +148,20 @@ def create_multiple_meals( return result +@router.put( + "/swap", + response_model=List[Union[Meal, SimpleMeal]], + response_model_exclude_unset=True, + summary="Swap meals", +) +def swap_meals( + *, db=Depends(get_db), meal_1: datetime.date, meal_2: datetime.date, mode: SwapMode +): + """Swaps meal attributes.""" + + return crud.meal.swap(db, date_1=meal_1, date_2=meal_2, mode=mode) + + @router.put( "/{date}", response_model=Union[Meal, SimpleMeal], diff --git a/app/core/cron.py b/app/core/cron.py new file mode 100644 index 0000000..d3f3c19 --- /dev/null +++ b/app/core/cron.py @@ -0,0 +1,20 @@ +"""Cron core.""" + +from enum import Enum + +from ..cron import backup_database, check_frozen_meals, update_notion_meals + + +class ValidCron(Enum): + """Defined crons that can be launched from the API.""" + + BACKUP_DATABASE = "backup-database" + CHECK_FROZEN_MEALS = "check-frozen-meals" + UPDATE_NOTION_MEALS = "update-notion-meals" + + +CRON_MAP = { + ValidCron.BACKUP_DATABASE: backup_database, + ValidCron.CHECK_FROZEN_MEALS: check_frozen_meals, + ValidCron.UPDATE_NOTION_MEALS: update_notion_meals, +} diff --git a/app/core/meals.py b/app/core/meals.py new file mode 100644 index 0000000..2f87803 --- /dev/null +++ b/app/core/meals.py @@ -0,0 +1,50 @@ +"""Meals core.""" + + +from enum import Enum +from typing import Any + +from fastapi import Query +from pydantic import parse_obj_as + + +class SwapMode(Enum): + """Valid swap modes.""" + + ALL = "all" + LUNCH = "lunch" + LUNCH_1 = "lunch1" + LUNCH_2 = "lunch2" + DINNER = "dinner" + + +class OutputEnum(Enum): + """Output types for meals.""" + + SIMPLE = "simple" + NORMAL = "normal" + + +def simplify_asked(output: OutputEnum = Query(None, description="Simplify output")): + """Returns true if the output must be simplified.""" + return output == OutputEnum.SIMPLE + + +def paginate(skip: int = 0, limit: int = 100): + """Returns the pagination.""" + return {"skip": skip, "limit": limit} + + +def simplify(input_data: Any, simplified_model: Any, simplify_flag: bool): + """Simplifies output if needed.""" + if not simplify_flag: + return input_data + return parse_obj_as(simplified_model, input_data) + + +def swap_attrs(obj1: Any, obj2: Any, attrname: str): + attr1 = getattr(obj1, attrname) + attr2 = getattr(obj2, attrname) + + setattr(obj1, attrname, attr2) + setattr(obj2, attrname, attr1) diff --git a/app/crud/crud_meal.py b/app/crud/crud_meal.py index 12939a8..58282ae 100644 --- a/app/crud/crud_meal.py +++ b/app/crud/crud_meal.py @@ -1,11 +1,12 @@ """Meals CRUD operations.""" -from datetime import datetime, timedelta +import datetime from typing import List from sqlalchemy import func from sqlalchemy.orm.session import Session +from ..core.meals import SwapMode, swap_attrs from ..crud.base import CRUDBase from ..models import Meal from ..schemas.meal import MealCreate, MealUpdate @@ -30,7 +31,7 @@ def get_today_or_404(self, db: Session): """Get today's menu or return 404.""" meal_db = self.get_today(db) if not meal_db: - self.raise_not_found_error(id=datetime.now().date()) + self.raise_not_found_error(id=datetime.datetime.now().date()) return meal_db def get_tomorrow_or_404(self, db: Session): @@ -43,11 +44,11 @@ def get_tomorrow_or_404(self, db: Session): @staticmethod def get_tomorrow_date(): """Returns tomorrow's date.""" - return (datetime.now() + timedelta(days=1)).date() + return (datetime.datetime.now() + datetime.timedelta(days=1)).date() def get_by_date_delta(self, db: Session, *, delta_days: int): """Get menu using a relative time delta.""" - date = (datetime.now() + timedelta(days=delta_days)).date() + date = (datetime.datetime.now() + datetime.timedelta(days=delta_days)).date() return self.get(db, id=date) def get_today(self, db: Session): @@ -76,6 +77,48 @@ def get_current_week(self, db: Session): week = get_current_week() return self.get_week(db, week=week) + def swap( + self, + db: Session, + *, + date_1: datetime.date, + date_2: datetime.date, + mode: SwapMode + ) -> List[Meal]: + """Swaps two meals data.""" + obj1 = self.get_or_404(db, id=date_1) + obj2 = self.get_or_404(db, id=date_2) + + attrnames = [] + if mode == SwapMode.ALL: + attrnames += ["lunch1", "lunch2", "dinner"] + elif mode == SwapMode.LUNCH: + attrnames += ["lunch1", "lunch2"] + elif mode == SwapMode.LUNCH_1: + attrnames.append("lunch1") + elif mode == SwapMode.LUNCH_2: + attrnames.append("lunch2") + elif mode == SwapMode.DINNER: + attrnames.append("dinner") + + if "lunch1" in attrnames: + attrnames.append("lunch1_frozen") + if "lunch2" in attrnames: + attrnames.append("lunch2_frozen") + if "dinner" in attrnames: + attrnames.append("dinner_frozen") + + for attr in attrnames: + swap_attrs(obj1, obj2, attr) + + db.add(obj1) + db.add(obj2) + db.commit() + db.refresh(obj1) + db.refresh(obj2) + + return [obj1, obj2] + def remove_week(self, db: Session, *, week: int): """Remove meals for the entire week.""" meals = self.get_week(db, week=week) diff --git a/pyproject.toml b/pyproject.toml index ef4ab5f..c69f228 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "meal-planner" -version = "1.3.0" +version = "1.4.0" description = "" authors = ["Diego Alloza González "] packages = [