mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-02-12 02:43:12 -05:00
refactor: event bus refactor (#1574)
* refactored event dispatching added EventDocumentType and EventOperation to Event added event listeners to bulk recipe changes overhauled shopping list item events to be more useful modified shopping list item repo to return more information * added internal documentation for event types * renamed message_types.py to event_types.py * added unique event id and fixed instantiation * generalized event listeners and publishers moved apprise publisher to new apprise event listener fixed duplicate message bug with apprise publisher * added JWT field for user-specified integration id * removed obselete test notification route * tuned up existing notification tests * added dependency to get integration_id from jwt * added base crud controller to facilitate events * simplified event publishing * temporarily fixed test notification
This commit is contained in:
@@ -6,7 +6,7 @@ from pydantic import UUID4
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from mealie.core.config import get_app_dirs, get_app_settings
|
||||
from mealie.core.dependencies.dependencies import get_admin_user, get_current_user
|
||||
from mealie.core.dependencies.dependencies import get_admin_user, get_current_user, get_integration_id
|
||||
from mealie.core.exceptions import mealie_registered_exceptions
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.core.settings.directories import AppDirectories
|
||||
@@ -17,6 +17,8 @@ from mealie.lang.providers import Translator
|
||||
from mealie.repos.all_repositories import AllRepositories
|
||||
from mealie.routes._base.checks import OperationChecks
|
||||
from mealie.schema.user.user import GroupInDB, PrivateUser
|
||||
from mealie.services.event_bus_service.event_bus_service import EventBusService
|
||||
from mealie.services.event_bus_service.event_types import EventDocumentDataBase, EventTypes
|
||||
|
||||
|
||||
class _BaseController(ABC):
|
||||
@@ -78,6 +80,7 @@ class BaseUserController(_BaseController):
|
||||
"""
|
||||
|
||||
user: PrivateUser = Depends(get_current_user)
|
||||
integration_id: str = Depends(get_integration_id)
|
||||
translator: Translator = Depends(local_provider)
|
||||
|
||||
# Manual Cache
|
||||
@@ -112,3 +115,20 @@ class BaseAdminController(BaseUserController):
|
||||
"""
|
||||
|
||||
user: PrivateUser = Depends(get_admin_user)
|
||||
|
||||
|
||||
class BaseCrudController(BaseUserController):
|
||||
"""
|
||||
Base class for all CRUD controllers to facilitate common CRUD functions.
|
||||
"""
|
||||
|
||||
event_bus: EventBusService = Depends(EventBusService)
|
||||
|
||||
def publish_event(self, event_type: EventTypes, document_data: EventDocumentDataBase, message: str = "") -> None:
|
||||
self.event_bus.dispatch(
|
||||
integration_id=self.integration_id,
|
||||
group_id=self.group_id,
|
||||
event_type=event_type,
|
||||
document_data=document_data,
|
||||
message=message,
|
||||
)
|
||||
|
||||
@@ -4,24 +4,25 @@ from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import UUID4
|
||||
|
||||
from mealie.core.exceptions import mealie_registered_exceptions
|
||||
from mealie.routes._base import BaseUserController, controller
|
||||
from mealie.routes._base import BaseCrudController, controller
|
||||
from mealie.routes._base.mixins import HttpRepo
|
||||
from mealie.routes._base.routers import MealieCrudRoute
|
||||
from mealie.schema import mapper
|
||||
from mealie.schema.cookbook import CreateCookBook, ReadCookBook, RecipeCookBook, SaveCookBook, UpdateCookBook
|
||||
from mealie.schema.cookbook.cookbook import CookBookPagination
|
||||
from mealie.schema.response.pagination import PaginationQuery
|
||||
from mealie.services.event_bus_service.event_bus_service import EventBusService, EventSource
|
||||
from mealie.services.event_bus_service.message_types import EventTypes
|
||||
from mealie.services.event_bus_service.event_types import (
|
||||
EventCookbookBulkData,
|
||||
EventCookbookData,
|
||||
EventOperation,
|
||||
EventTypes,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/groups/cookbooks", tags=["Groups: Cookbooks"], route_class=MealieCrudRoute)
|
||||
|
||||
|
||||
@controller(router)
|
||||
class GroupCookbookController(BaseUserController):
|
||||
|
||||
event_bus: EventBusService = Depends(EventBusService)
|
||||
|
||||
class GroupCookbookController(BaseCrudController):
|
||||
@cached_property
|
||||
def repo(self):
|
||||
return self.repos.cookbooks.by_group(self.group_id)
|
||||
@@ -53,16 +54,16 @@ class GroupCookbookController(BaseUserController):
|
||||
@router.post("", response_model=ReadCookBook, status_code=201)
|
||||
def create_one(self, data: CreateCookBook):
|
||||
data = mapper.cast(data, SaveCookBook, group_id=self.group_id)
|
||||
val = self.mixins.create_one(data)
|
||||
cookbook = self.mixins.create_one(data)
|
||||
|
||||
if val:
|
||||
self.event_bus.dispatch(
|
||||
self.user.group_id,
|
||||
EventTypes.cookbook_created,
|
||||
msg=self.t("notifications.generic-created", name=val.name),
|
||||
event_source=EventSource(event_type="create", item_type="cookbook", item_id=val.id, slug=val.slug),
|
||||
if cookbook:
|
||||
self.publish_event(
|
||||
event_type=EventTypes.cookbook_created,
|
||||
document_data=EventCookbookData(operation=EventOperation.create, cookbook_id=cookbook.id),
|
||||
message=self.t("notifications.generic-created", name=cookbook.name),
|
||||
)
|
||||
return val
|
||||
|
||||
return cookbook
|
||||
|
||||
@router.put("", response_model=list[ReadCookBook])
|
||||
def update_many(self, data: list[UpdateCookBook]):
|
||||
@@ -72,6 +73,14 @@ class GroupCookbookController(BaseUserController):
|
||||
cb = self.mixins.update_one(cookbook, cookbook.id)
|
||||
updated.append(cb)
|
||||
|
||||
if updated:
|
||||
self.publish_event(
|
||||
event_type=EventTypes.cookbook_updated,
|
||||
document_data=EventCookbookBulkData(
|
||||
operation=EventOperation.update, cookbook_ids=[cb.id for cb in updated]
|
||||
),
|
||||
)
|
||||
|
||||
return updated
|
||||
|
||||
@router.get("/{item_id}", response_model=RecipeCookBook)
|
||||
@@ -96,25 +105,24 @@ class GroupCookbookController(BaseUserController):
|
||||
|
||||
@router.put("/{item_id}", response_model=ReadCookBook)
|
||||
def update_one(self, item_id: str, data: CreateCookBook):
|
||||
val = self.mixins.update_one(data, item_id) # type: ignore
|
||||
if val:
|
||||
self.event_bus.dispatch(
|
||||
self.user.group_id,
|
||||
EventTypes.cookbook_updated,
|
||||
msg=self.t("notifications.generic-updated", name=val.name),
|
||||
event_source=EventSource(event_type="update", item_type="cookbook", item_id=val.id, slug=val.slug),
|
||||
cookbook = self.mixins.update_one(data, item_id) # type: ignore
|
||||
if cookbook:
|
||||
self.publish_event(
|
||||
event_type=EventTypes.cookbook_updated,
|
||||
document_data=EventCookbookData(operation=EventOperation.update, cookbook_id=cookbook.id),
|
||||
message=self.t("notifications.generic-updated", name=cookbook.name),
|
||||
)
|
||||
|
||||
return val
|
||||
return cookbook
|
||||
|
||||
@router.delete("/{item_id}", response_model=ReadCookBook)
|
||||
def delete_one(self, item_id: str):
|
||||
val = self.mixins.delete_one(item_id)
|
||||
if val:
|
||||
self.event_bus.dispatch(
|
||||
self.user.group_id,
|
||||
EventTypes.cookbook_deleted,
|
||||
msg=self.t("notifications.generic-deleted", name=val.name),
|
||||
event_source=EventSource(event_type="delete", item_type="cookbook", item_id=val.id, slug=val.slug),
|
||||
cookbook = self.mixins.delete_one(item_id)
|
||||
if cookbook:
|
||||
self.publish_event(
|
||||
event_type=EventTypes.cookbook_deleted,
|
||||
document_data=EventCookbookData(operation=EventOperation.delete, cookbook_id=cookbook.id),
|
||||
message=self.t("notifications.generic-deleted", name=cookbook.name),
|
||||
)
|
||||
return val
|
||||
|
||||
return cookbook
|
||||
|
||||
@@ -17,7 +17,16 @@ from mealie.schema.group.group_events import (
|
||||
)
|
||||
from mealie.schema.mapper import cast
|
||||
from mealie.schema.response.pagination import PaginationQuery
|
||||
from mealie.services.event_bus_service.event_bus_listeners import AppriseEventListener
|
||||
from mealie.services.event_bus_service.event_bus_service import EventBusService
|
||||
from mealie.services.event_bus_service.event_types import (
|
||||
Event,
|
||||
EventBusMessage,
|
||||
EventDocumentDataBase,
|
||||
EventDocumentType,
|
||||
EventOperation,
|
||||
EventTypes,
|
||||
)
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/groups/events/notifications", tags=["Group: Event Notifications"], route_class=MealieCrudRoute
|
||||
@@ -78,7 +87,18 @@ class GroupEventsNotifierController(BaseUserController):
|
||||
# =======================================================================
|
||||
# Test Event Notifications
|
||||
|
||||
# TODO: properly re-implement this with new event listeners
|
||||
@router.post("/{item_id}/test", status_code=204)
|
||||
def test_notification(self, item_id: UUID4):
|
||||
item: GroupEventNotifierPrivate = self.repo.get_one(item_id, override_schema=GroupEventNotifierPrivate)
|
||||
self.event_bus.test_publisher(item.apprise_url)
|
||||
|
||||
event_type = EventTypes.test_message
|
||||
test_event = Event(
|
||||
message=EventBusMessage.from_type(event_type, "test message"),
|
||||
event_type=event_type,
|
||||
integration_id="test_event",
|
||||
document_data=EventDocumentDataBase(document_type=EventDocumentType.generic, operation=EventOperation.info),
|
||||
)
|
||||
|
||||
test_listener = AppriseEventListener(self.event_bus.session, self.group_id)
|
||||
test_listener.publish_to_subscribers(test_event, [item.apprise_url])
|
||||
|
||||
@@ -3,7 +3,7 @@ from functools import cached_property
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from pydantic import UUID4
|
||||
|
||||
from mealie.routes._base.base_controllers import BaseUserController
|
||||
from mealie.routes._base.base_controllers import BaseCrudController
|
||||
from mealie.routes._base.controller import controller
|
||||
from mealie.routes._base.mixins import HttpRepo
|
||||
from mealie.schema.group.group_shopping_list import (
|
||||
@@ -20,18 +20,20 @@ from mealie.schema.group.group_shopping_list import (
|
||||
from mealie.schema.mapper import cast
|
||||
from mealie.schema.response.pagination import PaginationQuery
|
||||
from mealie.schema.response.responses import SuccessResponse
|
||||
from mealie.services.event_bus_service.event_bus_service import EventBusService, EventSource
|
||||
from mealie.services.event_bus_service.message_types import EventTypes
|
||||
from mealie.services.event_bus_service.event_types import (
|
||||
EventOperation,
|
||||
EventShoppingListData,
|
||||
EventShoppingListItemBulkData,
|
||||
EventShoppingListItemData,
|
||||
EventTypes,
|
||||
)
|
||||
from mealie.services.group_services.shopping_lists import ShoppingListService
|
||||
|
||||
item_router = APIRouter(prefix="/groups/shopping/items", tags=["Group: Shopping List Items"])
|
||||
|
||||
|
||||
@controller(item_router)
|
||||
class ShoppingListItemController(BaseUserController):
|
||||
|
||||
event_bus: EventBusService = Depends(EventBusService)
|
||||
|
||||
class ShoppingListItemController(BaseCrudController):
|
||||
@cached_property
|
||||
def service(self):
|
||||
return ShoppingListService(self.repos)
|
||||
@@ -79,19 +81,17 @@ class ShoppingListItemController(BaseUserController):
|
||||
shopping_list_item = self.mixins.create_one(data)
|
||||
|
||||
if shopping_list_item:
|
||||
self.event_bus.dispatch(
|
||||
self.user.group_id,
|
||||
EventTypes.shopping_list_updated,
|
||||
msg=self.t(
|
||||
self.publish_event(
|
||||
event_type=EventTypes.shopping_list_updated,
|
||||
document_data=EventShoppingListItemData(
|
||||
operation=EventOperation.create,
|
||||
shopping_list_id=shopping_list_item.shopping_list_id,
|
||||
shopping_list_item_id=shopping_list_item.id,
|
||||
),
|
||||
message=self.t(
|
||||
"notifications.generic-created",
|
||||
name=f"An item on shopping list {shopping_list_item.shopping_list_id}",
|
||||
),
|
||||
event_source=EventSource(
|
||||
event_type="create",
|
||||
item_type="shopping-list-item",
|
||||
item_id=shopping_list_item.id,
|
||||
shopping_list_id=shopping_list_item.shopping_list_id,
|
||||
),
|
||||
)
|
||||
|
||||
return shopping_list_item
|
||||
@@ -105,19 +105,17 @@ class ShoppingListItemController(BaseUserController):
|
||||
shopping_list_item = self.mixins.update_one(data, item_id)
|
||||
|
||||
if shopping_list_item:
|
||||
self.event_bus.dispatch(
|
||||
self.user.group_id,
|
||||
EventTypes.shopping_list_updated,
|
||||
msg=self.t(
|
||||
self.publish_event(
|
||||
event_type=EventTypes.shopping_list_updated,
|
||||
document_data=EventShoppingListItemData(
|
||||
operation=EventOperation.update,
|
||||
shopping_list_id=shopping_list_item.shopping_list_id,
|
||||
shopping_list_item_id=shopping_list_item.id,
|
||||
),
|
||||
message=self.t(
|
||||
"notifications.generic-updated",
|
||||
name=f"An item on shopping list {shopping_list_item.shopping_list_id}",
|
||||
),
|
||||
event_source=EventSource(
|
||||
event_type="update",
|
||||
item_type="shopping-list-item",
|
||||
item_id=shopping_list_item.id,
|
||||
shopping_list_id=shopping_list_item.shopping_list_id,
|
||||
),
|
||||
)
|
||||
|
||||
return shopping_list_item
|
||||
@@ -127,19 +125,17 @@ class ShoppingListItemController(BaseUserController):
|
||||
shopping_list_item = self.mixins.delete_one(item_id) # type: ignore
|
||||
|
||||
if shopping_list_item:
|
||||
self.event_bus.dispatch(
|
||||
self.user.group_id,
|
||||
EventTypes.shopping_list_updated,
|
||||
msg=self.t(
|
||||
self.publish_event(
|
||||
event_type=EventTypes.shopping_list_updated,
|
||||
document_data=EventShoppingListItemData(
|
||||
operation=EventOperation.delete,
|
||||
shopping_list_id=shopping_list_item.shopping_list_id,
|
||||
shopping_list_item_id=shopping_list_item.id,
|
||||
),
|
||||
message=self.t(
|
||||
"notifications.generic-deleted",
|
||||
name=f"An item on shopping list {shopping_list_item.shopping_list_id}",
|
||||
),
|
||||
event_source=EventSource(
|
||||
event_type="delete",
|
||||
item_type="shopping-list-item",
|
||||
item_id=shopping_list_item.id,
|
||||
shopping_list_id=shopping_list_item.shopping_list_id,
|
||||
),
|
||||
)
|
||||
|
||||
return shopping_list_item
|
||||
@@ -149,9 +145,7 @@ router = APIRouter(prefix="/groups/shopping/lists", tags=["Group: Shopping Lists
|
||||
|
||||
|
||||
@controller(router)
|
||||
class ShoppingListController(BaseUserController):
|
||||
event_bus: EventBusService = Depends(EventBusService)
|
||||
|
||||
class ShoppingListController(BaseCrudController):
|
||||
@cached_property
|
||||
def service(self):
|
||||
return ShoppingListService(self.repos)
|
||||
@@ -180,21 +174,16 @@ class ShoppingListController(BaseUserController):
|
||||
@router.post("", response_model=ShoppingListOut, status_code=201)
|
||||
def create_one(self, data: ShoppingListCreate):
|
||||
save_data = cast(data, ShoppingListSave, group_id=self.user.group_id)
|
||||
val = self.mixins.create_one(save_data)
|
||||
shopping_list = self.mixins.create_one(save_data)
|
||||
|
||||
if val:
|
||||
self.event_bus.dispatch(
|
||||
self.user.group_id,
|
||||
EventTypes.shopping_list_created,
|
||||
msg=self.t("notifications.generic-created", name=val.name),
|
||||
event_source=EventSource(
|
||||
event_type="create",
|
||||
item_type="shopping-list",
|
||||
item_id=val.id,
|
||||
),
|
||||
if shopping_list:
|
||||
self.publish_event(
|
||||
event_type=EventTypes.shopping_list_created,
|
||||
document_data=EventShoppingListData(operation=EventOperation.create, shopping_list_id=shopping_list.id),
|
||||
message=self.t("notifications.generic-created", name=shopping_list.name),
|
||||
)
|
||||
|
||||
return val
|
||||
return shopping_list
|
||||
|
||||
@router.get("/{item_id}", response_model=ShoppingListOut)
|
||||
def get_one(self, item_id: UUID4):
|
||||
@@ -202,54 +191,72 @@ class ShoppingListController(BaseUserController):
|
||||
|
||||
@router.put("/{item_id}", response_model=ShoppingListOut)
|
||||
def update_one(self, item_id: UUID4, data: ShoppingListUpdate):
|
||||
data = self.mixins.update_one(data, item_id) # type: ignore
|
||||
if data:
|
||||
self.event_bus.dispatch(
|
||||
self.user.group_id,
|
||||
EventTypes.shopping_list_updated,
|
||||
msg=self.t("notifications.generic-updated", name=data.name),
|
||||
event_source=EventSource(
|
||||
event_type="update",
|
||||
item_type="shopping-list",
|
||||
item_id=data.id,
|
||||
),
|
||||
shopping_list = self.mixins.update_one(data, item_id) # type: ignore
|
||||
|
||||
if shopping_list:
|
||||
self.publish_event(
|
||||
event_type=EventTypes.shopping_list_updated,
|
||||
document_data=EventShoppingListData(operation=EventOperation.update, shopping_list_id=shopping_list.id),
|
||||
message=self.t("notifications.generic-updated", name=shopping_list.name),
|
||||
)
|
||||
return data
|
||||
|
||||
return shopping_list
|
||||
|
||||
@router.delete("/{item_id}", response_model=ShoppingListOut)
|
||||
def delete_one(self, item_id: UUID4):
|
||||
data = self.mixins.delete_one(item_id) # type: ignore
|
||||
if data:
|
||||
self.event_bus.dispatch(
|
||||
self.user.group_id,
|
||||
EventTypes.shopping_list_deleted,
|
||||
msg=self.t("notifications.generic-deleted", name=data.name),
|
||||
event_source=EventSource(
|
||||
event_type="delete",
|
||||
item_type="shopping-list",
|
||||
item_id=data.id,
|
||||
),
|
||||
shopping_list = self.mixins.delete_one(item_id) # type: ignore
|
||||
if shopping_list:
|
||||
self.publish_event(
|
||||
event_type=EventTypes.shopping_list_deleted,
|
||||
document_data=EventShoppingListData(operation=EventOperation.delete, shopping_list_id=shopping_list.id),
|
||||
message=self.t("notifications.generic-deleted", name=shopping_list.name),
|
||||
)
|
||||
return data
|
||||
|
||||
return shopping_list
|
||||
|
||||
# =======================================================================
|
||||
# Other Operations
|
||||
|
||||
@router.post("/{item_id}/recipe/{recipe_id}", response_model=ShoppingListOut)
|
||||
def add_recipe_ingredients_to_list(self, item_id: UUID4, recipe_id: UUID4):
|
||||
shopping_list = self.service.add_recipe_ingredients_to_list(item_id, recipe_id)
|
||||
if shopping_list:
|
||||
self.event_bus.dispatch(
|
||||
self.user.group_id,
|
||||
EventTypes.shopping_list_updated,
|
||||
msg=self.t(
|
||||
"notifications.generic-updated",
|
||||
name=shopping_list.name,
|
||||
(
|
||||
shopping_list,
|
||||
new_shopping_list_items,
|
||||
updated_shopping_list_items,
|
||||
deleted_shopping_list_items,
|
||||
) = self.service.add_recipe_ingredients_to_list(item_id, recipe_id)
|
||||
|
||||
if new_shopping_list_items:
|
||||
self.publish_event(
|
||||
event_type=EventTypes.shopping_list_updated,
|
||||
document_data=EventShoppingListItemBulkData(
|
||||
operation=EventOperation.create,
|
||||
shopping_list_id=shopping_list.id,
|
||||
shopping_list_item_ids=[shopping_list_item.id for shopping_list_item in new_shopping_list_items],
|
||||
),
|
||||
event_source=EventSource(
|
||||
event_type="bulk-updated-items",
|
||||
item_type="shopping-list",
|
||||
item_id=shopping_list.id,
|
||||
)
|
||||
|
||||
if updated_shopping_list_items:
|
||||
self.publish_event(
|
||||
event_type=EventTypes.shopping_list_updated,
|
||||
document_data=EventShoppingListItemBulkData(
|
||||
operation=EventOperation.update,
|
||||
shopping_list_id=shopping_list.id,
|
||||
shopping_list_item_ids=[
|
||||
shopping_list_item.id for shopping_list_item in updated_shopping_list_items
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
if deleted_shopping_list_items:
|
||||
self.publish_event(
|
||||
event_type=EventTypes.shopping_list_updated,
|
||||
document_data=EventShoppingListItemBulkData(
|
||||
operation=EventOperation.delete,
|
||||
shopping_list_id=shopping_list.id,
|
||||
shopping_list_item_ids=[
|
||||
shopping_list_item.id for shopping_list_item in deleted_shopping_list_items
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
@@ -257,19 +264,33 @@ class ShoppingListController(BaseUserController):
|
||||
|
||||
@router.delete("/{item_id}/recipe/{recipe_id}", response_model=ShoppingListOut)
|
||||
def remove_recipe_ingredients_from_list(self, item_id: UUID4, recipe_id: UUID4):
|
||||
shopping_list = self.service.remove_recipe_ingredients_from_list(item_id, recipe_id)
|
||||
if shopping_list:
|
||||
self.event_bus.dispatch(
|
||||
self.user.group_id,
|
||||
EventTypes.shopping_list_updated,
|
||||
msg=self.t(
|
||||
"notifications.generic-updated",
|
||||
name=shopping_list.name,
|
||||
(
|
||||
shopping_list,
|
||||
updated_shopping_list_items,
|
||||
deleted_shopping_list_items,
|
||||
) = self.service.remove_recipe_ingredients_from_list(item_id, recipe_id)
|
||||
|
||||
if updated_shopping_list_items:
|
||||
self.publish_event(
|
||||
event_type=EventTypes.shopping_list_updated,
|
||||
document_data=EventShoppingListItemBulkData(
|
||||
operation=EventOperation.update,
|
||||
shopping_list_id=shopping_list.id,
|
||||
shopping_list_item_ids=[
|
||||
shopping_list_item.id for shopping_list_item in updated_shopping_list_items
|
||||
],
|
||||
),
|
||||
event_source=EventSource(
|
||||
event_type="bulk-updated-items",
|
||||
item_type="shopping-list",
|
||||
item_id=shopping_list.id,
|
||||
)
|
||||
|
||||
if deleted_shopping_list_items:
|
||||
self.publish_event(
|
||||
event_type=EventTypes.shopping_list_updated,
|
||||
document_data=EventShoppingListItemBulkData(
|
||||
operation=EventOperation.delete,
|
||||
shopping_list_id=shopping_list.id,
|
||||
shopping_list_item_ids=[
|
||||
shopping_list_item.id for shopping_list_item in deleted_shopping_list_items
|
||||
],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ from functools import cached_property
|
||||
from fastapi import APIRouter, Depends
|
||||
from pydantic import UUID4, BaseModel
|
||||
|
||||
from mealie.routes._base import BaseUserController, controller
|
||||
from mealie.routes._base import BaseCrudController, controller
|
||||
from mealie.routes._base.mixins import HttpRepo
|
||||
from mealie.schema import mapper
|
||||
from mealie.schema.recipe import CategoryIn, RecipeCategoryResponse
|
||||
@@ -11,8 +11,7 @@ from mealie.schema.recipe.recipe import RecipeCategory, RecipeCategoryPagination
|
||||
from mealie.schema.recipe.recipe_category import CategoryBase, CategorySave
|
||||
from mealie.schema.response.pagination import PaginationQuery
|
||||
from mealie.services import urls
|
||||
from mealie.services.event_bus_service.event_bus_service import EventBusService, EventSource
|
||||
from mealie.services.event_bus_service.message_types import EventTypes
|
||||
from mealie.services.event_bus_service.event_types import EventCategoryData, EventOperation, EventTypes
|
||||
|
||||
router = APIRouter(prefix="/categories", tags=["Organizer: Categories"])
|
||||
|
||||
@@ -27,10 +26,7 @@ class CategorySummary(BaseModel):
|
||||
|
||||
|
||||
@controller(router)
|
||||
class RecipeCategoryController(BaseUserController):
|
||||
|
||||
event_bus: EventBusService = Depends(EventBusService)
|
||||
|
||||
class RecipeCategoryController(BaseCrudController):
|
||||
# =========================================================================
|
||||
# CRUD Operations
|
||||
@cached_property
|
||||
@@ -56,19 +52,19 @@ class RecipeCategoryController(BaseUserController):
|
||||
def create_one(self, category: CategoryIn):
|
||||
"""Creates a Category in the database"""
|
||||
save_data = mapper.cast(category, CategorySave, group_id=self.group_id)
|
||||
data = self.mixins.create_one(save_data)
|
||||
if data:
|
||||
self.event_bus.dispatch(
|
||||
self.user.group_id,
|
||||
EventTypes.category_created,
|
||||
msg=self.t(
|
||||
new_category = self.mixins.create_one(save_data)
|
||||
if new_category:
|
||||
self.publish_event(
|
||||
event_type=EventTypes.category_created,
|
||||
document_data=EventCategoryData(operation=EventOperation.create, category_id=new_category.id),
|
||||
message=self.t(
|
||||
"notifications.generic-created-with-url",
|
||||
name=data.name,
|
||||
url=urls.category_url(data.slug, self.settings.BASE_URL),
|
||||
name=new_category.name,
|
||||
url=urls.category_url(new_category.slug, self.settings.BASE_URL),
|
||||
),
|
||||
event_source=EventSource(event_type="create", item_type="category", item_id=data.id, slug=data.slug),
|
||||
)
|
||||
return data
|
||||
|
||||
return new_category
|
||||
|
||||
@router.get("/{item_id}", response_model=CategorySummary)
|
||||
def get_one(self, item_id: UUID4):
|
||||
@@ -81,20 +77,20 @@ class RecipeCategoryController(BaseUserController):
|
||||
def update_one(self, item_id: UUID4, update_data: CategoryIn):
|
||||
"""Updates an existing Tag in the database"""
|
||||
save_data = mapper.cast(update_data, CategorySave, group_id=self.group_id)
|
||||
data = self.mixins.update_one(save_data, item_id)
|
||||
category = self.mixins.update_one(save_data, item_id)
|
||||
|
||||
if data:
|
||||
self.event_bus.dispatch(
|
||||
self.user.group_id,
|
||||
EventTypes.category_updated,
|
||||
msg=self.t(
|
||||
if category:
|
||||
self.publish_event(
|
||||
event_type=EventTypes.category_updated,
|
||||
document_data=EventCategoryData(operation=EventOperation.update, category_id=category.id),
|
||||
message=self.t(
|
||||
"notifications.generic-updated-with-url",
|
||||
name=data.name,
|
||||
url=urls.category_url(data.slug, self.settings.BASE_URL),
|
||||
name=category.name,
|
||||
url=urls.category_url(category.slug, self.settings.BASE_URL),
|
||||
),
|
||||
event_source=EventSource(event_type="update", item_type="category", item_id=data.id, slug=data.slug),
|
||||
)
|
||||
return data
|
||||
|
||||
return category
|
||||
|
||||
@router.delete("/{item_id}")
|
||||
def delete_one(self, item_id: UUID4):
|
||||
@@ -103,12 +99,11 @@ class RecipeCategoryController(BaseUserController):
|
||||
category does not impact a recipe. The category will be removed
|
||||
from any recipes that contain it
|
||||
"""
|
||||
if data := self.mixins.delete_one(item_id):
|
||||
self.event_bus.dispatch(
|
||||
self.user.group_id,
|
||||
EventTypes.category_deleted,
|
||||
msg=self.t("notifications.generic-deleted", name=data.name),
|
||||
event_source=EventSource(event_type="delete", item_type="category", item_id=data.id, slug=data.slug),
|
||||
if category := self.mixins.delete_one(item_id):
|
||||
self.publish_event(
|
||||
event_type=EventTypes.category_deleted,
|
||||
document_data=EventCategoryData(operation=EventOperation.delete, category_id=category.id),
|
||||
message=self.t("notifications.generic-deleted", name=category.name),
|
||||
)
|
||||
|
||||
# =========================================================================
|
||||
|
||||
@@ -3,7 +3,7 @@ from functools import cached_property
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from pydantic import UUID4
|
||||
|
||||
from mealie.routes._base import BaseUserController, controller
|
||||
from mealie.routes._base import BaseCrudController, controller
|
||||
from mealie.routes._base.mixins import HttpRepo
|
||||
from mealie.schema import mapper
|
||||
from mealie.schema.recipe import RecipeTagResponse, TagIn
|
||||
@@ -11,17 +11,13 @@ from mealie.schema.recipe.recipe import RecipeTag, RecipeTagPagination
|
||||
from mealie.schema.recipe.recipe_category import TagSave
|
||||
from mealie.schema.response.pagination import PaginationQuery
|
||||
from mealie.services import urls
|
||||
from mealie.services.event_bus_service.event_bus_service import EventBusService, EventSource
|
||||
from mealie.services.event_bus_service.message_types import EventTypes
|
||||
from mealie.services.event_bus_service.event_types import EventOperation, EventTagData, EventTypes
|
||||
|
||||
router = APIRouter(prefix="/tags", tags=["Organizer: Tags"])
|
||||
|
||||
|
||||
@controller(router)
|
||||
class TagController(BaseUserController):
|
||||
|
||||
event_bus: EventBusService = Depends(EventBusService)
|
||||
|
||||
class TagController(BaseCrudController):
|
||||
@cached_property
|
||||
def repo(self):
|
||||
return self.repos.tags.by_group(self.group_id)
|
||||
@@ -55,55 +51,58 @@ class TagController(BaseUserController):
|
||||
def create_one(self, tag: TagIn):
|
||||
"""Creates a Tag in the database"""
|
||||
save_data = mapper.cast(tag, TagSave, group_id=self.group_id)
|
||||
data = self.repo.create(save_data)
|
||||
if data:
|
||||
self.event_bus.dispatch(
|
||||
self.user.group_id,
|
||||
EventTypes.tag_created,
|
||||
msg=self.t(
|
||||
new_tag = self.repo.create(save_data)
|
||||
|
||||
if new_tag:
|
||||
self.publish_event(
|
||||
event_type=EventTypes.tag_created,
|
||||
document_data=EventTagData(operation=EventOperation.create, tag_id=new_tag.id),
|
||||
message=self.t(
|
||||
"notifications.generic-created-with-url",
|
||||
name=data.name,
|
||||
url=urls.tag_url(data.slug, self.settings.BASE_URL),
|
||||
name=new_tag.name,
|
||||
url=urls.tag_url(new_tag.slug, self.settings.BASE_URL),
|
||||
),
|
||||
event_source=EventSource(event_type="create", item_type="tag", item_id=data.id, slug=data.slug),
|
||||
)
|
||||
return data
|
||||
|
||||
return new_tag
|
||||
|
||||
@router.put("/{item_id}", response_model=RecipeTagResponse)
|
||||
def update_one(self, item_id: UUID4, new_tag: TagIn):
|
||||
"""Updates an existing Tag in the database"""
|
||||
save_data = mapper.cast(new_tag, TagSave, group_id=self.group_id)
|
||||
data = self.repo.update(item_id, save_data)
|
||||
if data:
|
||||
self.event_bus.dispatch(
|
||||
self.user.group_id,
|
||||
EventTypes.tag_updated,
|
||||
msg=self.t(
|
||||
tag = self.repo.update(item_id, save_data)
|
||||
|
||||
if tag:
|
||||
self.publish_event(
|
||||
event_type=EventTypes.tag_updated,
|
||||
document_data=EventTagData(operation=EventOperation.update, tag_id=tag.id),
|
||||
message=self.t(
|
||||
"notifications.generic-updated-with-url",
|
||||
name=data.name,
|
||||
url=urls.tag_url(data.slug, self.settings.BASE_URL),
|
||||
name=tag.name,
|
||||
url=urls.tag_url(tag.slug, self.settings.BASE_URL),
|
||||
),
|
||||
event_source=EventSource(event_type="update", item_type="tag", item_id=data.id, slug=data.slug),
|
||||
)
|
||||
return data
|
||||
|
||||
return tag
|
||||
|
||||
@router.delete("/{item_id}")
|
||||
def delete_recipe_tag(self, item_id: UUID4):
|
||||
"""Removes a recipe tag from the database. Deleting a
|
||||
"""
|
||||
Removes a recipe tag from the database. Deleting a
|
||||
tag does not impact a recipe. The tag will be removed
|
||||
from any recipes that contain it"""
|
||||
from any recipes that contain it
|
||||
"""
|
||||
|
||||
try:
|
||||
data = self.repo.delete(item_id)
|
||||
tag = self.repo.delete(item_id)
|
||||
except Exception as e:
|
||||
raise HTTPException(status.HTTP_400_BAD_REQUEST) from e
|
||||
|
||||
if data:
|
||||
self.event_bus.dispatch(
|
||||
self.user.group_id,
|
||||
EventTypes.tag_deleted,
|
||||
msg=self.t("notifications.generic-deleted", name=data.name),
|
||||
event_source=EventSource(event_type="delete", item_type="tag", item_id=data.id, slug=data.slug),
|
||||
if tag:
|
||||
self.publish_event(
|
||||
event_type=EventTypes.tag_deleted,
|
||||
document_data=EventTagData(operation=EventOperation.delete, tag_id=tag.id),
|
||||
message=self.t("notifications.generic-deleted", name=tag.name),
|
||||
)
|
||||
|
||||
@router.get("/slug/{tag_slug}", response_model=RecipeTagResponse)
|
||||
|
||||
@@ -18,7 +18,7 @@ from mealie.core.dependencies.dependencies import temporary_dir, validate_recipe
|
||||
from mealie.core.security import create_recipe_slug_token
|
||||
from mealie.pkgs import cache
|
||||
from mealie.repos.repository_recipes import RepositoryRecipes
|
||||
from mealie.routes._base import BaseUserController, controller
|
||||
from mealie.routes._base import BaseCrudController, controller
|
||||
from mealie.routes._base.mixins import HttpRepo
|
||||
from mealie.routes._base.routers import MealieCrudRoute, UserAPIRouter
|
||||
from mealie.schema.recipe import Recipe, RecipeImageTypes, ScrapeRecipe
|
||||
@@ -34,8 +34,12 @@ from mealie.schema.recipe.recipe_scraper import ScrapeRecipeTest
|
||||
from mealie.schema.recipe.request_helpers import RecipeZipTokenResponse, UpdateImageResponse
|
||||
from mealie.schema.response.responses import ErrorResponse
|
||||
from mealie.services import urls
|
||||
from mealie.services.event_bus_service.event_bus_service import EventBusService, EventSource
|
||||
from mealie.services.event_bus_service.message_types import EventTypes
|
||||
from mealie.services.event_bus_service.event_types import (
|
||||
EventOperation,
|
||||
EventRecipeBulkReportData,
|
||||
EventRecipeData,
|
||||
EventTypes,
|
||||
)
|
||||
from mealie.services.recipe.recipe_data_service import InvalidDomainError, NotAnImageError, RecipeDataService
|
||||
from mealie.services.recipe.recipe_service import RecipeService
|
||||
from mealie.services.recipe.template_service import TemplateService
|
||||
@@ -45,7 +49,7 @@ from mealie.services.scraper.scraper import create_from_url
|
||||
from mealie.services.scraper.scraper_strategies import ForceTimeoutException, RecipeScraperPackage
|
||||
|
||||
|
||||
class BaseRecipeController(BaseUserController):
|
||||
class BaseRecipeController(BaseCrudController):
|
||||
@cached_property
|
||||
def repo(self) -> RepositoryRecipes:
|
||||
return self.repos.recipes.by_group(self.group_id)
|
||||
@@ -119,8 +123,6 @@ router = UserAPIRouter(prefix="/recipes", tags=["Recipe: CRUD"], route_class=Mea
|
||||
|
||||
@controller(router)
|
||||
class RecipeController(BaseRecipeController):
|
||||
event_bus: EventBusService = Depends(EventBusService)
|
||||
|
||||
def handle_exceptions(self, ex: Exception) -> None:
|
||||
match type(ex):
|
||||
case exceptions.PermissionDenied:
|
||||
@@ -161,17 +163,14 @@ class RecipeController(BaseRecipeController):
|
||||
new_recipe = self.service.create_one(recipe)
|
||||
|
||||
if new_recipe:
|
||||
self.event_bus.dispatch(
|
||||
self.user.group_id,
|
||||
EventTypes.recipe_created,
|
||||
msg=self.t(
|
||||
self.publish_event(
|
||||
event_type=EventTypes.recipe_created,
|
||||
document_data=EventRecipeData(operation=EventOperation.create, recipe_slug=new_recipe.slug),
|
||||
message=self.t(
|
||||
"notifications.generic-created-with-url",
|
||||
name=new_recipe.name,
|
||||
url=urls.recipe_url(new_recipe.slug, self.settings.BASE_URL),
|
||||
),
|
||||
event_source=EventSource(
|
||||
event_type="create", item_type="recipe", item_id=new_recipe.id, slug=new_recipe.slug
|
||||
),
|
||||
)
|
||||
|
||||
return new_recipe.slug
|
||||
@@ -183,6 +182,11 @@ class RecipeController(BaseRecipeController):
|
||||
report_id = bulk_scraper.get_report_id()
|
||||
bg_tasks.add_task(bulk_scraper.scrape, bulk)
|
||||
|
||||
self.publish_event(
|
||||
event_type=EventTypes.recipe_created,
|
||||
document_data=EventRecipeBulkReportData(operation=EventOperation.create, report_id=report_id),
|
||||
)
|
||||
|
||||
return {"reportId": report_id}
|
||||
|
||||
@router.post("/test-scrape-url")
|
||||
@@ -202,6 +206,11 @@ class RecipeController(BaseRecipeController):
|
||||
def create_recipe_from_zip(self, temp_path=Depends(temporary_zip_path), archive: UploadFile = File(...)):
|
||||
"""Create recipe from archive"""
|
||||
recipe = self.service.create_from_zip(archive, temp_path)
|
||||
self.publish_event(
|
||||
event_type=EventTypes.recipe_created,
|
||||
document_data=EventRecipeData(operation=EventOperation.create, recipe_slug=recipe.slug),
|
||||
)
|
||||
|
||||
return recipe.slug
|
||||
|
||||
# ==================================================================================================================
|
||||
@@ -215,7 +224,7 @@ class RecipeController(BaseRecipeController):
|
||||
tags: Optional[list[UUID4 | str]] = Query(None),
|
||||
tools: Optional[list[UUID4 | str]] = Query(None),
|
||||
):
|
||||
response = self.repo.page_all(
|
||||
pagination_response = self.repo.page_all(
|
||||
pagination=q,
|
||||
load_food=q.load_food,
|
||||
categories=categories,
|
||||
@@ -223,10 +232,10 @@ class RecipeController(BaseRecipeController):
|
||||
tools=tools,
|
||||
)
|
||||
|
||||
response.set_pagination_guides(router.url_path_for("get_all"), q.dict())
|
||||
pagination_response.set_pagination_guides(router.url_path_for("get_all"), q.dict())
|
||||
|
||||
new_items = []
|
||||
for item in response.items:
|
||||
for item in pagination_response.items:
|
||||
# Pydantic/FastAPI can't seem to serialize the ingredient field on thier own.
|
||||
new_item = item.__dict__
|
||||
|
||||
@@ -235,8 +244,8 @@ class RecipeController(BaseRecipeController):
|
||||
|
||||
new_items.append(new_item)
|
||||
|
||||
response.items = [RecipeSummary.construct(**x) for x in new_items]
|
||||
json_compatible_response = jsonable_encoder(response)
|
||||
pagination_response.items = [RecipeSummary.construct(**x) for x in new_items]
|
||||
json_compatible_response = jsonable_encoder(pagination_response)
|
||||
|
||||
# Response is returned directly, to avoid validation and improve performance
|
||||
return JSONResponse(content=json_compatible_response)
|
||||
@@ -256,17 +265,14 @@ class RecipeController(BaseRecipeController):
|
||||
return None
|
||||
|
||||
if new_recipe:
|
||||
self.event_bus.dispatch(
|
||||
self.user.group_id,
|
||||
EventTypes.recipe_created,
|
||||
msg=self.t(
|
||||
self.publish_event(
|
||||
event_type=EventTypes.recipe_created,
|
||||
document_data=EventRecipeData(operation=EventOperation.create, recipe_slug=new_recipe.slug),
|
||||
message=self.t(
|
||||
"notifications.generic-created-with-url",
|
||||
name=new_recipe.name,
|
||||
url=urls.recipe_url(new_recipe.slug, self.settings.BASE_URL),
|
||||
),
|
||||
event_source=EventSource(
|
||||
event_type="create", item_type="recipe", item_id=new_recipe.id, slug=new_recipe.slug
|
||||
),
|
||||
)
|
||||
|
||||
return new_recipe.slug
|
||||
@@ -275,63 +281,60 @@ class RecipeController(BaseRecipeController):
|
||||
def update_one(self, slug: str, data: Recipe):
|
||||
"""Updates a recipe by existing slug and data."""
|
||||
try:
|
||||
data = self.service.update_one(slug, data)
|
||||
recipe = self.service.update_one(slug, data)
|
||||
except Exception as e:
|
||||
self.handle_exceptions(e)
|
||||
|
||||
if data:
|
||||
self.event_bus.dispatch(
|
||||
self.user.group_id,
|
||||
EventTypes.recipe_updated,
|
||||
msg=self.t(
|
||||
if recipe:
|
||||
self.publish_event(
|
||||
event_type=EventTypes.recipe_updated,
|
||||
document_data=EventRecipeData(operation=EventOperation.update, recipe_slug=recipe.slug),
|
||||
message=self.t(
|
||||
"notifications.generic-updated-with-url",
|
||||
name=data.name,
|
||||
url=urls.recipe_url(data.slug, self.settings.BASE_URL),
|
||||
name=recipe.name,
|
||||
url=urls.recipe_url(recipe.slug, self.settings.BASE_URL),
|
||||
),
|
||||
event_source=EventSource(event_type="update", item_type="recipe", item_id=data.id, slug=data.slug),
|
||||
)
|
||||
|
||||
return data
|
||||
return recipe
|
||||
|
||||
@router.patch("/{slug}")
|
||||
def patch_one(self, slug: str, data: Recipe):
|
||||
"""Updates a recipe by existing slug and data."""
|
||||
try:
|
||||
data = self.service.patch_one(slug, data)
|
||||
recipe = self.service.patch_one(slug, data)
|
||||
except Exception as e:
|
||||
self.handle_exceptions(e)
|
||||
|
||||
if data:
|
||||
self.event_bus.dispatch(
|
||||
self.user.group_id,
|
||||
EventTypes.recipe_updated,
|
||||
msg=self.t(
|
||||
if recipe:
|
||||
self.publish_event(
|
||||
event_type=EventTypes.recipe_updated,
|
||||
document_data=EventRecipeData(operation=EventOperation.update, recipe_slug=recipe.slug),
|
||||
message=self.t(
|
||||
"notifications.generic-updated-with-url",
|
||||
name=data.name,
|
||||
url=urls.recipe_url(data.slug, self.settings.BASE_URL),
|
||||
name=recipe.name,
|
||||
url=urls.recipe_url(recipe.slug, self.settings.BASE_URL),
|
||||
),
|
||||
event_source=EventSource(event_type="update", item_type="recipe", item_id=data.id, slug=data.slug),
|
||||
)
|
||||
|
||||
return data
|
||||
return recipe
|
||||
|
||||
@router.delete("/{slug}")
|
||||
def delete_one(self, slug: str):
|
||||
"""Deletes a recipe by slug"""
|
||||
try:
|
||||
data = self.service.delete_one(slug)
|
||||
recipe = self.service.delete_one(slug)
|
||||
except Exception as e:
|
||||
self.handle_exceptions(e)
|
||||
|
||||
if data:
|
||||
self.event_bus.dispatch(
|
||||
self.user.group_id,
|
||||
EventTypes.recipe_deleted,
|
||||
msg=self.t("notifications.generic-deleted", name=data.name),
|
||||
event_source=EventSource(event_type="delete", item_type="recipe", item_id=data.id, slug=data.slug),
|
||||
if recipe:
|
||||
self.publish_event(
|
||||
event_type=EventTypes.recipe_deleted,
|
||||
document_data=EventRecipeData(operation=EventOperation.delete, recipe_slug=recipe.slug),
|
||||
message=self.t("notifications.generic-deleted", name=recipe.name),
|
||||
)
|
||||
|
||||
return data
|
||||
return recipe
|
||||
|
||||
# ==================================================================================================================
|
||||
# Image and Assets
|
||||
|
||||
@@ -15,17 +15,22 @@ class UserApiTokensController(BaseUserController):
|
||||
@router.post("/api-tokens", status_code=status.HTTP_201_CREATED, response_model=LongLiveTokenOut)
|
||||
def create_api_token(
|
||||
self,
|
||||
token_name: LongLiveTokenIn,
|
||||
token_params: LongLiveTokenIn,
|
||||
):
|
||||
"""Create api_token in the Database"""
|
||||
|
||||
token_data = {"long_token": True, "id": str(self.user.id)}
|
||||
token_data = {
|
||||
"long_token": True,
|
||||
"id": str(self.user.id),
|
||||
"name": token_params.name,
|
||||
"integration_id": token_params.integration_id,
|
||||
}
|
||||
|
||||
five_years = timedelta(1825)
|
||||
token = create_access_token(token_data, five_years)
|
||||
|
||||
token_model = CreateToken(
|
||||
name=token_name.name,
|
||||
name=token_params.name,
|
||||
token=token,
|
||||
user_id=self.user.id,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user