diff --git a/mealie/app.py b/mealie/app.py index 4feeb44a93b8..c1940babbff6 100644 --- a/mealie/app.py +++ b/mealie/app.py @@ -10,7 +10,7 @@ from mealie.routes.mealplans import meal_plan_router from mealie.routes.media import media_router from mealie.routes.site_settings import settings_router from mealie.services.events import create_general_event -from mealie.services.recipe.all_recipes import subscripte_to_recipe_events +from mealie.services.recipe.all_recipe_service import subscripte_to_recipe_events logger = get_logger() diff --git a/mealie/routes/categories/categories.py b/mealie/routes/categories/categories.py index 94fae7a50bb3..d51a7891bfdc 100644 --- a/mealie/routes/categories/categories.py +++ b/mealie/routes/categories/categories.py @@ -1,9 +1,9 @@ from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm.session import Session +from mealie.core.dependencies import is_logged_in from mealie.db.database import db from mealie.db.db_setup import generate_session -from mealie.core.dependencies import is_logged_in from mealie.routes.routers import AdminAPIRouter, UserAPIRouter from mealie.schema.recipe import CategoryIn, RecipeCategoryResponse diff --git a/mealie/routes/groups/crud.py b/mealie/routes/groups/crud.py index 9ef1fe888e94..648fa200e127 100644 --- a/mealie/routes/groups/crud.py +++ b/mealie/routes/groups/crud.py @@ -1,9 +1,9 @@ from fastapi import BackgroundTasks, Depends, HTTPException, status from sqlalchemy.orm.session import Session +from mealie.core.dependencies import get_current_user from mealie.db.database import db from mealie.db.db_setup import generate_session -from mealie.core.dependencies import get_current_user from mealie.routes.routers import AdminAPIRouter, UserAPIRouter from mealie.schema.user import GroupBase, GroupInDB, UpdateGroup, UserInDB from mealie.services.events import create_group_event diff --git a/mealie/routes/mealplans/crud.py b/mealie/routes/mealplans/crud.py index ae08855a0dae..4813050b4611 100644 --- a/mealie/routes/mealplans/crud.py +++ b/mealie/routes/mealplans/crud.py @@ -2,9 +2,9 @@ from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, status from sqlalchemy.orm.session import Session from starlette.responses import FileResponse +from mealie.core.dependencies import get_current_user from mealie.db.database import db from mealie.db.db_setup import generate_session -from mealie.core.dependencies import get_current_user from mealie.routes.routers import UserAPIRouter from mealie.schema.meal_plan import MealPlanIn, MealPlanOut from mealie.schema.user import GroupInDB, UserInDB diff --git a/mealie/routes/mealplans/helpers.py b/mealie/routes/mealplans/helpers.py index 7fadb452a0b8..7b7bb161c17d 100644 --- a/mealie/routes/mealplans/helpers.py +++ b/mealie/routes/mealplans/helpers.py @@ -1,10 +1,10 @@ from fastapi import Depends from sqlalchemy.orm.session import Session +from mealie.core.dependencies import get_current_user from mealie.core.root_logger import get_logger from mealie.db.database import db from mealie.db.db_setup import generate_session -from mealie.core.dependencies import get_current_user from mealie.routes.routers import UserAPIRouter from mealie.schema.meal_plan import ListItem, MealPlanOut, ShoppingListIn, ShoppingListOut from mealie.schema.recipe import Recipe diff --git a/mealie/routes/media/recipe.py b/mealie/routes/media/recipe.py index 6b0d86c2aa94..adec289054dc 100644 --- a/mealie/routes/media/recipe.py +++ b/mealie/routes/media/recipe.py @@ -19,11 +19,11 @@ class ImageType(str, Enum): tiny = "tiny-original.webp" -@router.get("/{recipe_slug}/images/{file_name}") -async def get_recipe_img(recipe_slug: str, file_name: ImageType = ImageType.original): +@router.get("/{slug}/images/{file_name}") +async def get_recipe_img(slug: str, file_name: ImageType = ImageType.original): """Takes in a recipe slug, returns the static image. This route is proxied in the docker image and should not hit the API in production""" - recipe_image = Recipe(slug=recipe_slug).image_dir.joinpath(file_name.value) + recipe_image = Recipe(slug=slug).image_dir.joinpath(file_name.value) if recipe_image: return FileResponse(recipe_image) @@ -31,10 +31,10 @@ async def get_recipe_img(recipe_slug: str, file_name: ImageType = ImageType.orig raise HTTPException(status.HTTP_404_NOT_FOUND) -@router.get("/{recipe_slug}/assets/{file_name}") -async def get_recipe_asset(recipe_slug: str, file_name: str): +@router.get("/{slug}/assets/{file_name}") +async def get_recipe_asset(slug: str, file_name: str): """ Returns a recipe asset """ - file = Recipe(slug=recipe_slug).asset_dir.joinpath(file_name) + file = Recipe(slug=slug).asset_dir.joinpath(file_name) try: return FileResponse(file) diff --git a/mealie/routes/recipe/all_recipe_routes.py b/mealie/routes/recipe/all_recipe_routes.py index 6c2dce4b160b..783c5d7e39cf 100644 --- a/mealie/routes/recipe/all_recipe_routes.py +++ b/mealie/routes/recipe/all_recipe_routes.py @@ -4,7 +4,7 @@ from sqlalchemy.orm.session import Session from mealie.db.database import db from mealie.db.db_setup import generate_session from mealie.schema.recipe import RecipeSummary -from mealie.services.recipe.all_recipes import AllRecipesService +from mealie.services.recipe.all_recipe_service import AllRecipesService router = APIRouter() diff --git a/mealie/routes/recipe/comments.py b/mealie/routes/recipe/comments.py index 1c0d51f24809..a50c593f21c3 100644 --- a/mealie/routes/recipe/comments.py +++ b/mealie/routes/recipe/comments.py @@ -3,9 +3,9 @@ from http.client import HTTPException from fastapi import Depends, status from sqlalchemy.orm.session import Session +from mealie.core.dependencies import get_current_user from mealie.db.database import db from mealie.db.db_setup import generate_session -from mealie.core.dependencies import get_current_user from mealie.routes.routers import UserAPIRouter from mealie.schema.recipe import CommentOut, CreateComment, SaveComment from mealie.schema.user import UserInDB diff --git a/mealie/routes/recipe/image_and_assets.py b/mealie/routes/recipe/image_and_assets.py index f1945f65d199..c54d073e15dc 100644 --- a/mealie/routes/recipe/image_and_assets.py +++ b/mealie/routes/recipe/image_and_assets.py @@ -14,33 +14,33 @@ from mealie.services.image.image import scrape_image, write_image user_router = UserAPIRouter() -@user_router.post("/{recipe_slug}/image") +@user_router.post("/{slug}/image") def scrape_image_url( - recipe_slug: str, + slug: str, url: CreateRecipeByURL, ): """ Removes an existing image and replaces it with the incoming file. """ - scrape_image(url.url, recipe_slug) + scrape_image(url.url, slug) -@user_router.put("/{recipe_slug}/image") +@user_router.put("/{slug}/image") def update_recipe_image( - recipe_slug: str, + slug: str, image: bytes = File(...), extension: str = Form(...), session: Session = Depends(generate_session), ): """ Removes an existing image and replaces it with the incoming file. """ - write_image(recipe_slug, image, extension) - new_version = db.recipes.update_image(session, recipe_slug, extension) + write_image(slug, image, extension) + new_version = db.recipes.update_image(session, slug, extension) return {"image": new_version} -@user_router.post("/{recipe_slug}/assets", response_model=RecipeAsset) +@user_router.post("/{slug}/assets", response_model=RecipeAsset) def upload_recipe_asset( - recipe_slug: str, + slug: str, name: str = Form(...), icon: str = Form(...), extension: str = Form(...), @@ -50,7 +50,7 @@ def upload_recipe_asset( """ Upload a file to store as a recipe asset """ file_name = slugify(name) + "." + extension asset_in = RecipeAsset(name=name, icon=icon, file_name=file_name) - dest = Recipe(slug=recipe_slug).asset_dir.joinpath(file_name) + dest = Recipe(slug=slug).asset_dir.joinpath(file_name) with dest.open("wb") as buffer: copyfileobj(file.file, buffer) @@ -58,7 +58,7 @@ def upload_recipe_asset( if not dest.is_file(): raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR) - recipe: Recipe = db.recipes.get(session, recipe_slug) + recipe: Recipe = db.recipes.get(session, slug) recipe.assets.append(asset_in) - db.recipes.update(session, recipe_slug, recipe.dict()) + db.recipes.update(session, slug, recipe.dict()) return asset_in diff --git a/mealie/routes/recipe/recipe_crud_routes.py b/mealie/routes/recipe/recipe_crud_routes.py index 49cc9f23fcea..544b9a9531a7 100644 --- a/mealie/routes/recipe/recipe_crud_routes.py +++ b/mealie/routes/recipe/recipe_crud_routes.py @@ -2,24 +2,20 @@ import json import shutil from zipfile import ZipFile -from fastapi import APIRouter, BackgroundTasks, Depends, File +from fastapi import APIRouter, Depends, File from fastapi.datastructures import UploadFile from scrape_schema_recipe import scrape_url from sqlalchemy.orm.session import Session from starlette.responses import FileResponse -from mealie.core.config import settings +from mealie.core.dependencies import temporary_zip_path from mealie.core.root_logger import get_logger from mealie.db.database import db from mealie.db.db_setup import generate_session -from mealie.core.dependencies import get_current_user, temporary_zip_path from mealie.routes.routers import UserAPIRouter from mealie.schema.recipe import CreateRecipeByURL, Recipe, RecipeImageTypes from mealie.schema.recipe.recipe import CreateRecipe -from mealie.schema.user import UserInDB -from mealie.services.events import create_recipe_event from mealie.services.image.image import write_image -from mealie.services.recipe.media import check_assets from mealie.services.recipe.recipe_service import RecipeService from mealie.services.scraper.scraper import create_from_url @@ -28,44 +24,30 @@ public_router = APIRouter() logger = get_logger() +@public_router.get("/{slug}", response_model=Recipe) +def get_recipe(recipe_service: RecipeService = Depends(RecipeService.read_existing)): + """ Takes in a recipe slug, returns all data for a recipe """ + return recipe_service.recipe + + @user_router.post("", status_code=201, response_model=str) def create_from_name(data: CreateRecipe, recipe_service: RecipeService = Depends(RecipeService.base)) -> str: """ Takes in a JSON string and loads data into the database as a new entry""" return recipe_service.create_recipe(data).slug -@user_router.post("/test-scrape-url") -def test_parse_recipe_url(url: CreateRecipeByURL): - return scrape_url(url.url) - - @user_router.post("/create-url", status_code=201, response_model=str) -def parse_recipe_url( - background_tasks: BackgroundTasks, - url: CreateRecipeByURL, - session: Session = Depends(generate_session), - current_user: UserInDB = Depends(get_current_user), -): +def parse_recipe_url(url: CreateRecipeByURL, recipe_service: RecipeService = Depends(RecipeService.base)): """ Takes in a URL and attempts to scrape data and load it into the database """ recipe = create_from_url(url.url) - recipe: Recipe = db.recipes.create(session, recipe.dict()) - - background_tasks.add_task( - create_recipe_event, - "Recipe Created (URL)", - f"'{recipe.name}' by {current_user.full_name} \n {settings.BASE_URL}/recipe/{recipe.slug}", - session=session, - attachment=recipe.image_dir.joinpath("min-original.webp"), - ) - - return recipe.slug + return recipe_service.create_recipe(recipe).slug -@public_router.get("/{slug}", response_model=Recipe) -def get_recipe(recipe_service: RecipeService = Depends(RecipeService.read_existing)): - """ Takes in a recipe slug, returns all data for a recipe """ - return recipe_service.recipe +@user_router.post("/test-scrape-url") +def test_parse_recipe_url(url: CreateRecipeByURL): + # TODO: Replace with more current implementation of testing schema + return scrape_url(url.url) @user_router.post("/create-from-zip") @@ -98,54 +80,36 @@ async def create_recipe_from_zip( return recipe -@public_router.get("/{recipe_slug}/zip") +@public_router.get("/{slug}/zip") async def get_recipe_as_zip( - recipe_slug: str, session: Session = Depends(generate_session), temp_path=Depends(temporary_zip_path) + slug: str, session: Session = Depends(generate_session), temp_path=Depends(temporary_zip_path) ): """ Get a Recipe and It's Original Image as a Zip File """ - recipe: Recipe = db.recipes.get(session, recipe_slug) + recipe: Recipe = db.recipes.get(session, slug) image_asset = recipe.image_dir.joinpath(RecipeImageTypes.original.value) with ZipFile(temp_path, "w") as myzip: - myzip.writestr(f"{recipe_slug}.json", recipe.json()) + myzip.writestr(f"{slug}.json", recipe.json()) if image_asset.is_file(): myzip.write(image_asset, arcname=image_asset.name) - return FileResponse(temp_path, filename=f"{recipe_slug}.zip") + return FileResponse(temp_path, filename=f"{slug}.zip") -@user_router.put("/{recipe_slug}") -def update_recipe( - recipe_slug: str, - data: Recipe, - session: Session = Depends(generate_session), -): +@user_router.put("/{slug}") +def update_recipe(data: Recipe, recipe_service: RecipeService = Depends(RecipeService.write_existing)): """ Updates a recipe by existing slug and data. """ - recipe: Recipe = db.recipes.update(session, recipe_slug, data.dict()) - - check_assets(original_slug=recipe_slug, recipe=recipe) - - return recipe + return recipe_service.update_recipe(data) -@user_router.patch("/{recipe_slug}") -def patch_recipe( - recipe_slug: str, - data: Recipe, - session: Session = Depends(generate_session), -): +@user_router.patch("/{slug}") +def patch_recipe(data: Recipe, recipe_service: RecipeService = Depends(RecipeService.write_existing)): """ Updates a recipe by existing slug and data. """ - recipe: Recipe = db.recipes.patch( - session, recipe_slug, new_data=data.dict(exclude_unset=True, exclude_defaults=True) - ) - - check_assets(original_slug=recipe_slug, recipe=recipe) - - return recipe + return recipe_service.patch_recipe(data) @user_router.delete("/{slug}") diff --git a/mealie/routes/shopping_lists/__init__.py b/mealie/routes/shopping_lists/__init__.py index 56270e43a015..daa14751f8cb 100644 --- a/mealie/routes/shopping_lists/__init__.py +++ b/mealie/routes/shopping_lists/__init__.py @@ -1,9 +1,9 @@ from fastapi import Depends from sqlalchemy.orm.session import Session +from mealie.core.dependencies import get_current_user from mealie.db.database import db from mealie.db.db_setup import generate_session -from mealie.core.dependencies import get_current_user from mealie.routes.routers import UserAPIRouter from mealie.schema.meal_plan import ShoppingListIn, ShoppingListOut from mealie.schema.user import UserInDB diff --git a/mealie/routes/site_settings/site_settings.py b/mealie/routes/site_settings/site_settings.py index 5acfbdd4cb81..782cb1acff71 100644 --- a/mealie/routes/site_settings/site_settings.py +++ b/mealie/routes/site_settings/site_settings.py @@ -1,9 +1,9 @@ from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm.session import Session +from mealie.core.dependencies import get_current_user from mealie.db.database import db from mealie.db.db_setup import generate_session -from mealie.core.dependencies import get_current_user from mealie.routes.routers import AdminAPIRouter from mealie.schema.admin import SiteSettings from mealie.schema.user import GroupInDB, UserInDB diff --git a/mealie/routes/tags/tags.py b/mealie/routes/tags/tags.py index c4e52964f87f..20aeef14d1e7 100644 --- a/mealie/routes/tags/tags.py +++ b/mealie/routes/tags/tags.py @@ -1,9 +1,9 @@ from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm.session import Session +from mealie.core.dependencies import is_logged_in from mealie.db.database import db from mealie.db.db_setup import generate_session -from mealie.core.dependencies import is_logged_in from mealie.routes.routers import AdminAPIRouter, UserAPIRouter from mealie.schema.recipe import RecipeTagResponse, TagIn diff --git a/mealie/routes/users/api_tokens.py b/mealie/routes/users/api_tokens.py index 426b50938989..246a0fd0e6a1 100644 --- a/mealie/routes/users/api_tokens.py +++ b/mealie/routes/users/api_tokens.py @@ -4,10 +4,10 @@ from fastapi import HTTPException, status from fastapi.param_functions import Depends from sqlalchemy.orm.session import Session +from mealie.core.dependencies import get_current_user from mealie.core.security import create_access_token from mealie.db.database import db from mealie.db.db_setup import generate_session -from mealie.core.dependencies import get_current_user from mealie.routes.routers import UserAPIRouter from mealie.schema.user import CreateToken, LoingLiveTokenIn, LongLiveTokenInDB, UserInDB diff --git a/mealie/routes/users/crud.py b/mealie/routes/users/crud.py index 28cb185fd0ac..47fbf1c164c4 100644 --- a/mealie/routes/users/crud.py +++ b/mealie/routes/users/crud.py @@ -2,10 +2,10 @@ from fastapi import BackgroundTasks, Depends, HTTPException, status from sqlalchemy.orm.session import Session from mealie.core import security +from mealie.core.dependencies import get_current_user from mealie.core.security import get_password_hash from mealie.db.database import db from mealie.db.db_setup import generate_session -from mealie.core.dependencies import get_current_user from mealie.routes.routers import AdminAPIRouter, UserAPIRouter from mealie.routes.users._helpers import assert_user_change_allowed from mealie.schema.user import UserBase, UserIn, UserInDB, UserOut diff --git a/mealie/routes/users/favorites.py b/mealie/routes/users/favorites.py index bccaeef6770b..bbe96ab783e9 100644 --- a/mealie/routes/users/favorites.py +++ b/mealie/routes/users/favorites.py @@ -1,9 +1,9 @@ from fastapi import Depends from sqlalchemy.orm.session import Session +from mealie.core.dependencies import get_current_user from mealie.db.database import db from mealie.db.db_setup import generate_session -from mealie.core.dependencies import get_current_user from mealie.routes.routers import UserAPIRouter from mealie.routes.users._helpers import assert_user_change_allowed from mealie.schema.user import UserFavorites, UserInDB diff --git a/mealie/routes/users/passwords.py b/mealie/routes/users/passwords.py index 161f08006c35..71dc8b917d6c 100644 --- a/mealie/routes/users/passwords.py +++ b/mealie/routes/users/passwords.py @@ -2,10 +2,10 @@ from fastapi import Depends, HTTPException, status from sqlalchemy.orm.session import Session from mealie.core.config import settings +from mealie.core.dependencies import get_current_user from mealie.core.security import get_password_hash, verify_password from mealie.db.database import db from mealie.db.db_setup import generate_session -from mealie.core.dependencies import get_current_user from mealie.routes.routers import UserAPIRouter from mealie.routes.users._helpers import assert_user_change_allowed from mealie.schema.user import ChangePassword, UserInDB diff --git a/mealie/routes/users/sign_up.py b/mealie/routes/users/sign_up.py index caeead30ed61..9f038edc413b 100644 --- a/mealie/routes/users/sign_up.py +++ b/mealie/routes/users/sign_up.py @@ -3,10 +3,10 @@ import uuid from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, status from sqlalchemy.orm.session import Session +from mealie.core.dependencies import get_admin_user from mealie.core.security import get_password_hash from mealie.db.database import db from mealie.db.db_setup import generate_session -from mealie.core.dependencies import get_admin_user from mealie.routes.routers import AdminAPIRouter from mealie.schema.user import SignUpIn, SignUpOut, SignUpToken, UserIn, UserInDB from mealie.services.events import create_user_event diff --git a/mealie/services/recipe/all_recipes.py b/mealie/services/recipe/all_recipe_service.py similarity index 100% rename from mealie/services/recipe/all_recipes.py rename to mealie/services/recipe/all_recipe_service.py index bbf1e942f802..69143028abb5 100644 --- a/mealie/services/recipe/all_recipes.py +++ b/mealie/services/recipe/all_recipe_service.py @@ -5,10 +5,10 @@ from fastapi import Depends, Response from fastapi.encoders import jsonable_encoder from sqlalchemy.orm.session import Session +from mealie.core.dependencies import is_logged_in from mealie.core.root_logger import get_logger from mealie.db.database import db from mealie.db.db_setup import SessionLocal, generate_session -from mealie.core.dependencies import is_logged_in from mealie.schema.recipe import RecipeSummary logger = get_logger() diff --git a/mealie/services/recipe/common_deps.py b/mealie/services/recipe/common_deps.py deleted file mode 100644 index ae0577bdeaa6..000000000000 --- a/mealie/services/recipe/common_deps.py +++ /dev/null @@ -1,41 +0,0 @@ -from typing import Any - -from fastapi import BackgroundTasks, Depends -from pydantic import BaseModel -from sqlalchemy.orm.session import Session - -from mealie.db.db_setup import generate_session -from mealie.core.dependencies import get_current_user, is_logged_in - - -class CommonDeps(BaseModel): - session: Session - background_tasks: BackgroundTasks - user: Any - - class Config: - arbitrary_types_allowed = True - - -def read_deps( - background_tasks: BackgroundTasks, - session: Session = Depends(generate_session), - current_user=Depends(is_logged_in), -): - return CommonDeps( - session=session, - background_tasks=background_tasks, - user=current_user, - ) - - -def write_deps( - background_tasks: BackgroundTasks, - session: Session = Depends(generate_session), - current_user=Depends(get_current_user), -): - return CommonDeps( - session=session, - background_tasks=background_tasks, - user=current_user, - ) diff --git a/mealie/services/recipe/media.py b/mealie/services/recipe/media.py deleted file mode 100644 index 9be89214329b..000000000000 --- a/mealie/services/recipe/media.py +++ /dev/null @@ -1,34 +0,0 @@ -from pathlib import Path -from shutil import copytree, rmtree - -from mealie.core.config import app_dirs -from mealie.core.root_logger import get_logger -from mealie.schema.recipe import Recipe - -logger = get_logger() - - -def check_assets(original_slug, recipe: Recipe) -> None: - if original_slug != recipe.slug: - current_dir = app_dirs.RECIPE_DATA_DIR.joinpath(original_slug) - - try: - copytree(current_dir, recipe.directory, dirs_exist_ok=True) - - except FileNotFoundError: - logger.error(f"Recipe Directory not Found: {original_slug}") - logger.info(f"Renaming Recipe Directory: {original_slug} -> {recipe.slug}") - - all_asset_files = [x.file_name for x in recipe.assets] - for file in recipe.asset_dir.iterdir(): - file: Path - if file.is_dir(): - continue - if file.name not in all_asset_files: - file.unlink() - - -def delete_assets(recipe_slug): - recipe_dir = Recipe(slug=recipe_slug).directory - rmtree(recipe_dir, ignore_errors=True) - logger.info(f"Recipe Directory Removed: {recipe_slug}") diff --git a/mealie/services/recipe/recipe_service.py b/mealie/services/recipe/recipe_service.py index 818d570858a7..4f28d636da0f 100644 --- a/mealie/services/recipe/recipe_service.py +++ b/mealie/services/recipe/recipe_service.py @@ -1,20 +1,33 @@ +from pathlib import Path +from shutil import copytree, rmtree +from typing import Union + from fastapi import BackgroundTasks, Depends, HTTPException, status from sqlalchemy.exc import IntegrityError from sqlalchemy.orm.session import Session -from mealie.core.config import get_settings +from mealie.core.config import get_app_dirs, get_settings from mealie.core.dependencies import ReadDeps from mealie.core.dependencies.grouped import WriteDeps +from mealie.core.root_logger import get_logger from mealie.db.database import get_database from mealie.db.db_setup import SessionLocal from mealie.schema.recipe.recipe import CreateRecipe, Recipe from mealie.schema.user.user import UserInDB from mealie.services.events import create_recipe_event -from mealie.services.recipe.media import delete_assets + +logger = get_logger(module=__name__) class RecipeService: - recipe: Recipe + """ + Class Methods: + `read_existing`: Reads an existing recipe from the database. + `write_existing`: Updates an existing recipe in the database. + `base`: Requires write permissions, but doesn't perform recipe checks + """ + + recipe: Recipe # Required for proper type hints def __init__(self, session: Session, user: UserInDB, background_tasks: BackgroundTasks = None) -> None: self.session = session or SessionLocal() @@ -22,8 +35,9 @@ class RecipeService: self.background_tasks = background_tasks self.recipe: Recipe = None - # Static Globals + # Static Globals Dependency Injection self.db = get_database() + self.app_dirs = get_app_dirs() self.settings = get_settings() @classmethod @@ -99,10 +113,11 @@ class RecipeService: raise HTTPException(status.HTTP_403_FORBIDDEN) # CRUD METHODS - def create_recipe(self, new_recipe: CreateRecipe) -> Recipe: + def create_recipe(self, create_data: Union[Recipe, CreateRecipe]) -> Recipe: + if isinstance(create_data, CreateRecipe): + create_data = Recipe(name=create_data.name) try: - create_data = Recipe(name=new_recipe.name) self.recipe = self.db.recipes.create(self.session, create_data) except IntegrityError: raise HTTPException(status.HTTP_400_BAD_REQUEST, detail={"message": "RECIPE_ALREADY_EXISTS"}) @@ -114,6 +129,32 @@ class RecipeService: return self.recipe + def update_recipe(self, update_data: Recipe) -> Recipe: + original_slug = self.recipe.slug + + try: + self.recipe = self.db.recipes.update(self.session, original_slug, update_data) + except IntegrityError: + raise HTTPException(status.HTTP_400_BAD_REQUEST, detail={"message": "RECIPE_ALREADY_EXISTS"}) + + self._check_assets(original_slug) + + return self.recipe + + def patch_recipe(self, patch_data: Recipe) -> Recipe: + original_slug = self.recipe.slug + + try: + self.recipe = self.db.recipes.patch( + self.session, original_slug, patch_data.dict(exclude_unset=True, exclude_defaults=True) + ) + except IntegrityError: + raise HTTPException(status.HTTP_400_BAD_REQUEST, detail={"message": "RECIPE_ALREADY_EXISTS"}) + + self._check_assets(original_slug) + + return self.recipe + def delete_recipe(self) -> Recipe: """removes a recipe from the database and purges the existing files from the filesystem. @@ -126,7 +167,7 @@ class RecipeService: try: recipe: Recipe = self.db.recipes.delete(self.session, self.recipe.slug) - delete_assets(recipe_slug=self.recipe.slug) + self._delete_assets() except Exception: raise HTTPException(status.HTTP_400_BAD_REQUEST) @@ -135,3 +176,27 @@ class RecipeService: def _create_event(self, title: str, message: str) -> None: self.background_tasks.add_task(create_recipe_event, title, message, self.session) + + def _check_assets(self, original_slug) -> None: + if original_slug != self.recipe.slug: + current_dir = self.app_dirs.RECIPE_DATA_DIR.joinpath(original_slug) + + try: + copytree(current_dir, self.recipe.directory, dirs_exist_ok=True) + logger.info(f"Renaming Recipe Directory: {original_slug} -> {self.recipe.slug}") + except FileNotFoundError: + logger.error(f"Recipe Directory not Found: {original_slug}") + + all_asset_files = [x.file_name for x in self.recipe.assets] + + for file in self.recipe.asset_dir.iterdir(): + file: Path + if file.is_dir(): + continue + if file.name not in all_asset_files: + file.unlink() + + def _delete_assets(self) -> None: + recipe_dir = self.recipe.directory + rmtree(recipe_dir, ignore_errors=True) + logger.info(f"Recipe Directory Removed: {self.recipe.slug}") diff --git a/mealie/services/scheduler/scheduler_utils.py b/mealie/services/scheduler/scheduler_utils.py index eb6240f4a1ff..e6bb65fc5e3c 100644 --- a/mealie/services/scheduler/scheduler_utils.py +++ b/mealie/services/scheduler/scheduler_utils.py @@ -5,6 +5,4 @@ Cron = collections.namedtuple("Cron", "hours minutes") def cron_parser(time_str: str) -> Cron: time = time_str.split(":") - cron = Cron(hours=int(time[0]), minutes=int(time[1])) - - return cron + return Cron(hours=int(time[0]), minutes=int(time[1]))