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:
Michael Genson
2022-08-27 13:52:45 -05:00
committed by GitHub
parent caa9e03050
commit 23c039b42d
17 changed files with 720 additions and 403 deletions

View File

@@ -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

View File

@@ -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])

View File

@@ -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
],
),
)