mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-07-09 03:04:54 -04:00
refactor(backend): ♻️ refactor backend services (#669)
* refactor(backend): ♻️ refactor backend services * refactor(backend): ♻️ move user model folder into own directory for future expansion * fix overriding results Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
parent
b550dae593
commit
3d87ffc3a5
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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 *
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -1 +1,3 @@
|
||||
from .group import *
|
||||
from .shopping_list import *
|
||||
from .webhooks import *
|
||||
|
@ -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):
|
@ -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):
|
||||
|
@ -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):
|
@ -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):
|
||||
|
@ -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
|
||||
|
1
mealie/db/models/users/__init__.py
Normal file
1
mealie/db/models/users/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .users import *
|
@ -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
|
||||
)
|
@ -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):
|
||||
|
@ -1,3 +1,2 @@
|
||||
from .base_http_service import *
|
||||
from .base_service import *
|
||||
from .http_services import *
|
||||
from .router_factory import *
|
||||
|
@ -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
|
||||
|
@ -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()
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user