diff --git a/mealie/db/data_access_layer/_base_access_model.py b/mealie/db/data_access_layer/_base_access_model.py index 33e90f593f78..4099a9911da4 100644 --- a/mealie/db/data_access_layer/_base_access_model.py +++ b/mealie/db/data_access_layer/_base_access_model.py @@ -96,8 +96,8 @@ class BaseAccessModel(Generic[T, D]): if any_case: search_attr = getattr(self.sql_model, key) result = session.query(self.sql_model).filter(func.lower(search_attr) == key.lower()).one_or_none() - - result = session.query(self.sql_model).filter_by(**{key: value}).one_or_none() + else: + result = session.query(self.sql_model).filter_by(**{key: value}).one_or_none() if not result: return diff --git a/mealie/db/data_access_layer/db_access.py b/mealie/db/data_access_layer/db_access.py index 92713a13b70c..c0ccfe89604e 100644 --- a/mealie/db/data_access_layer/db_access.py +++ b/mealie/db/data_access_layer/db_access.py @@ -3,9 +3,10 @@ from logging import getLogger from sqlalchemy.orm.session import Session from mealie.db.data_access_layer.group_access_model import GroupDataAccessModel -from mealie.db.models.cookbook import CookBook from mealie.db.models.event import Event, EventNotification from mealie.db.models.group import Group +from mealie.db.models.group.cookbook import CookBook +from mealie.db.models.group.shopping_list import ShoppingList from mealie.db.models.group.webhooks import GroupWebhooksModel from mealie.db.models.mealplan import MealPlan from mealie.db.models.recipe.category import Category @@ -13,7 +14,6 @@ from mealie.db.models.recipe.comment import RecipeComment from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel from mealie.db.models.recipe.recipe import RecipeModel, Tag from mealie.db.models.settings import SiteSettings -from mealie.db.models.shopping_list import ShoppingList from mealie.db.models.sign_up import SignUp from mealie.db.models.users import LongLiveToken, User from mealie.schema.admin import SiteSettings as SiteSettingsSchema @@ -39,7 +39,9 @@ from .user_access_model import UserDataAccessModel logger = getLogger() -DEFAULT_PK = "id" +pk_id = "id" +pk_slug = "slug" +pk_token = "token" class CategoryDataAccessModel(BaseAccessModel): @@ -53,38 +55,37 @@ class TagsDataAccessModel(BaseAccessModel): class DatabaseAccessLayer: - """ - `DatabaseAccessLayer` class is the data access layer for all database actions within - Mealie. Database uses composition from classes derived from BaseAccessModel. These - can be substantiated from the BaseAccessModel class or through inheritance when - additional methods are required. - """ - def __init__(self) -> None: + """ + `DatabaseAccessLayer` class is the data access layer for all database actions within + Mealie. Database uses composition from classes derived from BaseAccessModel. These + can be substantiated from the BaseAccessModel class or through inheritance when + additional methods are required. + """ # Recipes - self.recipes = RecipeDataAccessModel("slug", RecipeModel, Recipe) - self.ingredient_foods = BaseAccessModel(DEFAULT_PK, IngredientFoodModel, IngredientFood) - self.ingredient_units = BaseAccessModel(DEFAULT_PK, IngredientUnitModel, IngredientUnit) - self.comments = BaseAccessModel(DEFAULT_PK, RecipeComment, CommentOut) + self.recipes = RecipeDataAccessModel(pk_slug, RecipeModel, Recipe) + self.ingredient_foods = BaseAccessModel(pk_id, IngredientFoodModel, IngredientFood) + self.ingredient_units = BaseAccessModel(pk_id, IngredientUnitModel, IngredientUnit) + self.comments = BaseAccessModel(pk_id, RecipeComment, CommentOut) # Tags and Categories - self.categories = CategoryDataAccessModel("slug", Category, RecipeCategoryResponse) - self.tags = TagsDataAccessModel("slug", Tag, RecipeTagResponse) + self.categories = CategoryDataAccessModel(pk_slug, Category, RecipeCategoryResponse) + self.tags = TagsDataAccessModel(pk_slug, Tag, RecipeTagResponse) # Site - self.settings = BaseAccessModel(DEFAULT_PK, SiteSettings, SiteSettingsSchema) - self.sign_ups = BaseAccessModel("token", SignUp, SignUpOut) - self.event_notifications = BaseAccessModel(DEFAULT_PK, EventNotification, EventNotificationIn) - self.events = BaseAccessModel(DEFAULT_PK, Event, EventSchema) + self.settings = BaseAccessModel(pk_id, SiteSettings, SiteSettingsSchema) + self.sign_ups = BaseAccessModel(pk_token, SignUp, SignUpOut) + self.event_notifications = BaseAccessModel(pk_id, EventNotification, EventNotificationIn) + self.events = BaseAccessModel(pk_id, Event, EventSchema) # Users - self.users = UserDataAccessModel(DEFAULT_PK, User, PrivateUser) - self.api_tokens = BaseAccessModel(DEFAULT_PK, LongLiveToken, LongLiveTokenInDB) + self.users = UserDataAccessModel(pk_id, User, PrivateUser) + self.api_tokens = BaseAccessModel(pk_id, LongLiveToken, LongLiveTokenInDB) # Group Data - self.groups = GroupDataAccessModel(DEFAULT_PK, Group, GroupInDB) - self.meals = BaseAccessModel(DEFAULT_PK, MealPlan, MealPlanOut) - self.webhooks = BaseAccessModel(DEFAULT_PK, GroupWebhooksModel, ReadWebhook) - self.shopping_lists = BaseAccessModel(DEFAULT_PK, ShoppingList, ShoppingListOut) - self.cookbooks = BaseAccessModel(DEFAULT_PK, CookBook, ReadCookBook) + self.groups = GroupDataAccessModel(pk_id, Group, GroupInDB) + self.meals = BaseAccessModel(pk_id, MealPlan, MealPlanOut) + self.webhooks = BaseAccessModel(pk_id, GroupWebhooksModel, ReadWebhook) + self.shopping_lists = BaseAccessModel(pk_id, ShoppingList, ShoppingListOut) + self.cookbooks = BaseAccessModel(pk_id, CookBook, ReadCookBook) diff --git a/mealie/db/models/_all_models.py b/mealie/db/models/_all_models.py index 13c7121ca9a7..594d7915a557 100644 --- a/mealie/db/models/_all_models.py +++ b/mealie/db/models/_all_models.py @@ -3,6 +3,5 @@ from .group import * from .mealplan import * from .recipe.recipe import * from .settings import * -from .shopping_list import * from .sign_up import * from .users import * diff --git a/mealie/db/models/_model_base.py b/mealie/db/models/_model_base.py index 763298477e50..f5d082a48a9f 100644 --- a/mealie/db/models/_model_base.py +++ b/mealie/db/models/_model_base.py @@ -1,4 +1,3 @@ -import uuid from datetime import datetime from sqlalchemy import Column, DateTime, Integer @@ -7,22 +6,11 @@ from sqlalchemy.orm import declarative_base from sqlalchemy.orm.session import Session -def get_uuid_as_hex() -> str: - """ - Generate a UUID as a hex string. - :return: UUID as a hex string. - """ - return uuid.uuid4().hex - - @as_declarative() class Base: id = Column(Integer, primary_key=True) created_at = Column(DateTime, default=datetime.now()) - - # @declared_attr - # def __tablename__(cls): - # return cls.__name__.lower() + update_at = Column(DateTime, default=datetime.now(), onupdate=datetime.now()) class BaseMixins: diff --git a/mealie/db/models/event.py b/mealie/db/models/event.py index bf79bb412097..5ec0fc1e750f 100644 --- a/mealie/db/models/event.py +++ b/mealie/db/models/event.py @@ -2,6 +2,8 @@ from sqlalchemy import Boolean, Column, DateTime, Integer, String from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase +from ._model_utils import auto_init + class EventNotification(SqlAlchemyBase, BaseMixins): __tablename__ = "event_notifications" @@ -19,19 +21,9 @@ class EventNotification(SqlAlchemyBase, BaseMixins): group = Column(Boolean, default=False) user = Column(Boolean, default=False) - def __init__( - self, name, notification_url, type, general, recipe, backup, scheduled, migration, group, user, **_ - ) -> None: - self.name = name - self.notification_url = notification_url - self.type = type - self.general = general - self.recipe = recipe - self.backup = backup - self.scheduled = scheduled - self.migration = migration - self.group = group - self.user = user + @auto_init() + def __init__(self, **_) -> None: + pass class Event(SqlAlchemyBase, BaseMixins): @@ -42,8 +34,6 @@ class Event(SqlAlchemyBase, BaseMixins): time_stamp = Column(DateTime) category = Column(String) - def __init__(self, title, text, time_stamp, category, **_) -> None: - self.title = title - self.text = text - self.time_stamp = time_stamp - self.category = category + @auto_init() + def __init__(self, **_) -> None: + pass diff --git a/mealie/db/models/group/__init__.py b/mealie/db/models/group/__init__.py index abd1f750a4a5..e5b296260ea1 100644 --- a/mealie/db/models/group/__init__.py +++ b/mealie/db/models/group/__init__.py @@ -1 +1,3 @@ from .group import * +from .shopping_list import * +from .webhooks import * diff --git a/mealie/db/models/cookbook.py b/mealie/db/models/group/cookbook.py similarity index 82% rename from mealie/db/models/cookbook.py rename to mealie/db/models/group/cookbook.py index 00106c3ec3c6..c2ae38bf79ec 100644 --- a/mealie/db/models/cookbook.py +++ b/mealie/db/models/group/cookbook.py @@ -1,8 +1,8 @@ from sqlalchemy import Column, ForeignKey, Integer, String, orm -from ._model_base import BaseMixins, SqlAlchemyBase -from ._model_utils import auto_init -from .recipe.category import Category, cookbooks_to_categories +from .._model_base import BaseMixins, SqlAlchemyBase +from .._model_utils import auto_init +from ..recipe.category import Category, cookbooks_to_categories class CookBook(SqlAlchemyBase, BaseMixins): diff --git a/mealie/db/models/group/group.py b/mealie/db/models/group/group.py index 0b455e074263..a27d8d8e5b6b 100644 --- a/mealie/db/models/group/group.py +++ b/mealie/db/models/group/group.py @@ -3,12 +3,12 @@ import sqlalchemy.orm as orm from sqlalchemy.orm.session import Session from mealie.core.config import settings -from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase -from mealie.db.models.cookbook import CookBook -from mealie.db.models.group.webhooks import GroupWebhooksModel -from mealie.db.models.recipe.category import Category, group2categories +from .._model_base import BaseMixins, SqlAlchemyBase from .._model_utils import auto_init +from ..group.webhooks import GroupWebhooksModel +from ..recipe.category import Category, group2categories +from .cookbook import CookBook class Group(SqlAlchemyBase, BaseMixins): diff --git a/mealie/db/models/shopping_list.py b/mealie/db/models/group/shopping_list.py similarity index 93% rename from mealie/db/models/shopping_list.py rename to mealie/db/models/group/shopping_list.py index 757f8cddc83b..e5bdf6cd9bb3 100644 --- a/mealie/db/models/shopping_list.py +++ b/mealie/db/models/group/shopping_list.py @@ -3,8 +3,8 @@ from requests import Session from sqlalchemy import Boolean, Column, ForeignKey, Integer, String from sqlalchemy.ext.orderinglist import ordering_list -from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase -from mealie.db.models.group import Group +from .._model_base import BaseMixins, SqlAlchemyBase +from .group import Group class ShoppingListItem(SqlAlchemyBase, BaseMixins): diff --git a/mealie/db/models/mealplan.py b/mealie/db/models/mealplan.py index d5c85c4cf9c6..3e02572d00b9 100644 --- a/mealie/db/models/mealplan.py +++ b/mealie/db/models/mealplan.py @@ -5,7 +5,8 @@ from sqlalchemy.ext.orderinglist import ordering_list from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase from mealie.db.models.group import Group from mealie.db.models.recipe.recipe import RecipeModel -from mealie.db.models.shopping_list import ShoppingList + +from .group.shopping_list import ShoppingList class Meal(SqlAlchemyBase): diff --git a/mealie/db/models/sign_up.py b/mealie/db/models/sign_up.py index 6d220cff6e32..1ac87ca62957 100644 --- a/mealie/db/models/sign_up.py +++ b/mealie/db/models/sign_up.py @@ -2,6 +2,8 @@ from sqlalchemy import Boolean, Column, Integer, String from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase +from ._model_utils import auto_init + class SignUp(SqlAlchemyBase, BaseMixins): __tablename__ = "sign_ups" @@ -10,13 +12,6 @@ class SignUp(SqlAlchemyBase, BaseMixins): name = Column(String, index=True) admin = Column(Boolean, default=False) - def __init__( - self, - session, - token, - name, - admin, - ) -> None: - self.token = token - self.name = name - self.admin = admin + @auto_init() + def __init__(self, **_) -> None: + pass diff --git a/mealie/db/models/users/__init__.py b/mealie/db/models/users/__init__.py new file mode 100644 index 000000000000..9917a30a0b9e --- /dev/null +++ b/mealie/db/models/users/__init__.py @@ -0,0 +1 @@ +from .users import * diff --git a/mealie/db/models/users.py b/mealie/db/models/users/users.py similarity index 99% rename from mealie/db/models/users.py rename to mealie/db/models/users/users.py index 40ae4e4f71b8..d3dd71f5d209 100644 --- a/mealie/db/models/users.py +++ b/mealie/db/models/users/users.py @@ -27,9 +27,11 @@ class User(SqlAlchemyBase, BaseMixins): username = Column(String, index=True, unique=True) email = Column(String, unique=True, index=True) password = Column(String) + admin = Column(Boolean, default=False) + group_id = Column(Integer, ForeignKey("groups.id")) group = orm.relationship("Group", back_populates="users") - admin = Column(Boolean, default=False) + tokens: list[LongLiveToken] = orm.relationship( LongLiveToken, back_populates="user", cascade="all, delete, delete-orphan", single_parent=True ) diff --git a/mealie/schema/meal_plan/shopping_list.py b/mealie/schema/meal_plan/shopping_list.py index 344786c0ac43..deb0c984295e 100644 --- a/mealie/schema/meal_plan/shopping_list.py +++ b/mealie/schema/meal_plan/shopping_list.py @@ -3,7 +3,7 @@ from typing import Optional from fastapi_camelcase import CamelModel from pydantic.utils import GetterDict -from mealie.db.models.shopping_list import ShoppingList +from mealie.db.models.group.shopping_list import ShoppingList class ListItem(CamelModel): diff --git a/mealie/services/base_http_service/__init__.py b/mealie/services/base_http_service/__init__.py index 7e003fb22af3..94e328e00d18 100644 --- a/mealie/services/base_http_service/__init__.py +++ b/mealie/services/base_http_service/__init__.py @@ -1,3 +1,2 @@ -from .base_http_service import * -from .base_service import * +from .http_services import * from .router_factory import * diff --git a/mealie/services/base_http_service/base_http_service.py b/mealie/services/base_http_service/base_http_service.py index 511032a4a749..df1f4e6f4f00 100644 --- a/mealie/services/base_http_service/base_http_service.py +++ b/mealie/services/base_http_service/base_http_service.py @@ -1,12 +1,12 @@ from abc import ABC, abstractmethod -from typing import Callable, Generic, Type, TypeVar +from typing import Any, Callable, Generic, Type, TypeVar from fastapi import BackgroundTasks, Depends, HTTPException, status from sqlalchemy.orm.session import Session from mealie.core.config import get_app_dirs, get_settings -from mealie.core.dependencies.grouped import PublicDeps, UserDeps from mealie.core.root_logger import get_logger +from mealie.db.data_access_layer.db_access import DatabaseAccessLayer from mealie.db.database import get_database from mealie.db.db_setup import SessionLocal from mealie.schema.user.user import PrivateUser @@ -44,10 +44,10 @@ class BaseHttpService(Generic[T, D], ABC): delete_one: Callable = None delete_all: Callable = None + db_access: DatabaseAccessLayer = None + # Type Definitions _schema = None - _create_schema = None - _update_schema = None # Function called to create a server side event event_func: Callable = None @@ -67,14 +67,6 @@ class BaseHttpService(Generic[T, D], ABC): self.app_dirs = get_app_dirs() self.settings = get_settings() - @property - def group_id(self): - # TODO: Populate Group in Private User Call WARNING: May require significant refactoring - if not self._group_id_cache: - group = self.db.groups.get(self.session, self.user.group, "name") - self._group_id_cache = group.id - return self._group_id_cache - def _existing_factory(dependency: Type[CLS_DEP]) -> classmethod: def cls_method(cls, item_id: T, deps: CLS_DEP = Depends(dependency)): new_class = cls(deps.session, deps.user, deps.bg_task) @@ -89,21 +81,42 @@ class BaseHttpService(Generic[T, D], ABC): return classmethod(cls_method) - # TODO: Refactor to allow for configurable dependencies base on substantiation - read_existing = _existing_factory(PublicDeps) - write_existing = _existing_factory(UserDeps) + @classmethod + @abstractmethod + def public(cls, deps: Any): + pass - public = _class_method_factory(PublicDeps) - private = _class_method_factory(UserDeps) + @classmethod + @abstractmethod + def private(cls, deps: Any): + pass + + @classmethod + @abstractmethod + def read_existing(cls, deps: Any): + pass + + @classmethod + @abstractmethod + def write_existing(cls, deps: Any): + pass + + @abstractmethod + def populate_item(self) -> None: + pass + + @property + def group_id(self): + # TODO: Populate Group in Private User Call WARNING: May require significant refactoring + if not self._group_id_cache: + group = self.db.groups.get(self.session, self.user.group, "name") + self._group_id_cache = group.id + return self._group_id_cache def assert_existing(self, id: T) -> None: self.populate_item(id) self._check_item() - @abstractmethod - def populate_item(self) -> None: - ... - def _check_item(self) -> None: if not self.item: raise HTTPException(status.HTTP_404_NOT_FOUND) @@ -114,8 +127,38 @@ class BaseHttpService(Generic[T, D], ABC): if not group_id or group_id != self.group_id: raise HTTPException(status.HTTP_403_FORBIDDEN) + if hasattr(self, "check_item"): + self.check_item() + def _create_event(self, title: str, message: str) -> None: if not self.__class__.event_func: raise NotImplementedError("`event_func` must be set by child class") self.background_tasks.add_task(self.__class__.event_func, title, message, self.session) + + # Generic CRUD Functions + def _create_one(self, data: Any, exception_msg="generic-create-error") -> D: + try: + self.item = self.db_access.create(self.session, data) + except Exception as ex: + logger.exception(ex) + raise HTTPException(status.HTTP_400_BAD_REQUEST, detail={"message": exception_msg, "exception": str(ex)}) + + return self.item + + def _update_one(self, data: Any, id: int = None) -> D: + if not self.item: + return + + target_id = id or self.item.id + self.item = self.db_access.update(self.session, target_id, data) + + return self.item + + def _delete_one(self, id: int = None) -> D: + if not self.item: + return + + target_id = id or self.item.id + self.item = self.db_access.delete(self.session, target_id) + return self.item diff --git a/mealie/services/base_http_service/base_service.py b/mealie/services/base_http_service/base_service.py deleted file mode 100644 index 6202256c88b4..000000000000 --- a/mealie/services/base_http_service/base_service.py +++ /dev/null @@ -1,17 +0,0 @@ -from mealie.core.config import get_app_dirs, get_settings -from mealie.core.root_logger import get_logger -from mealie.db.database import get_database -from mealie.db.db_setup import generate_session - -logger = get_logger() - - -class BaseService: - def __init__(self) -> None: - # Static Globals Dependency Injection - self.db = get_database() - self.app_dirs = get_app_dirs() - self.settings = get_settings() - - def session_context(self): - return generate_session() diff --git a/mealie/services/base_http_service/router_factory.py b/mealie/services/base_http_service/router_factory.py index cfe656f42f89..12da54702d59 100644 --- a/mealie/services/base_http_service/router_factory.py +++ b/mealie/services/base_http_service/router_factory.py @@ -1,5 +1,5 @@ import inspect -from typing import Any, Callable, Optional, Sequence, Type, TypeVar +from typing import Any, Callable, Optional, Sequence, Type, TypeVar, get_type_hints from fastapi import APIRouter from fastapi.params import Depends @@ -8,7 +8,7 @@ from pydantic import BaseModel from .base_http_service import BaseHttpService -"""" +""" This code is largely based off of the FastAPI Crud Router https://github.com/awtkns/fastapi-crudrouter/blob/master/fastapi_crudrouter/core/_base.py """ @@ -18,25 +18,40 @@ S = TypeVar("S", bound=BaseHttpService) DEPENDENCIES = Optional[Sequence[Depends]] +def get_return(func: Callable, default) -> Type: + return get_type_hints(func).get("return", default) + + +def get_func_args(func: Callable) -> Sequence[str]: + for _, value in get_type_hints(func).items(): + if value: + return value + else: + return None + + class RouterFactory(APIRouter): + schema: Type[T] - create_schema: Type[T] - update_schema: Type[T] _base_path: str = "/" def __init__(self, service: Type[S], prefix: Optional[str] = None, tags: Optional[list[str]] = None, *_, **kwargs): + """ + RouterFactory takes a concrete service class derived from the BaseHttpService class and returns common + CRUD Routes for the service. The following features are implmeneted in the RouterFactory: + + 1. API endpoint Descriptions are read from the docstrings of the methods in the passed in service class + 2. Return types are inferred from the concrete service schema, or specified from the return type annotations. + This provides flexibility to return different types based on each route depending on client needs. + 3. Arguemnt types are inferred for Post and Put routes where the first type annotated argument is the data that + is beging posted or updated. Note that this is only done for the first argument of the method. + 4. The Get and Delete routes assume that you've defined the `write_existing` and `read_existing` methods in the + service class. The dependencies defined in the `write_existing` and `read_existing` methods are passed directly + to the FastAPI router and as such should include the `item_id` or equilivent argument. + """ self.service: Type[S] = service self.schema: Type[T] = service._schema - # HACK: Special Case for Coobooks, not sure this is a good way to handle the abstraction :/ - if hasattr(self.service, "_get_one_schema"): - self.get_one_schema = self.service._get_one_schema - else: - self.get_one_schema = self.schema - - self.update_schema: Type[T] = service._update_schema - self.create_schema: Type[T] = service._create_schema - prefix = str(prefix or self.schema.__name__).lower() prefix = self._base_path + prefix.strip("/") tags = tags or [prefix.strip("/").capitalize()] @@ -88,7 +103,7 @@ class RouterFactory(APIRouter): "/{item_id}", self._get_one(), methods=["GET"], - response_model=self.get_one_schema, + response_model=get_type_hints(self.service.populate_item).get("return", self.schema), summary="Get One", description=inspect.cleandoc(self.service.populate_item.__doc__ or ""), ) @@ -104,7 +119,6 @@ class RouterFactory(APIRouter): ) if self.service.delete_one: - print(self.service.delete_one.__doc__) self._add_api_route( "/{item_id}", self._delete_one(), @@ -160,19 +174,25 @@ class RouterFactory(APIRouter): return route def _create(self, *args: Any, **kwargs: Any) -> Callable[..., Any]: - def route(data: self.create_schema, service: S = Depends(self.service.private)) -> T: # type: ignore + create_schema = get_func_args(self.service.create_one) or self.schema + + def route(data: create_schema, service: S = Depends(self.service.private)) -> T: # type: ignore return service.create_one(data) return route def _update(self, *args: Any, **kwargs: Any) -> Callable[..., Any]: - def route(data: self.update_schema, service: S = Depends(self.service.write_existing)) -> T: # type: ignore + update_schema = get_func_args(self.service.update_one) or self.schema + + def route(data: update_schema, service: S = Depends(self.service.write_existing)) -> T: # type: ignore return service.update_one(data) return route def _update_many(self, *args: Any, **kwargs: Any) -> Callable[..., Any]: - def route(data: list[self.update_schema], service: S = Depends(self.service.write_existing)) -> T: # type: ignore + update_many_schema = get_func_args(self.service.update_many) or list[self.schema] + + def route(data: update_many_schema, service: S = Depends(self.service.private)) -> T: # type: ignore return service.update_many(data) return route diff --git a/mealie/services/cookbook/cookbook_service.py b/mealie/services/cookbook/cookbook_service.py index 2eee39483586..8fbdd3540d11 100644 --- a/mealie/services/cookbook/cookbook_service.py +++ b/mealie/services/cookbook/cookbook_service.py @@ -1,11 +1,11 @@ from __future__ import annotations -from fastapi import HTTPException, status - from mealie.core.root_logger import get_logger +from mealie.db.database import get_database from mealie.schema.cookbook.cookbook import CreateCookBook, ReadCookBook, RecipeCookBook, SaveCookBook, UpdateCookBook from mealie.services.base_http_service.http_services import UserHttpService from mealie.services.events import create_group_event +from mealie.utils.error_messages import ErrorMessages logger = get_logger(module=__name__) @@ -15,11 +15,10 @@ class CookbookService(UserHttpService[int, ReadCookBook]): _restrict_by_group = True _schema = ReadCookBook - _create_schema = CreateCookBook - _update_schema = UpdateCookBook - _get_one_schema = RecipeCookBook - def populate_item(self, item_id: int | str): + db_access = get_database().cookbooks + + def populate_item(self, item_id: int) -> RecipeCookBook: try: item_id = int(item_id) except Exception: @@ -37,25 +36,13 @@ class CookbookService(UserHttpService[int, ReadCookBook]): return items def create_one(self, data: CreateCookBook) -> ReadCookBook: - try: - self.item = self.db.cookbooks.create(self.session, SaveCookBook(group_id=self.group_id, **data.dict())) - except Exception as ex: - raise HTTPException( - status.HTTP_400_BAD_REQUEST, detail={"message": "PAGE_CREATION_ERROR", "exception": str(ex)} - ) + data = SaveCookBook(group_id=self.group_id, **data.dict()) + return self._create_one(data, ErrorMessages.cookbook_create_failure) - return self.item + def update_one(self, data: UpdateCookBook, id: int = None) -> ReadCookBook: + return self._update_one(data, id) - def update_one(self, data: CreateCookBook, id: int = None) -> ReadCookBook: - if not self.item: - return - - target_id = id or self.item.id - self.item = self.db.cookbooks.update(self.session, target_id, data) - - return self.item - - def update_many(self, data: list[ReadCookBook]) -> list[ReadCookBook]: + def update_many(self, data: list[UpdateCookBook]) -> list[ReadCookBook]: updated = [] for cookbook in data: @@ -65,10 +52,4 @@ class CookbookService(UserHttpService[int, ReadCookBook]): return updated def delete_one(self, id: int = None) -> ReadCookBook: - if not self.item: - return - - target_id = id or self.item.id - self.item = self.db.cookbooks.delete(self.session, target_id) - - return self.item + return self._delete_one(id) diff --git a/mealie/services/group/group_service.py b/mealie/services/group/group_service.py index 1455e56fb0cb..1bc2bf402c99 100644 --- a/mealie/services/group/group_service.py +++ b/mealie/services/group/group_service.py @@ -6,13 +6,13 @@ from mealie.core.dependencies.grouped import UserDeps from mealie.core.root_logger import get_logger from mealie.schema.recipe.recipe_category import CategoryBase from mealie.schema.user.user import GroupInDB -from mealie.services.base_http_service.base_http_service import BaseHttpService +from mealie.services.base_http_service.http_services import UserHttpService from mealie.services.events import create_group_event logger = get_logger(module=__name__) -class GroupSelfService(BaseHttpService[int, str]): +class GroupSelfService(UserHttpService[int, str]): _restrict_by_group = True event_func = create_group_event item: GroupInDB @@ -36,8 +36,9 @@ class GroupSelfService(BaseHttpService[int, str]): if self.item.id != self.group_id: raise HTTPException(status.HTTP_403_FORBIDDEN) - def populate_item(self, _: str = None): + def populate_item(self, _: str = None) -> GroupInDB: self.item = self.db.groups.get(self.session, self.group_id) + return self.item def update_categories(self, new_categories: list[CategoryBase]): if not self.item: diff --git a/mealie/services/group/webhook_service.py b/mealie/services/group/webhook_service.py index 3a5565c09ad1..8dd940d7607e 100644 --- a/mealie/services/group/webhook_service.py +++ b/mealie/services/group/webhook_service.py @@ -5,13 +5,13 @@ from fastapi import HTTPException, status from mealie.core.root_logger import get_logger from mealie.schema.group import ReadWebhook from mealie.schema.group.webhook import CreateWebhook, SaveWebhook -from mealie.services.base_http_service.base_http_service import BaseHttpService +from mealie.services.base_http_service.http_services import UserHttpService from mealie.services.events import create_group_event logger = get_logger(module=__name__) -class WebhookService(BaseHttpService[int, ReadWebhook]): +class WebhookService(UserHttpService[int, ReadWebhook]): event_func = create_group_event _restrict_by_group = True @@ -19,8 +19,9 @@ class WebhookService(BaseHttpService[int, ReadWebhook]): _create_schema = CreateWebhook _update_schema = CreateWebhook - def populate_item(self, id: int | str): + def populate_item(self, id: int) -> ReadWebhook: self.item = self.db.webhooks.get_one(self.session, id) + return self.item def get_all(self) -> list[ReadWebhook]: return self.db.webhooks.get(self.session, self.group_id, match_key="group_id", limit=9999) diff --git a/mealie/services/recipe/recipe_service.py b/mealie/services/recipe/recipe_service.py index ff8a00e1cac6..addfbf4feb0c 100644 --- a/mealie/services/recipe/recipe_service.py +++ b/mealie/services/recipe/recipe_service.py @@ -8,13 +8,13 @@ from sqlalchemy.exc import IntegrityError from mealie.core.dependencies.grouped import PublicDeps, UserDeps from mealie.core.root_logger import get_logger from mealie.schema.recipe.recipe import CreateRecipe, Recipe -from mealie.services.base_http_service.base_http_service import BaseHttpService +from mealie.services.base_http_service.http_services import PublicHttpService from mealie.services.events import create_recipe_event logger = get_logger(module=__name__) -class RecipeService(BaseHttpService[str, Recipe]): +class RecipeService(PublicHttpService[str, Recipe]): """ Class Methods: `read_existing`: Reads an existing recipe from the database. diff --git a/mealie/services/user/user_service.py b/mealie/services/user/user_service.py index 37e28e803b6c..26da2c56a549 100644 --- a/mealie/services/user/user_service.py +++ b/mealie/services/user/user_service.py @@ -3,13 +3,13 @@ from fastapi import HTTPException, status from mealie.core.root_logger import get_logger from mealie.core.security import hash_password, verify_password from mealie.schema.user.user import ChangePassword, PrivateUser -from mealie.services.base_http_service.base_http_service import BaseHttpService +from mealie.services.base_http_service.http_services import UserHttpService from mealie.services.events import create_user_event logger = get_logger(module=__name__) -class UserService(BaseHttpService[int, str]): +class UserService(UserHttpService[int, str]): event_func = create_user_event acting_user: PrivateUser = None