mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-02-04 15:03:10 -05:00
refactor(backend): ♻️ cleanup HTTP service classes and remove database singleton (#687)
* refactor(backend): ♻️ cleanup duplicate code in http services * refactor(backend): ♻️ refactor database away from singleton design removed the database single and instead injected the session into a new Database class that is created during each request life-cycle. Now sessions no longer need to be passed into each method on the database All tests pass, but there are likely some hidden breaking changes that were not discovered. * fix venv * disable venv cache * fix install script * bump poetry version * postgres fixes * revert install * fix db initialization for postgres * add postgres to docker * refactor(backend): ♻️ cleanup unused and duplicate code in http services * refactor(backend): remove sessions from arguments * refactor(backend): ♻️ convert units and ingredients to use http service class * test(backend): ✅ add unit and food tests * lint * update tags * re-enable cache * fix missing fraction in db * fix lint Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
@@ -7,7 +7,6 @@ from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.core.config import get_app_dirs, get_settings
|
||||
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
|
||||
@@ -45,8 +44,6 @@ class BaseHttpService(Generic[T, D], ABC):
|
||||
delete_one: Callable = None
|
||||
delete_all: Callable = None
|
||||
|
||||
db_access: DatabaseAccessLayer = None
|
||||
|
||||
# Type Definitions
|
||||
_schema = None
|
||||
|
||||
@@ -64,7 +61,7 @@ class BaseHttpService(Generic[T, D], ABC):
|
||||
self.background_tasks = background_tasks
|
||||
|
||||
# Static Globals Dependency Injection
|
||||
self.db = get_database()
|
||||
self.db = get_database(session)
|
||||
self.app_dirs = get_app_dirs()
|
||||
self.settings = get_settings()
|
||||
|
||||
@@ -110,7 +107,7 @@ class BaseHttpService(Generic[T, D], ABC):
|
||||
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")
|
||||
group = self.db.groups.get(self.user.group, "name")
|
||||
self._group_id_cache = group.id
|
||||
return self._group_id_cache
|
||||
|
||||
|
||||
@@ -1,27 +1,37 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
from fastapi import HTTPException, status
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.db.data_access_layer.db_access import DatabaseAccessLayer
|
||||
from mealie.db.data_access_layer._access_model import AccessModel
|
||||
|
||||
C = TypeVar("C", bound=BaseModel)
|
||||
R = TypeVar("R", bound=BaseModel)
|
||||
U = TypeVar("U", bound=BaseModel)
|
||||
DAL = TypeVar("DAL", bound=DatabaseAccessLayer)
|
||||
DAL = TypeVar("DAL", bound=AccessModel)
|
||||
logger = get_logger()
|
||||
|
||||
|
||||
class CrudHttpMixins(Generic[C, R, U]):
|
||||
item: C
|
||||
class CrudHttpMixins(Generic[C, R, U], ABC):
|
||||
item: R
|
||||
session: Session
|
||||
dal: DAL
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def dal(self) -> DAL:
|
||||
...
|
||||
|
||||
def populate_item(self, id: int) -> R:
|
||||
self.item = self.dal.get_one(id)
|
||||
return self.item
|
||||
|
||||
def _create_one(self, data: C, exception_msg="generic-create-error") -> R:
|
||||
try:
|
||||
self.item = self.dal.create(self.session, data)
|
||||
self.item = self.dal.create(data)
|
||||
except Exception as ex:
|
||||
logger.exception(ex)
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST, detail={"message": exception_msg, "exception": str(ex)})
|
||||
@@ -33,17 +43,26 @@ class CrudHttpMixins(Generic[C, R, U]):
|
||||
return
|
||||
|
||||
target_id = item_id or self.item.id
|
||||
self.item = self.dal.update(self.session, target_id, data)
|
||||
self.item = self.dal.update(target_id, data)
|
||||
|
||||
return self.item
|
||||
|
||||
def _patch_one(self) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def _delete_one(self, item_id: int = None) -> None:
|
||||
if not self.item:
|
||||
return
|
||||
def _patch_one(self, data: U, item_id: int) -> None:
|
||||
try:
|
||||
self.item = self.dal.patch(item_id, data.dict(exclude_unset=True, exclude_defaults=True))
|
||||
except IntegrityError:
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST, detail={"message": "generic-patch-error"})
|
||||
|
||||
def _delete_one(self, item_id: int = None) -> R:
|
||||
target_id = item_id or self.item.id
|
||||
self.item = self.dal.delete(self.session, target_id)
|
||||
logger.info(f"Deleting item with id {target_id}")
|
||||
|
||||
try:
|
||||
self.item = self.dal.delete(target_id)
|
||||
except Exception as ex:
|
||||
logger.exception(ex)
|
||||
raise HTTPException(
|
||||
status.HTTP_400_BAD_REQUEST, detail={"message": "generic-delete-error", "exception": str(ex)}
|
||||
)
|
||||
|
||||
return self.item
|
||||
|
||||
@@ -10,7 +10,7 @@ from pydantic.main import BaseModel
|
||||
|
||||
from mealie.core import root_logger
|
||||
from mealie.core.config import app_dirs
|
||||
from mealie.db.database import db
|
||||
from mealie.db.database import get_database
|
||||
from mealie.db.db_setup import create_session
|
||||
from mealie.services.events import create_backup_event
|
||||
|
||||
@@ -114,29 +114,31 @@ def backup_all(
|
||||
):
|
||||
db_export = ExportDatabase(tag=tag, templates=templates)
|
||||
|
||||
db = get_database(session)
|
||||
|
||||
if export_users:
|
||||
all_users = db.users.get_all(session)
|
||||
all_users = db.users.get_all()
|
||||
db_export.export_items(all_users, "users")
|
||||
|
||||
if export_groups:
|
||||
all_groups = db.groups.get_all(session)
|
||||
all_groups = db.groups.get_all()
|
||||
db_export.export_items(all_groups, "groups")
|
||||
|
||||
if export_recipes:
|
||||
all_recipes = db.recipes.get_all(session)
|
||||
all_recipes = db.recipes.get_all()
|
||||
db_export.export_recipe_dirs()
|
||||
db_export.export_items(all_recipes, "recipes", export_list=False, slug_folder=True)
|
||||
db_export.export_templates(all_recipes)
|
||||
|
||||
all_comments = db.comments.get_all(session)
|
||||
all_comments = db.comments.get_all()
|
||||
db_export.export_items(all_comments, "comments")
|
||||
|
||||
if export_settings:
|
||||
all_settings = db.settings.get_all(session)
|
||||
all_settings = db.settings.get_all()
|
||||
db_export.export_items(all_settings, "settings")
|
||||
|
||||
if export_notifications:
|
||||
all_notifications = db.event_notifications.get_all(session)
|
||||
all_notifications = db.event_notifications.get_all()
|
||||
db_export.export_items(all_notifications, "notifications")
|
||||
|
||||
return db_export.finish_export()
|
||||
|
||||
@@ -8,7 +8,7 @@ from pydantic.main import BaseModel
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.core.config import app_dirs
|
||||
from mealie.db.database import db
|
||||
from mealie.db.database import get_database
|
||||
from mealie.schema.admin import (
|
||||
CommentImport,
|
||||
GroupImport,
|
||||
@@ -44,6 +44,7 @@ class ImportDatabase:
|
||||
"""
|
||||
self.user = user
|
||||
self.session = session
|
||||
self.db = get_database(session)
|
||||
self.archive = app_dirs.BACKUP_DIR.joinpath(zip_archive)
|
||||
self.force_imports = force_import
|
||||
|
||||
@@ -72,7 +73,7 @@ class ImportDatabase:
|
||||
recipe.user_id = self.user.id
|
||||
|
||||
import_status = self.import_model(
|
||||
db_table=db.recipes,
|
||||
db_table=self.db.recipes,
|
||||
model=recipe,
|
||||
return_model=RecipeImport,
|
||||
name_attr="name",
|
||||
@@ -101,7 +102,7 @@ class ImportDatabase:
|
||||
comment: CommentOut
|
||||
|
||||
self.import_model(
|
||||
db_table=db.comments,
|
||||
db_table=self.db.comments,
|
||||
model=comment,
|
||||
return_model=CommentImport,
|
||||
name_attr="uuid",
|
||||
@@ -166,7 +167,7 @@ class ImportDatabase:
|
||||
|
||||
for notify in notifications:
|
||||
import_status = self.import_model(
|
||||
db_table=db.event_notifications,
|
||||
db_table=self.db.event_notifications,
|
||||
model=notify,
|
||||
return_model=NotificationImport,
|
||||
name_attr="name",
|
||||
@@ -183,7 +184,7 @@ class ImportDatabase:
|
||||
settings = settings[0]
|
||||
|
||||
try:
|
||||
db.settings.update(self.session, 1, settings.dict())
|
||||
self.db.settings.update(1, settings.dict())
|
||||
import_status = SettingsImport(name="Site Settings", status=True)
|
||||
|
||||
except Exception as inst:
|
||||
@@ -198,7 +199,7 @@ class ImportDatabase:
|
||||
group_imports = []
|
||||
|
||||
for group in groups:
|
||||
import_status = self.import_model(db.groups, group, GroupImport, search_key="name")
|
||||
import_status = self.import_model(self.db.groups, group, GroupImport, search_key="name")
|
||||
group_imports.append(import_status)
|
||||
|
||||
return group_imports
|
||||
@@ -209,13 +210,13 @@ class ImportDatabase:
|
||||
user_imports = []
|
||||
for user in users:
|
||||
if user.id == 1: # Update Default User
|
||||
db.users.update(self.session, 1, user.dict())
|
||||
self.db.users.update(1, user.dict())
|
||||
import_status = UserImport(name=user.full_name, status=True)
|
||||
user_imports.append(import_status)
|
||||
continue
|
||||
|
||||
import_status = self.import_model(
|
||||
db_table=db.users,
|
||||
db_table=self.db.users,
|
||||
model=user,
|
||||
return_model=UserImport,
|
||||
name_attr="full_name",
|
||||
@@ -283,7 +284,7 @@ class ImportDatabase:
|
||||
model_name = getattr(model, name_attr)
|
||||
search_value = getattr(model, search_key)
|
||||
|
||||
item = db_table.get(self.session, search_value, search_key)
|
||||
item = db_table.get(search_value, search_key)
|
||||
if item:
|
||||
if not self.force_imports:
|
||||
return return_model(
|
||||
@@ -293,9 +294,9 @@ class ImportDatabase:
|
||||
)
|
||||
|
||||
primary_key = getattr(item, db_table.primary_key)
|
||||
db_table.delete(self.session, primary_key)
|
||||
db_table.delete(primary_key)
|
||||
try:
|
||||
db_table.create(self.session, model.dict())
|
||||
db_table.create(model.dict())
|
||||
import_status = return_model(name=model_name, status=True)
|
||||
|
||||
except Exception as inst:
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import apprise
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.db.database import db
|
||||
from mealie.db.database import get_database
|
||||
from mealie.db.db_setup import create_session
|
||||
from mealie.schema.events import Event, EventCategory
|
||||
|
||||
|
||||
def test_notification(notification_url, event=None) -> bool:
|
||||
|
||||
if event is None:
|
||||
event = Event(
|
||||
title="Test Notification",
|
||||
@@ -38,9 +37,10 @@ def post_notifications(event: Event, notification_urls=list[str], hard_fail=Fals
|
||||
def save_event(title, text, category, session: Session, attachment=None):
|
||||
event = Event(title=title, text=text, category=category)
|
||||
session = session or create_session()
|
||||
db.events.create(session, event.dict())
|
||||
db = get_database(session)
|
||||
db.events.create(event.dict())
|
||||
|
||||
notification_objects = db.event_notifications.get(session=session, match_value=True, match_key=category, limit=9999)
|
||||
notification_objects = db.event_notifications.get(match_value=True, match_key=category, limit=9999)
|
||||
notification_urls = [x.notification_url for x in notification_objects]
|
||||
post_notifications(event, notification_urls, attachment=attachment)
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import cached_property
|
||||
|
||||
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.crud_http_mixins import CrudHttpMixins
|
||||
from mealie.services._base_http_service.http_services import UserHttpService
|
||||
@@ -12,17 +13,16 @@ logger = get_logger(module=__name__)
|
||||
|
||||
|
||||
class CookbookService(
|
||||
UserHttpService[int, ReadCookBook],
|
||||
CrudHttpMixins[CreateCookBook, ReadCookBook, UpdateCookBook],
|
||||
UserHttpService[int, ReadCookBook],
|
||||
):
|
||||
event_func = create_group_event
|
||||
_restrict_by_group = True
|
||||
|
||||
_schema = ReadCookBook
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.dal = get_database().cookbooks
|
||||
@cached_property
|
||||
def dal(self):
|
||||
return self.db.cookbooks
|
||||
|
||||
def populate_item(self, item_id: int) -> RecipeCookBook:
|
||||
try:
|
||||
@@ -31,13 +31,13 @@ class CookbookService(
|
||||
pass
|
||||
|
||||
if isinstance(item_id, int):
|
||||
self.item = self.db.cookbooks.get_one(self.session, item_id, override_schema=RecipeCookBook)
|
||||
self.item = self.dal.get_one(item_id, override_schema=RecipeCookBook)
|
||||
|
||||
else:
|
||||
self.item = self.db.cookbooks.get_one(self.session, item_id, key="slug", override_schema=RecipeCookBook)
|
||||
self.item = self.dal.get_one(item_id, key="slug", override_schema=RecipeCookBook)
|
||||
|
||||
def get_all(self) -> list[ReadCookBook]:
|
||||
items = self.db.cookbooks.get(self.session, self.group_id, "group_id", limit=999)
|
||||
items = self.dal.get(self.group_id, "group_id", limit=999)
|
||||
items.sort(key=lambda x: x.position)
|
||||
return items
|
||||
|
||||
@@ -52,7 +52,7 @@ class CookbookService(
|
||||
updated = []
|
||||
|
||||
for cookbook in data:
|
||||
cb = self.db.cookbooks.update(self.session, cookbook.id, cookbook)
|
||||
cb = self.dal.update(cookbook.id, cookbook)
|
||||
updated.append(cb)
|
||||
|
||||
return updated
|
||||
|
||||
@@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
from uuid import uuid4
|
||||
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi import Depends
|
||||
|
||||
from mealie.core.dependencies.grouped import UserDeps
|
||||
from mealie.core.root_logger import get_logger
|
||||
@@ -17,7 +17,7 @@ logger = get_logger(module=__name__)
|
||||
|
||||
|
||||
class GroupSelfService(UserHttpService[int, str]):
|
||||
_restrict_by_group = True
|
||||
_restrict_by_group = False
|
||||
event_func = create_group_event
|
||||
item: GroupInDB
|
||||
|
||||
@@ -31,31 +31,21 @@ class GroupSelfService(UserHttpService[int, str]):
|
||||
"""Override parent method to remove `item_id` from arguments"""
|
||||
return super().write_existing(item_id=0, deps=deps)
|
||||
|
||||
def assert_existing(self, _: str = None):
|
||||
self.populate_item()
|
||||
|
||||
if not self.item:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND)
|
||||
|
||||
if self.item.id != self.group_id:
|
||||
raise HTTPException(status.HTTP_403_FORBIDDEN)
|
||||
|
||||
def populate_item(self, _: str = None) -> GroupInDB:
|
||||
self.item = self.db.groups.get(self.session, self.group_id)
|
||||
self.item = self.db.groups.get(self.group_id)
|
||||
return self.item
|
||||
|
||||
def update_categories(self, new_categories: list[CategoryBase]):
|
||||
self.item.categories = new_categories
|
||||
return self.db.groups.update(self.session, self.group_id, self.item)
|
||||
return self.db.groups.update(self.group_id, self.item)
|
||||
|
||||
def update_preferences(self, new_preferences: UpdateGroupPreferences):
|
||||
self.db.group_preferences.update(self.session, self.group_id, new_preferences)
|
||||
|
||||
self.db.group_preferences.update(self.group_id, new_preferences)
|
||||
return self.populate_item()
|
||||
|
||||
def create_invite_token(self, uses: int = 1) -> None:
|
||||
token = SaveInviteToken(uses_left=uses, group_id=self.group_id, token=uuid4().hex)
|
||||
return self.db.group_tokens.create(self.session, token)
|
||||
return self.db.group_invite_tokens.create(token)
|
||||
|
||||
def get_invite_tokens(self) -> list[ReadInviteToken]:
|
||||
return self.db.group_tokens.multi_query(self.session, {"group_id": self.group_id})
|
||||
return self.db.group_invite_tokens.multi_query({"group_id": self.group_id})
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
from mealie.db.database import get_database
|
||||
from mealie.db.data_access_layer.access_model_factory import Database
|
||||
from mealie.schema.group.group_preferences import CreateGroupPreferences
|
||||
from mealie.schema.user.user import GroupBase, GroupInDB
|
||||
|
||||
|
||||
def create_new_group(session, g_base: GroupBase, g_preferences: CreateGroupPreferences = None) -> GroupInDB:
|
||||
db = get_database()
|
||||
created_group = db.groups.create(session, g_base)
|
||||
def create_new_group(db: Database, g_base: GroupBase, g_preferences: CreateGroupPreferences = None) -> GroupInDB:
|
||||
created_group = db.groups.create(g_base)
|
||||
|
||||
g_preferences = g_preferences or CreateGroupPreferences(group_id=0) # Assign Temporary ID before group is created
|
||||
|
||||
g_preferences.group_id = created_group.id
|
||||
|
||||
db.group_preferences.create(session, g_preferences)
|
||||
db.group_preferences.create(g_preferences)
|
||||
|
||||
return created_group
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date
|
||||
from functools import cached_property
|
||||
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.db.database import get_database
|
||||
from mealie.schema.meal_plan import CreatePlanEntry, ReadPlanEntry, SavePlanEntry, UpdatePlanEntry
|
||||
|
||||
from .._base_http_service.crud_http_mixins import CrudHttpMixins
|
||||
@@ -13,26 +13,27 @@ from ..events import create_group_event
|
||||
logger = get_logger(module=__name__)
|
||||
|
||||
|
||||
class MealService(UserHttpService[int, ReadPlanEntry], CrudHttpMixins[CreatePlanEntry, ReadPlanEntry, UpdatePlanEntry]):
|
||||
class MealService(CrudHttpMixins[CreatePlanEntry, ReadPlanEntry, UpdatePlanEntry], UserHttpService[int, ReadPlanEntry]):
|
||||
event_func = create_group_event
|
||||
_restrict_by_group = True
|
||||
|
||||
_schema = ReadPlanEntry
|
||||
item: ReadPlanEntry
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.dal = get_database().meals
|
||||
@cached_property
|
||||
def dal(self):
|
||||
return self.db.meals
|
||||
|
||||
def populate_item(self, id: int) -> ReadPlanEntry:
|
||||
self.item = self.db.meals.get_one(self.session, id)
|
||||
self.item = self.dal.get_one(id)
|
||||
return self.item
|
||||
|
||||
def get_slice(self, start: date = None, end: date = None) -> list[ReadPlanEntry]:
|
||||
# 2 days ago
|
||||
return self.db.meals.get_slice(self.session, start, end, group_id=self.group_id)
|
||||
return self.dal.get_slice(start, end, group_id=self.group_id)
|
||||
|
||||
def get_today(self) -> list[ReadPlanEntry]:
|
||||
return self.db.meals.get_today(self.session, group_id=self.group_id)
|
||||
return self.dal.get_today(group_id=self.group_id)
|
||||
|
||||
def create_one(self, data: CreatePlanEntry) -> ReadPlanEntry:
|
||||
data = self.cast(data, SavePlanEntry)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import cached_property
|
||||
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.db.database import get_database
|
||||
from mealie.schema.group import ReadWebhook
|
||||
from mealie.schema.group.webhook import CreateWebhook, SaveWebhook
|
||||
from mealie.services._base_http_service.crud_http_mixins import CrudHttpMixins
|
||||
@@ -11,22 +12,21 @@ from mealie.services.events import create_group_event
|
||||
logger = get_logger(module=__name__)
|
||||
|
||||
|
||||
class WebhookService(UserHttpService[int, ReadWebhook], CrudHttpMixins[ReadWebhook, CreateWebhook, CreateWebhook]):
|
||||
class WebhookService(CrudHttpMixins[ReadWebhook, CreateWebhook, CreateWebhook], UserHttpService[int, ReadWebhook]):
|
||||
event_func = create_group_event
|
||||
_restrict_by_group = True
|
||||
|
||||
_schema = ReadWebhook
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.dal = get_database().webhooks
|
||||
@cached_property
|
||||
def dal(self):
|
||||
return self.db.webhooks
|
||||
|
||||
def populate_item(self, id: int) -> ReadWebhook:
|
||||
self.item = self.db.webhooks.get_one(self.session, id)
|
||||
self.item = self.dal.get_one(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)
|
||||
return self.dal.get(self.group_id, match_key="group_id", limit=9999)
|
||||
|
||||
def create_one(self, data: CreateWebhook) -> ReadWebhook:
|
||||
data = self.cast(data, SaveWebhook)
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
from datetime import date, timedelta
|
||||
from typing import Union
|
||||
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.db.database import db
|
||||
from mealie.db.db_setup import create_session
|
||||
from mealie.schema.meal_plan import MealDayIn, MealPlanIn
|
||||
from mealie.schema.recipe import Recipe
|
||||
from mealie.schema.user import GroupInDB
|
||||
|
||||
|
||||
def set_mealplan_dates(meal_plan_base: MealPlanIn) -> MealPlanIn:
|
||||
for x, plan_days in enumerate(meal_plan_base.plan_days):
|
||||
plan_days: MealDayIn
|
||||
plan_days.date = meal_plan_base.start_date + timedelta(days=x)
|
||||
|
||||
|
||||
def get_todays_meal(session: Session, group: Union[int, GroupInDB]) -> Recipe:
|
||||
"""Returns the given mealplan for today based off the group. If the group
|
||||
Type is of type int, then a query will be made to the database to get the
|
||||
grop object."
|
||||
|
||||
Args:
|
||||
session (Session): SqlAlchemy Session
|
||||
group (Union[int, GroupInDB]): Either the id of the group or the GroupInDB Object
|
||||
|
||||
Returns:
|
||||
Recipe: Pydantic Recipe Object
|
||||
"""
|
||||
|
||||
session = session or create_session()
|
||||
|
||||
if isinstance(group, int):
|
||||
group: GroupInDB = db.groups.get(session, group)
|
||||
|
||||
today_slug = None
|
||||
|
||||
for mealplan in group.mealplans:
|
||||
for plan_day in mealplan.plan_days:
|
||||
if plan_day.date == date.today():
|
||||
if plan_day.meals[0].slug and plan_day.meals[0].slug != "":
|
||||
today_slug = plan_day.meals[0].slug
|
||||
else:
|
||||
return plan_day.meals[0]
|
||||
|
||||
if today_slug:
|
||||
return db.recipes.get(session, today_slug)
|
||||
else:
|
||||
return None
|
||||
@@ -7,7 +7,7 @@ import yaml
|
||||
from pydantic import BaseModel
|
||||
|
||||
from mealie.core import root_logger
|
||||
from mealie.db.database import db
|
||||
from mealie.db.database import get_database
|
||||
from mealie.schema.admin import MigrationImport
|
||||
from mealie.schema.recipe import Recipe
|
||||
from mealie.schema.user.user import PrivateUser
|
||||
@@ -37,6 +37,10 @@ class MigrationBase(BaseModel):
|
||||
|
||||
user: PrivateUser
|
||||
|
||||
@property
|
||||
def db(self):
|
||||
return get_database(self.session)
|
||||
|
||||
@property
|
||||
def temp_dir(self) -> TemporaryDirectory:
|
||||
"""unpacks the migration_file into a temporary directory
|
||||
@@ -66,7 +70,7 @@ class MigrationBase(BaseModel):
|
||||
with open(yaml_file, "r") as f:
|
||||
contents = f.read().split("---")
|
||||
recipe_data = {}
|
||||
for x, document in enumerate(contents):
|
||||
for _, document in enumerate(contents):
|
||||
|
||||
# Check if None or Empty String
|
||||
if document is None or document == "":
|
||||
@@ -172,7 +176,7 @@ class MigrationBase(BaseModel):
|
||||
exception = ""
|
||||
status = False
|
||||
try:
|
||||
db.recipes.create(self.session, recipe.dict())
|
||||
self.db.recipes.create(recipe.dict())
|
||||
status = True
|
||||
|
||||
except Exception as inst:
|
||||
|
||||
37
mealie/services/recipe/recipe_food_service.py
Normal file
37
mealie/services/recipe/recipe_food_service.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import cached_property
|
||||
|
||||
from mealie.schema.recipe.recipe_ingredient import CreateIngredientFood, IngredientFood
|
||||
from mealie.services._base_http_service.crud_http_mixins import CrudHttpMixins
|
||||
from mealie.services._base_http_service.http_services import UserHttpService
|
||||
from mealie.services.events import create_recipe_event
|
||||
|
||||
|
||||
class RecipeFoodService(
|
||||
CrudHttpMixins[IngredientFood, CreateIngredientFood, CreateIngredientFood],
|
||||
UserHttpService[int, IngredientFood],
|
||||
):
|
||||
event_func = create_recipe_event
|
||||
_restrict_by_group = False
|
||||
_schema = IngredientFood
|
||||
|
||||
@cached_property
|
||||
def dal(self):
|
||||
return self.db.ingredient_foods
|
||||
|
||||
def populate_item(self, id: int) -> IngredientFood:
|
||||
self.item = self.dal.get_one(id)
|
||||
return self.item
|
||||
|
||||
def get_all(self) -> list[IngredientFood]:
|
||||
return self.dal.get_all()
|
||||
|
||||
def create_one(self, data: CreateIngredientFood) -> IngredientFood:
|
||||
return self._create_one(data)
|
||||
|
||||
def update_one(self, data: IngredientFood, item_id: int = None) -> IngredientFood:
|
||||
return self._update_one(data, item_id)
|
||||
|
||||
def delete_one(self, id: int = None) -> IngredientFood:
|
||||
return self._delete_one(id)
|
||||
@@ -1,13 +1,15 @@
|
||||
from functools import cached_property
|
||||
from pathlib import Path
|
||||
from shutil import copytree, rmtree
|
||||
from typing import Union
|
||||
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from mealie.core.dependencies.grouped import PublicDeps, UserDeps
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.db.data_access_layer.recipe_access_model import RecipeDataAccessModel
|
||||
from mealie.schema.recipe.recipe import CreateRecipe, Recipe, RecipeSummary
|
||||
from mealie.services._base_http_service.crud_http_mixins import CrudHttpMixins
|
||||
from mealie.services._base_http_service.http_services import UserHttpService
|
||||
from mealie.services.events import create_recipe_event
|
||||
from mealie.services.recipe.mixins import recipe_creation_factory
|
||||
@@ -15,7 +17,7 @@ from mealie.services.recipe.mixins import recipe_creation_factory
|
||||
logger = get_logger(module=__name__)
|
||||
|
||||
|
||||
class RecipeService(UserHttpService[str, Recipe]):
|
||||
class RecipeService(CrudHttpMixins[CreateRecipe, Recipe, Recipe], UserHttpService[str, Recipe]):
|
||||
"""
|
||||
Class Methods:
|
||||
`read_existing`: Reads an existing recipe from the database.
|
||||
@@ -25,6 +27,10 @@ class RecipeService(UserHttpService[str, Recipe]):
|
||||
|
||||
event_func = create_recipe_event
|
||||
|
||||
@cached_property
|
||||
def dal(self) -> RecipeDataAccessModel:
|
||||
return self.db.recipes
|
||||
|
||||
@classmethod
|
||||
def write_existing(cls, slug: str, deps: UserDeps = Depends()):
|
||||
return super().write_existing(slug, deps)
|
||||
@@ -35,75 +41,49 @@ class RecipeService(UserHttpService[str, Recipe]):
|
||||
|
||||
def assert_existing(self, slug: str):
|
||||
self.populate_item(slug)
|
||||
|
||||
if not self.item:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND)
|
||||
|
||||
if not self.item.settings.public and not self.user:
|
||||
raise HTTPException(status.HTTP_403_FORBIDDEN)
|
||||
|
||||
def populate_item(self, slug: str) -> Recipe:
|
||||
self.item = self.db.recipes.get(self.session, slug)
|
||||
return self.item
|
||||
|
||||
# CRUD METHODS
|
||||
def get_all(self, start=0, limit=None):
|
||||
return self.db.recipes.multi_query(
|
||||
self.session, {"group_id": self.user.group_id}, start=start, limit=limit, override_schema=RecipeSummary
|
||||
{"group_id": self.user.group_id},
|
||||
start=start,
|
||||
limit=limit,
|
||||
override_schema=RecipeSummary,
|
||||
)
|
||||
|
||||
def create_one(self, create_data: Union[Recipe, CreateRecipe]) -> Recipe:
|
||||
create_data = recipe_creation_factory(self.user, name=create_data.name, additional_attrs=create_data.dict())
|
||||
|
||||
try:
|
||||
self.item = self.db.recipes.create(self.session, create_data)
|
||||
except IntegrityError:
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST, detail={"message": "RECIPE_ALREADY_EXISTS"})
|
||||
|
||||
self._create_one(create_data, "RECIPE_ALREAD_EXISTS")
|
||||
self._create_event(
|
||||
"Recipe Created",
|
||||
f"'{self.item.name}' by {self.user.username} \n {self.settings.BASE_URL}/recipe/{self.item.slug}",
|
||||
)
|
||||
|
||||
return self.item
|
||||
|
||||
def update_one(self, update_data: Recipe) -> Recipe:
|
||||
original_slug = self.item.slug
|
||||
|
||||
try:
|
||||
self.item = 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)
|
||||
|
||||
self._update_one(update_data, original_slug)
|
||||
self.check_assets(original_slug)
|
||||
return self.item
|
||||
|
||||
def patch_one(self, patch_data: Recipe) -> Recipe:
|
||||
original_slug = self.item.slug
|
||||
|
||||
try:
|
||||
self.item = 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)
|
||||
|
||||
self._patch_one(patch_data, original_slug)
|
||||
self.check_assets(original_slug)
|
||||
return self.item
|
||||
|
||||
def delete_one(self) -> Recipe:
|
||||
try:
|
||||
recipe: Recipe = self.db.recipes.delete(self.session, self.item.slug)
|
||||
self._delete_assets()
|
||||
except Exception:
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||
self._delete_one(self.item.slug)
|
||||
self.delete_assets()
|
||||
self._create_event("Recipe Delete", f"'{self.item.name}' deleted by {self.user.full_name}")
|
||||
return self.item
|
||||
|
||||
self._create_event("Recipe Delete", f"'{recipe.name}' deleted by {self.user.full_name}")
|
||||
return recipe
|
||||
|
||||
def _check_assets(self, original_slug: str) -> None:
|
||||
def check_assets(self, original_slug: str) -> None:
|
||||
"""Checks if the recipe slug has changed, and if so moves the assets to a new file with the new slug."""
|
||||
if original_slug != self.item.slug:
|
||||
current_dir = self.app_dirs.RECIPE_DATA_DIR.joinpath(original_slug)
|
||||
@@ -123,7 +103,7 @@ class RecipeService(UserHttpService[str, Recipe]):
|
||||
if file.name not in all_asset_files:
|
||||
file.unlink()
|
||||
|
||||
def _delete_assets(self) -> None:
|
||||
def delete_assets(self) -> None:
|
||||
recipe_dir = self.item.directory
|
||||
rmtree(recipe_dir, ignore_errors=True)
|
||||
logger.info(f"Recipe Directory Removed: {self.item.slug}")
|
||||
|
||||
37
mealie/services/recipe/recipe_unit_service.py
Normal file
37
mealie/services/recipe/recipe_unit_service.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from functools import cached_property
|
||||
|
||||
from mealie.schema.recipe.recipe_ingredient import CreateIngredientUnit, IngredientUnit
|
||||
from mealie.services._base_http_service.crud_http_mixins import CrudHttpMixins
|
||||
from mealie.services._base_http_service.http_services import UserHttpService
|
||||
from mealie.services.events import create_recipe_event
|
||||
|
||||
|
||||
class RecipeUnitService(
|
||||
CrudHttpMixins[IngredientUnit, CreateIngredientUnit, CreateIngredientUnit],
|
||||
UserHttpService[int, IngredientUnit],
|
||||
):
|
||||
event_func = create_recipe_event
|
||||
_restrict_by_group = False
|
||||
_schema = IngredientUnit
|
||||
|
||||
@cached_property
|
||||
def dal(self):
|
||||
return self.db.ingredient_units
|
||||
|
||||
def populate_item(self, id: int) -> IngredientUnit:
|
||||
self.item = self.dal.get_one(id)
|
||||
return self.item
|
||||
|
||||
def get_all(self) -> list[IngredientUnit]:
|
||||
return self.dal.get_all()
|
||||
|
||||
def create_one(self, data: CreateIngredientUnit) -> IngredientUnit:
|
||||
return self._create_one(data)
|
||||
|
||||
def update_one(self, data: IngredientUnit, item_id: int = None) -> IngredientUnit:
|
||||
return self._update_one(data, item_id)
|
||||
|
||||
def delete_one(self, id: int = None) -> IngredientUnit:
|
||||
return self._delete_one(id)
|
||||
@@ -3,7 +3,7 @@ import datetime
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
|
||||
from mealie.core import root_logger
|
||||
from mealie.db.database import db
|
||||
from mealie.db.database import get_database
|
||||
from mealie.db.db_setup import create_session
|
||||
from mealie.db.models.event import Event
|
||||
from mealie.schema.user import GroupInDB
|
||||
@@ -39,7 +39,8 @@ def update_webhook_schedule():
|
||||
poll the database for changes and reschedule the webhook time
|
||||
"""
|
||||
session = create_session()
|
||||
all_groups: list[GroupInDB] = db.groups.get_all(session)
|
||||
db = get_database(session)
|
||||
all_groups: list[GroupInDB] = db.groups.get_all()
|
||||
|
||||
for group in all_groups:
|
||||
|
||||
@@ -100,7 +101,8 @@ def add_group_to_schedule(scheduler, group: GroupInDB):
|
||||
|
||||
def init_webhook_schedule(scheduler, job_store: dict):
|
||||
session = create_session()
|
||||
all_groups: list[GroupInDB] = db.groups.get_all(session)
|
||||
db = get_database(session)
|
||||
all_groups: list[GroupInDB] = db.groups.get_all()
|
||||
|
||||
for group in all_groups:
|
||||
job_store.update(add_group_to_schedule(scheduler, group))
|
||||
|
||||
@@ -29,14 +29,14 @@ class RegistrationService(PublicHttpService[int, str]):
|
||||
|
||||
elif registration.group_token and registration.group_token != "":
|
||||
|
||||
token_entry = self.db.group_tokens.get(self.session, registration.group_token)
|
||||
token_entry = self.db.group_invite_tokens.get(registration.group_token)
|
||||
|
||||
print("Token Entry", token_entry)
|
||||
|
||||
if not token_entry:
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST, {"message": "Invalid group token"})
|
||||
|
||||
group = self.db.groups.get(self.session, token_entry.group_id)
|
||||
group = self.db.groups.get(token_entry.group_id)
|
||||
|
||||
else:
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST, {"message": "Missing group"})
|
||||
@@ -47,10 +47,10 @@ class RegistrationService(PublicHttpService[int, str]):
|
||||
token_entry.uses_left = token_entry.uses_left - 1
|
||||
|
||||
if token_entry.uses_left == 0:
|
||||
self.db.group_tokens.delete(self.session, token_entry.token)
|
||||
self.db.group_invite_tokens.delete(token_entry.token)
|
||||
|
||||
else:
|
||||
self.db.group_tokens.update(self.session, token_entry.token, token_entry)
|
||||
self.db.group_invite_tokens.update(token_entry.token, token_entry)
|
||||
|
||||
return user
|
||||
|
||||
@@ -64,7 +64,7 @@ class RegistrationService(PublicHttpService[int, str]):
|
||||
group=group.name,
|
||||
)
|
||||
|
||||
return self.db.users.create(self.session, new_user)
|
||||
return self.db.users.create(new_user)
|
||||
|
||||
def _register_new_group(self) -> GroupInDB:
|
||||
group_data = GroupBase(name=self.registration.group)
|
||||
@@ -81,4 +81,4 @@ class RegistrationService(PublicHttpService[int, str]):
|
||||
recipe_disable_amount=self.registration.advanced,
|
||||
)
|
||||
|
||||
return create_new_group(self.session, group_data, group_preferences)
|
||||
return create_new_group(self.db, group_data, group_preferences)
|
||||
|
||||
@@ -25,7 +25,7 @@ class UserService(UserHttpService[int, str]):
|
||||
|
||||
def _populate_target_user(self, id: int = None):
|
||||
if id:
|
||||
self.target_user = self.db.users.get(self.session, id)
|
||||
self.target_user = self.db.users.get(id)
|
||||
if not self.target_user:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND)
|
||||
else:
|
||||
@@ -38,4 +38,4 @@ class UserService(UserHttpService[int, str]):
|
||||
|
||||
self.target_user.password = hash_password(password_change.new_password)
|
||||
|
||||
return self.db.users.update_password(self.session, self.target_user.id, self.target_user.password)
|
||||
return self.db.users.update_password(self.target_user.id, self.target_user.password)
|
||||
|
||||
Reference in New Issue
Block a user