mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-12-30 05:47:09 -05:00
Feature/group based notifications (#918)
* fix group page * setup group notification for backend * update type generators * script to auto-generate schema exports * setup frontend CRUD interface * remove old notifications UI * drop old events api * add test functionality * update naming for fields * add event dispatcher functionality * bump to python 3.10 * bump python version * purge old event code * use-async apprise * set mealie logo as image * unify styles for buttons rows * add links to banners
This commit is contained in:
@@ -9,8 +9,7 @@ from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.core.config import get_app_dirs
|
||||
from mealie.repos.all_repositories import get_repositories
|
||||
from mealie.schema.admin import CommentImport, GroupImport, NotificationImport, RecipeImport, UserImport
|
||||
from mealie.schema.events import EventNotificationIn
|
||||
from mealie.schema.admin import CommentImport, GroupImport, RecipeImport, UserImport
|
||||
from mealie.schema.recipe import Recipe, RecipeCommentOut
|
||||
from mealie.schema.user import PrivateUser, UpdateGroup
|
||||
from mealie.services.image import minify
|
||||
@@ -159,24 +158,6 @@ class ImportDatabase:
|
||||
|
||||
minify.migrate_images()
|
||||
|
||||
def import_notifications(self):
|
||||
notify_file = self.import_dir.joinpath("notifications", "notifications.json")
|
||||
notifications = ImportDatabase.read_models_file(notify_file, EventNotificationIn)
|
||||
import_notifications = []
|
||||
|
||||
for notify in notifications:
|
||||
import_status = self.import_model(
|
||||
db_table=self.db.event_notifications,
|
||||
model=notify,
|
||||
return_model=NotificationImport,
|
||||
name_attr="name",
|
||||
search_key="notification_url",
|
||||
)
|
||||
|
||||
import_notifications.append(import_status)
|
||||
|
||||
return import_notifications
|
||||
|
||||
def import_settings(self):
|
||||
return []
|
||||
|
||||
@@ -330,11 +311,6 @@ def import_database(
|
||||
user_report = import_session.import_users()
|
||||
|
||||
notification_report = []
|
||||
if import_notifications:
|
||||
notification_report = import_session.import_notifications()
|
||||
|
||||
# if import_recipes:
|
||||
# import_session.import_comments()
|
||||
|
||||
import_session.clean_up()
|
||||
|
||||
|
||||
0
mealie/services/event_bus_service/__init__.py
Normal file
0
mealie/services/event_bus_service/__init__.py
Normal file
46
mealie/services/event_bus_service/event_bus_service.py
Normal file
46
mealie/services/event_bus_service/event_bus_service.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from fastapi import BackgroundTasks, Depends
|
||||
from pydantic import UUID4
|
||||
|
||||
from mealie.db.db_setup import generate_session
|
||||
from mealie.repos.repository_factory import AllRepositories
|
||||
from mealie.schema.group.group_events import GroupEventNotifierPrivate
|
||||
|
||||
from .message_types import EventBusMessage, EventTypes
|
||||
from .publisher import ApprisePublisher, PublisherLike
|
||||
|
||||
|
||||
class EventBusService:
|
||||
def __init__(self, bg: BackgroundTasks, session=Depends(generate_session)) -> None:
|
||||
self.bg = bg
|
||||
self._publisher = ApprisePublisher
|
||||
self.session = session
|
||||
self.group_id = None
|
||||
|
||||
@property
|
||||
def publisher(self) -> PublisherLike:
|
||||
return self._publisher()
|
||||
|
||||
def get_urls(self, event_type: EventTypes) -> list[str]:
|
||||
repos = AllRepositories(self.session)
|
||||
|
||||
notifiers: list[GroupEventNotifierPrivate] = repos.group_event_notifier.by_group(self.group_id).multi_query(
|
||||
{"enabled": True}, override_schema=GroupEventNotifierPrivate
|
||||
)
|
||||
|
||||
return [notifier.apprise_url for notifier in notifiers if getattr(notifier.options, event_type.name)]
|
||||
|
||||
def dispatch(self, group_id: UUID4, event_type: EventTypes, msg: str = "") -> None:
|
||||
self.group_id = group_id
|
||||
|
||||
def _dispatch():
|
||||
if urls := self.get_urls(event_type):
|
||||
self.publisher.publish(EventBusMessage.from_type(event_type, body=msg), urls)
|
||||
|
||||
self.bg.add_task(_dispatch)
|
||||
|
||||
def test_publisher(self, url: str) -> None:
|
||||
self.bg.add_task(
|
||||
self.publisher.publish,
|
||||
event=EventBusMessage.from_type(EventTypes.test_message, body="This is a test event."),
|
||||
notification_urls=[url],
|
||||
)
|
||||
47
mealie/services/event_bus_service/message_types.py
Normal file
47
mealie/services/event_bus_service/message_types.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from enum import Enum, auto
|
||||
|
||||
|
||||
class EventTypes(Enum):
|
||||
test_message = auto()
|
||||
|
||||
recipe_created = auto()
|
||||
recipe_updated = auto()
|
||||
recipe_deleted = auto()
|
||||
|
||||
user_signup = auto()
|
||||
|
||||
data_migrations = auto()
|
||||
data_export = auto()
|
||||
data_import = auto()
|
||||
|
||||
mealplan_entry_created = auto()
|
||||
|
||||
shopping_list_created = auto()
|
||||
shopping_list_updated = auto()
|
||||
shopping_list_deleted = auto()
|
||||
|
||||
cookbook_created = auto()
|
||||
cookbook_updated = auto()
|
||||
cookbook_deleted = auto()
|
||||
|
||||
tag_created = auto()
|
||||
tag_updated = auto()
|
||||
tag_deleted = auto()
|
||||
|
||||
category_created = auto()
|
||||
category_updated = auto()
|
||||
category_deleted = auto()
|
||||
|
||||
|
||||
class EventBusMessage:
|
||||
title: str
|
||||
body: str = ""
|
||||
|
||||
def __init__(self, title, body) -> None:
|
||||
self.title = title
|
||||
self.body = body
|
||||
|
||||
@classmethod
|
||||
def from_type(cls, event_type: EventTypes, body: str = "") -> "EventBusMessage":
|
||||
title = event_type.name.replace("_", " ").title()
|
||||
return cls(title=title, body=body)
|
||||
29
mealie/services/event_bus_service/publisher.py
Normal file
29
mealie/services/event_bus_service/publisher.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from typing import Protocol
|
||||
|
||||
import apprise
|
||||
|
||||
from mealie.services.event_bus_service.event_bus_service import EventBusMessage
|
||||
|
||||
|
||||
class PublisherLike(Protocol):
|
||||
def publish(self, event: EventBusMessage, notification_urls: list[str]):
|
||||
...
|
||||
|
||||
|
||||
class ApprisePublisher:
|
||||
def __init__(self, hard_fail=False) -> None:
|
||||
asset = apprise.AppriseAsset(
|
||||
async_mode=True,
|
||||
image_url_mask="https://raw.githubusercontent.com/hay-kot/mealie/dev/frontend/public/img/icons/android-chrome-maskable-512x512.png",
|
||||
)
|
||||
self.apprise = apprise.Apprise(asset=asset)
|
||||
self.hard_fail = hard_fail
|
||||
|
||||
def publish(self, event: EventBusMessage, notification_urls: list[str]):
|
||||
for dest in notification_urls:
|
||||
status = self.apprise.add(dest)
|
||||
|
||||
if not status and self.hard_fail:
|
||||
raise Exception("Apprise URL Add Failed")
|
||||
|
||||
self.apprise.notify(title=event.title, body=event.body)
|
||||
@@ -1,4 +1,3 @@
|
||||
import apprise
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.db.db_setup import create_session
|
||||
@@ -6,44 +5,12 @@ from mealie.repos.all_repositories import get_repositories
|
||||
from mealie.schema.events import Event, EventCategory
|
||||
|
||||
|
||||
def test_notification(notification_url, event=None) -> bool:
|
||||
if event is None:
|
||||
event = Event(
|
||||
title="Test Notification",
|
||||
text="This is a test message from the Mealie API server",
|
||||
category=EventCategory.general.value,
|
||||
)
|
||||
|
||||
post_notifications(event, [notification_url], hard_fail=True)
|
||||
|
||||
|
||||
def post_notifications(event: Event, notification_urls=list[str], hard_fail=False, attachment=None):
|
||||
asset = apprise.AppriseAsset(async_mode=False)
|
||||
apobj = apprise.Apprise(asset=asset)
|
||||
|
||||
for dest in notification_urls:
|
||||
status = apobj.add(dest)
|
||||
|
||||
if not status and hard_fail:
|
||||
raise Exception("Apprise URL Add Failed")
|
||||
|
||||
apobj.notify(
|
||||
body=event.text,
|
||||
title=event.title,
|
||||
attach=str(attachment),
|
||||
)
|
||||
|
||||
|
||||
def save_event(title, text, category, session: Session, attachment=None):
|
||||
def save_event(title, text, category, session: Session):
|
||||
event = Event(title=title, text=text, category=category)
|
||||
session = session or create_session()
|
||||
db = get_repositories(session)
|
||||
db.events.create(event.dict())
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def create_general_event(title, text, session=None):
|
||||
category = EventCategory.general
|
||||
@@ -52,8 +19,7 @@ def create_general_event(title, text, session=None):
|
||||
|
||||
def create_recipe_event(title, text, session=None, attachment=None):
|
||||
category = EventCategory.recipe
|
||||
|
||||
save_event(title=title, text=text, category=category, session=session, attachment=attachment)
|
||||
save_event(title=title, text=text, category=category, session=session)
|
||||
|
||||
|
||||
def create_backup_event(title, text, session=None):
|
||||
|
||||
Reference in New Issue
Block a user