feat: extend Apprise JSON notification functionality with programmatic data (#1355)

* Fixed incorrect generic deleted notification text

* Added custom "event_source" header for json notifs

* Added internal reference data to event notifs

* Added event listeners to shopping list items

* Fixed type issues

* moved JSON event source k:v pairs to message body

* added hook for all supported custom endpoints
fixed bug that excluded non-custom notification types

* created event_source class to replace loosely-typed dict

* fixed silent error when dispatching a null task

* moved url updates to static function

* added unit tests for event_source url manipulation

* removed array from event bus (it's unsupported)
This commit is contained in:
Michael Genson
2022-06-15 14:49:42 -05:00
committed by GitHub
parent 3030e3e7f4
commit 754e77c9cb
42 changed files with 296 additions and 54 deletions

View File

@@ -8,7 +8,7 @@ from mealie.routes._base import BaseUserController, controller
from mealie.routes._base.mixins import HttpRepo
from mealie.schema import mapper
from mealie.schema.cookbook import CreateCookBook, ReadCookBook, RecipeCookBook, SaveCookBook, UpdateCookBook
from mealie.services.event_bus_service.event_bus_service import EventBusService
from mealie.services.event_bus_service.event_bus_service import EventBusService, EventSource
from mealie.services.event_bus_service.message_types import EventTypes
router = APIRouter(prefix="/groups/cookbooks", tags=["Groups: Cookbooks"])
@@ -53,6 +53,7 @@ class GroupCookbookController(BaseUserController):
self.deps.acting_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),
)
return val
@@ -94,6 +95,7 @@ class GroupCookbookController(BaseUserController):
self.deps.acting_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),
)
return val
@@ -106,5 +108,6 @@ class GroupCookbookController(BaseUserController):
self.deps.acting_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),
)
return val

View File

@@ -19,7 +19,7 @@ from mealie.schema.group.group_shopping_list import (
from mealie.schema.mapper import cast
from mealie.schema.query import GetAll
from mealie.schema.response.responses import SuccessResponse
from mealie.services.event_bus_service.event_bus_service import EventBusService
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.group_services.shopping_lists import ShoppingListService
@@ -75,7 +75,25 @@ class ShoppingListItemController(BaseUserController):
@item_router.post("", response_model=ShoppingListItemOut, status_code=201)
def create_one(self, data: ShoppingListItemCreate):
return self.mixins.create_one(data)
shopping_list_item = self.mixins.create_one(data)
if shopping_list_item:
self.event_bus.dispatch(
self.deps.acting_user.group_id,
EventTypes.shopping_list_updated,
msg=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
@item_router.get("/{item_id}", response_model=ShoppingListItemOut)
def get_one(self, item_id: UUID4):
@@ -83,11 +101,47 @@ class ShoppingListItemController(BaseUserController):
@item_router.put("/{item_id}", response_model=ShoppingListItemOut)
def update_one(self, item_id: UUID4, data: ShoppingListItemUpdate):
return self.mixins.update_one(data, item_id)
shopping_list_item = self.mixins.update_one(data, item_id)
if shopping_list_item:
self.event_bus.dispatch(
self.deps.acting_user.group_id,
EventTypes.shopping_list_updated,
msg=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
@item_router.delete("/{item_id}", response_model=ShoppingListItemOut)
def delete_one(self, item_id: UUID4):
return self.mixins.delete_one(item_id) # type: ignore
shopping_list_item = self.mixins.delete_one(item_id) # type: ignore
if shopping_list_item:
self.event_bus.dispatch(
self.deps.acting_user.group_id,
EventTypes.shopping_list_updated,
msg=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
router = APIRouter(prefix="/groups/shopping/lists", tags=["Group: Shopping Lists"])
@@ -126,6 +180,11 @@ class ShoppingListController(BaseUserController):
self.deps.acting_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,
),
)
return val
@@ -142,6 +201,11 @@ class ShoppingListController(BaseUserController):
self.deps.acting_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,
),
)
return data
@@ -151,8 +215,13 @@ class ShoppingListController(BaseUserController):
if data:
self.event_bus.dispatch(
self.deps.acting_user.group_id,
EventTypes.shopping_list_updated,
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,
),
)
return data
@@ -161,8 +230,40 @@ class ShoppingListController(BaseUserController):
@router.post("/{item_id}/recipe/{recipe_id}", response_model=ShoppingListOut)
def add_recipe_ingredients_to_list(self, item_id: UUID4, recipe_id: UUID4):
return self.service.add_recipe_ingredients_to_list(item_id, recipe_id)
shopping_list = self.service.add_recipe_ingredients_to_list(item_id, recipe_id)
if shopping_list:
self.event_bus.dispatch(
self.deps.acting_user.group_id,
EventTypes.shopping_list_updated,
msg=self.t(
"notifications.generic-updated",
name=shopping_list.name,
),
event_source=EventSource(
event_type="bulk-updated-items",
item_type="shopping-list",
item_id=shopping_list.id,
),
)
return shopping_list
@router.delete("/{item_id}/recipe/{recipe_id}", response_model=ShoppingListOut)
def remove_recipe_ingredients_from_list(self, item_id: UUID4, recipe_id: UUID4):
return self.service.remove_recipe_ingredients_from_list(item_id, recipe_id)
shopping_list = self.service.remove_recipe_ingredients_from_list(item_id, recipe_id)
if shopping_list:
self.event_bus.dispatch(
self.deps.acting_user.group_id,
EventTypes.shopping_list_updated,
msg=self.t(
"notifications.generic-updated",
name=shopping_list.name,
),
event_source=EventSource(
event_type="bulk-updated-items",
item_type="shopping-list",
item_id=shopping_list.id,
),
)
return shopping_list

View File

@@ -10,7 +10,7 @@ from mealie.schema.recipe import CategoryIn, RecipeCategoryResponse
from mealie.schema.recipe.recipe import RecipeCategory
from mealie.schema.recipe.recipe_category import CategoryBase, CategorySave
from mealie.services import urls
from mealie.services.event_bus_service.event_bus_service import EventBusService
from mealie.services.event_bus_service.event_bus_service import EventBusService, EventSource
from mealie.services.event_bus_service.message_types import EventTypes
router = APIRouter(prefix="/categories", tags=["Organizer: Categories"])
@@ -59,6 +59,7 @@ class RecipeCategoryController(BaseUserController):
name=data.name,
url=urls.category_url(data.slug, self.deps.settings.BASE_URL),
),
event_source=EventSource(event_type="create", item_type="category", item_id=data.id, slug=data.slug),
)
return data
@@ -84,6 +85,7 @@ class RecipeCategoryController(BaseUserController):
name=data.name,
url=urls.category_url(data.slug, self.deps.settings.BASE_URL),
),
event_source=EventSource(event_type="update", item_type="category", item_id=data.id, slug=data.slug),
)
return data
@@ -99,6 +101,7 @@ class RecipeCategoryController(BaseUserController):
self.deps.acting_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),
)
# =========================================================================

View File

@@ -10,7 +10,7 @@ from mealie.schema.recipe import RecipeTagResponse, TagIn
from mealie.schema.recipe.recipe import RecipeTag
from mealie.schema.recipe.recipe_category import TagSave
from mealie.services import urls
from mealie.services.event_bus_service.event_bus_service import EventBusService
from mealie.services.event_bus_service.event_bus_service import EventBusService, EventSource
from mealie.services.event_bus_service.message_types import EventTypes
router = APIRouter(prefix="/tags", tags=["Organizer: Tags"])
@@ -58,6 +58,7 @@ class TagController(BaseUserController):
name=data.name,
url=urls.tag_url(data.slug, self.deps.settings.BASE_URL),
),
event_source=EventSource(event_type="create", item_type="tag", item_id=data.id, slug=data.slug),
)
return data
@@ -75,6 +76,7 @@ class TagController(BaseUserController):
name=data.name,
url=urls.tag_url(data.slug, self.deps.settings.BASE_URL),
),
event_source=EventSource(event_type="update", item_type="tag", item_id=data.id, slug=data.slug),
)
return data
@@ -94,6 +96,7 @@ class TagController(BaseUserController):
self.deps.acting_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),
)
@router.get("/slug/{tag_slug}", response_model=RecipeTagResponse)

View File

@@ -28,7 +28,7 @@ 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
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.recipe.recipe_data_service import RecipeDataService
from mealie.services.recipe.recipe_service import RecipeService
@@ -162,6 +162,9 @@ class RecipeController(BaseRecipeController):
name=new_recipe.name,
url=urls.recipe_url(new_recipe.slug, self.deps.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
@@ -227,11 +230,27 @@ class RecipeController(BaseRecipeController):
def create_one(self, data: CreateRecipe) -> str | None:
"""Takes in a JSON string and loads data into the database as a new entry"""
try:
return self.service.create_one(data).slug
new_recipe = self.service.create_one(data)
except Exception as e:
self.handle_exceptions(e)
return None
if new_recipe:
self.event_bus.dispatch(
self.deps.acting_user.group_id,
EventTypes.recipe_created,
msg=self.t(
"notifications.generic-created-with-url",
name=new_recipe.name,
url=urls.recipe_url(new_recipe.slug, self.deps.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
@router.put("/{slug}")
def update_one(self, slug: str, data: Recipe):
"""Updates a recipe by existing slug and data."""
@@ -249,6 +268,7 @@ class RecipeController(BaseRecipeController):
name=data.name,
url=urls.recipe_url(data.slug, self.deps.settings.BASE_URL),
),
event_source=EventSource(event_type="update", item_type="recipe", item_id=data.id, slug=data.slug),
)
return data
@@ -260,6 +280,19 @@ class RecipeController(BaseRecipeController):
data = self.service.patch_one(slug, data)
except Exception as e:
self.handle_exceptions(e)
if data:
self.event_bus.dispatch(
self.deps.acting_user.group_id,
EventTypes.recipe_updated,
msg=self.t(
"notifications.generic-updated-with-url",
name=data.name,
url=urls.recipe_url(data.slug, self.deps.settings.BASE_URL),
),
event_source=EventSource(event_type="update", item_type="recipe", item_id=data.id, slug=data.slug),
)
return data
@router.delete("/{slug}")
@@ -275,6 +308,7 @@ class RecipeController(BaseRecipeController):
self.deps.acting_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),
)
return data