diff --git a/backend/features/reminder_handler.py b/backend/features/reminder_handler.py index e326ad7..26656df 100644 --- a/backend/features/reminder_handler.py +++ b/backend/features/reminder_handler.py @@ -6,10 +6,9 @@ from datetime import datetime from typing import TYPE_CHECKING, Union from backend.base.definitions import Constants, RepeatQuantity, SendResult -from backend.base.helpers import (Singleton, find_next_time, - send_apprise_notification, when_not_none) +from backend.base.helpers import Singleton, find_next_time, when_not_none from backend.base.logging import LOGGER -from backend.implementations.notification_services import NotificationService +from backend.implementations.reminders import Reminder from backend.internals.db_models import UserlessRemindersDB from backend.internals.server import Server @@ -40,14 +39,7 @@ class ReminderHandler(metaclass=Singleton): user_id = self.reminder_db.reminder_id_to_user_id( reminder.id ) - result = send_apprise_notification( - [ - NotificationService(user_id, ns).get().url - for ns in reminder.notification_services - ], - reminder.title, - reminder.text - ) + result = Reminder(user_id, reminder.id).trigger_reminder() self.thread = None self.time = None diff --git a/backend/implementations/reminders.py b/backend/implementations/reminders.py index a8c2466..a8af777 100644 --- a/backend/implementations/reminders.py +++ b/backend/implementations/reminders.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -from dataclasses import asdict from datetime import datetime from typing import List, Union @@ -12,16 +11,13 @@ from backend.base.definitions import (WEEKDAY_NUMBER, ReminderData, from backend.base.helpers import (find_next_time, search_filter, send_apprise_notification, when_not_none) from backend.base.logging import LOGGER -from backend.features.reminder_handler import ReminderHandler from backend.implementations.notification_services import NotificationService from backend.internals.db_models import RemindersDB -REMINDER_HANDLER = ReminderHandler() - class Reminder: def __init__(self, user_id: int, reminder_id: int) -> None: - """Represent a reminder. + """Create an instance. Args: user_id (int): The ID of the user. @@ -29,11 +25,10 @@ class Reminder: Raises: ReminderNotFound: Reminder with given ID does not exist or is not - owned by user. + owned by user. """ self.user_id = user_id self.id = reminder_id - self.reminder_db = RemindersDB(self.user_id) if not self.reminder_db.exists(self.id): @@ -48,72 +43,96 @@ class Reminder: """ return self.reminder_db.fetch(self.id)[0] + def trigger_reminder(self) -> SendResult: + """Send the reminder. + + Returns: + SendResult: The result of the sending process. + """ + LOGGER.info(f'Triggering reminder {self.id}') + + reminder_data = self.get() + + urls = [ + NotificationService(self.user_id, ns_id).get().url + for ns_id in reminder_data.notification_services + ] + + return send_apprise_notification( + urls, + reminder_data.title, + reminder_data.text + ) + def update( self, - title: Union[None, str] = None, - time: Union[None, int] = None, - notification_services: Union[None, List[int]] = None, - text: Union[None, str] = None, - repeat_quantity: Union[None, RepeatQuantity] = None, - repeat_interval: Union[None, int] = None, - weekdays: Union[None, List[WEEKDAY_NUMBER]] = None, - cron_schedule: Union[None, str] = None, - color: Union[None, str] = None, - enabled: Union[None, bool] = None + title: Union[str, None] = None, + time: Union[int, None] = None, + notification_services: Union[List[int], None] = None, + text: Union[str, None] = None, + repeat_quantity: Union[RepeatQuantity, None] = None, + repeat_interval: Union[int, None] = None, + weekdays: Union[List[WEEKDAY_NUMBER], None] = None, + cron_schedule: Union[str, None] = None, + color: Union[str, None] = None, + enabled: Union[bool, None] = None ) -> ReminderData: """Edit the reminder. Args: - title (Union[None, str]): The new title of the entry. + title (Union[str, None], optional): The new title of the reminder. Defaults to None. - time (Union[None, int]): The new UTC epoch timestamp when the - reminder should be send. + time (Union[int, None], optional): The new UTC epoch timestamp when + the reminder should be send. Defaults to None. - notification_services (Union[None, List[int]]): The new list - of id's of the notification services to use to send the reminder. + notification_services (Union[List[int], None], optional): The new + IDs of the notification services to use to send the reminder. Defaults to None. - text (Union[None, str], optional): The new body of the reminder. + text (Union[str, None], optional): The new body of the reminder. Defaults to None. - repeat_quantity (Union[None, RepeatQuantity], optional): The new - quantity of the repeat specified for the reminder. + repeat_quantity (Union[RepeatQuantity, None], optional): The new + quantity of the repeat_interval specified for the reminder. Defaults to None. - repeat_interval (Union[None, int], optional): The new amount of - repeat_quantity, like "5" (hours). + repeat_interval (Union[int, None], optional): The new interval of + repeat_quantity, like "5" (hours). Defaults to None. - weekdays (Union[None, List[WEEKDAY_NUMBER]], optional): The new - indexes of the days of the week that the reminder should run. + weekdays (Union[List[WEEKDAY_NUMBER], None], optional): The new + indexes of the days of the week that the reminder should run. Defaults to None. - cron_schedule (Union[None, str], optional): The new cron schedule - that the reminder should run on. + cron_schedule (Union[str, None], optional): The new cron schedule + that the reminder should run on. Defaults to None. - color (Union[None, str], optional): The new hex code of the color - of the reminder, which is shown in the web-ui. + color (Union[str, None], optional): The new hex code of the color + of the reminder, which is shown in the web-ui. Defaults to None. - enabled (Union[None, bool], optional): Whether the reminder should - be enabled. + enabled (Union[bool, None], optional): Whether the reminder should + be enabled. Defaults to None. Note about args: Either repeat_quantity and repeat_interval are given, weekdays is - given or neither, but not both. + given, cron_schedule is given, or none, but not a combination. Raises: - NotificationServiceNotFound: One of the notification services was not found. + NotificationServiceNotFound: One of the notification services was + not found. InvalidKeyValue: The value of one of the keys is not valid or - the "Note about args" is violated. + the "Note about args" is violated. Returns: ReminderData: The new reminder info. """ + from backend.features.reminder_handler import ReminderHandler + LOGGER.info( f'Updating notification service {self.id}: ' + f'{title=}, {time=}, {notification_services=}, {text=}, ' @@ -137,9 +156,9 @@ class Reminder: raise InvalidKeyValue('weekdays', weekdays) repeated_reminder = ( - (repeat_quantity is not None and repeat_interval is not None) - or weekdays is not None - or cron_schedule is not None + (repeat_quantity is not None and repeat_interval) + or weekdays + or cron_schedule ) if time is not None: @@ -149,12 +168,12 @@ class Reminder: time = round(time) if notification_services: - # Check if all notification services exist + # Check if all notification services exist. Creating an instance + # raises NotificationServiceNotFound if the ID is not valid. for ns in notification_services: NotificationService(self.user_id, ns) - # Get current data and update it with new values - data = asdict(self.get()) + data = self.get().todict() new_values = { 'title': title, @@ -225,14 +244,16 @@ class Reminder: data["enabled"] ) - REMINDER_HANDLER.find_next_reminder(next_time) + ReminderHandler().find_next_reminder(next_time) return self.get() def delete(self) -> None: "Delete the reminder" + from backend.features.reminder_handler import ReminderHandler + LOGGER.info(f'Deleting reminder {self.id}') self.reminder_db.delete(self.id) - REMINDER_HANDLER.find_next_reminder() + ReminderHandler().find_next_reminder() return @@ -247,18 +268,19 @@ class Reminders: self.reminder_db = RemindersDB(self.user_id) return - def fetchall( + def get_all( self, sort_by: SortingMethod = SortingMethod.TIME ) -> List[ReminderData]: - """Get all reminders. + """Get all reminders of the user. Args: - sort_by (SortingMethod, optional): How to sort the result. + sort_by (SortingMethod, optional): How to sort the + reminders. Defaults to SortingMethod.TIME. Returns: - List[ReminderData]: The info of each reminder. + List[ReminderData]: The info about all reminders of the user. """ reminders = self.reminder_db.fetch() reminders.sort(key=sort_by.value[0], reverse=sort_by.value[1]) @@ -273,34 +295,33 @@ class Reminders: Args: query (str): The term to search for. - sort_by (SortingMethod, optional): How to sort the result. + sort_by (SortingMethod, optional): How to sort the + reminders. Defaults to SortingMethod.TIME. Returns: - List[ReminderData]: All reminders that match. Similar output to - self.fetchall. + List[ReminderData]: The info about all reminders of the user that + match the search term. """ - reminders = [ + return [ r - for r in self.fetchall(sort_by) + for r in self.get_all(sort_by) if search_filter(query, r) ] - return reminders - def fetchone(self, id: int) -> Reminder: - """Get one reminder. + def get_one(self, reminder_id: int) -> Reminder: + """Get a reminder instance based on the ID. Args: - id (int): The ID of the reminder to fetch. + reminder_id (int): The ID of the reminder to fetch. Raises: - ReminderNotFound: The reminder with the given ID does not exist - or is not owned by the user. + ReminderNotFound: The user does not own a reminder with the given ID. Returns: - Reminder: A Reminder instance. + Reminder: Instance of Reminder. """ - return Reminder(self.user_id, id) + return Reminder(self.user_id, reminder_id) def add( self, @@ -308,66 +329,68 @@ class Reminders: time: int, notification_services: List[int], text: str = '', - repeat_quantity: Union[None, RepeatQuantity] = None, - repeat_interval: Union[None, int] = None, - weekdays: Union[None, List[WEEKDAY_NUMBER]] = None, - cron_schedule: Union[None, str] = None, - color: Union[None, str] = None, + repeat_quantity: Union[RepeatQuantity, None] = None, + repeat_interval: Union[int, None] = None, + weekdays: Union[List[WEEKDAY_NUMBER], None] = None, + cron_schedule: Union[str, None] = None, + color: Union[str, None] = None, enabled: bool = True ) -> Reminder: """Add a reminder. Args: - title (str): The title of the entry. + title (str): The title of the reminder. time (int): The UTC epoch timestamp the the reminder should be send. - notification_services (List[int]): The id's of the notification - services to use to send the reminder. + notification_services (List[int]): The IDs of the notification + services to use to send the reminder. text (str, optional): The body of the reminder. Defaults to ''. - repeat_quantity (Union[None, RepeatQuantity], optional): The quantity - of the repeat specified for the reminder. + repeat_quantity (Union[RepeatQuantity, None], optional): The + quantity of the repeat_interval specified for the reminder. Defaults to None. - repeat_interval (Union[None, int], optional): The amount of - repeat_quantity, like "5" (hours). + repeat_interval (Union[int, None], optional): The interval of + repeat_quantity, like "5" (hours). Defaults to None. - weekdays (Union[None, List[WEEKDAY_NUMBER]], optional): The indexes - of the days of the week that the reminder should run. + weekdays (Union[List[WEEKDAY_NUMBER], None], optional): The indexes + of the days of the week that the reminder should run. Defaults to None. - cron_schedule (Union[None, str], optional): The cron schedule that - the reminder should run on. + cron_schedule (Union[str, None], optional): The cron schedule that + the reminder should run on. Defaults to None. - color (Union[None, str], optional): The hex code of the color of the - reminder, which is shown in the web-ui. + color (Union[str, None], optional): The hex code of the color of the + reminder, which is shown in the web-ui. Defaults to None. - enabled (Union[None, bool], optional): Whether the reminder should - be enabled. + enabled (Union[bool, None], optional): Whether the reminder should + be enabled. Defaults to None. Note about args: - Either repeat_quantity and repeat_interval are given, - weekdays is given, cron_schedule is given or none. + Either repeat_quantity and repeat_interval are given, weekdays is + given, cron_schedule is given, or none, but not a combination. Raises: NotificationServiceNotFound: One of the notification services was - not found. - InvalidKeyValue: The value of one of the keys is not valid - or the "Note about args" is violated. + not found. + InvalidKeyValue: The value of one of the keys is not valid or + the "Note about args" is violated. Returns: Reminder: The info about the reminder. """ + from backend.features.reminder_handler import ReminderHandler + LOGGER.info( - f'Adding reminder with {title=}, {time=}, {notification_services=}, ' + - f'{text=}, {repeat_quantity=}, {repeat_interval=}, {weekdays=}, ' + + f'Adding reminder with {title=}, {time=}, {notification_services=}, ' + f'{text=}, {repeat_quantity=}, {repeat_interval=}, {weekdays=}, ' f'{cron_schedule=}, {color=}, {enabled=}') # Validate data @@ -392,7 +415,8 @@ class Reminders: ): raise InvalidKeyValue('weekdays', weekdays) - # Check if all notification services exist + # Check if all notification services exist. Creating an instance + # raises NotificationServiceNotFound if the ID is not valid. for ns in notification_services: NotificationService(self.user_id, ns) @@ -419,8 +443,10 @@ class Reminders: ) new_id = self.reminder_db.add( - title, text, - time, repeat_quantity_str, + title, + text, + time, + repeat_quantity_str, repeat_interval, weekdays_str, cron_schedule, @@ -430,9 +456,9 @@ class Reminders: enabled ) - REMINDER_HANDLER.find_next_reminder(time) + ReminderHandler().find_next_reminder(time) - return self.fetchone(new_id) + return self.get_one(new_id) def test_reminder( self, @@ -440,29 +466,31 @@ class Reminders: notification_services: List[int], text: str = '' ) -> SendResult: - """Test send a reminder draft. + """Test a reminder by sending it. Args: - title (str): Title title of the entry. + title (str): The title of the reminder. - notification_service (int): The id of the notification service to - use to send the reminder. + notification_service (int): The IDs of the notification + services to use to send the reminder. text (str, optional): The body of the reminder. Defaults to ''. Returns: - SendResult: Whether or not it was successful. + SendResult: The result of the test. """ LOGGER.info( f'Testing reminder with {title=}, {notification_services=}, {text=}' ) + urls = [ + NotificationService(self.user_id, ns_id).get().url + for ns_id in notification_services + ] + return send_apprise_notification( - [ - NotificationService(self.user_id, ns_id).get().url - for ns_id in notification_services - ], + urls, title, text ) diff --git a/backend/implementations/static_reminders.py b/backend/implementations/static_reminders.py index 72841d1..06fdb07 100644 --- a/backend/implementations/static_reminders.py +++ b/backend/implementations/static_reminders.py @@ -177,7 +177,7 @@ class StaticReminders: Defaults to TimelessSortingMethod.TITLE. Returns: - List[StaticReminderData]: the info about all static reminders of + List[StaticReminderData]: The info about all static reminders of the user that match the search term. """ return [ @@ -211,7 +211,7 @@ class StaticReminders: """Add a static reminder. Args: - title (str): The title of the entry. + title (str): The title of the static reminder. notification_services (List[int]): The IDs of the notification services to use to send the static reminder. diff --git a/frontend/api.py b/frontend/api.py index c730f1d..d365595 100644 --- a/frontend/api.py +++ b/frontend/api.py @@ -276,7 +276,7 @@ def api_reminders_list(inputs: Dict[str, Any]): reminders = Reminders(api_key_map[g.hashed_api_key].user_data.user_id) if request.method == 'GET': - result = reminders.fetchall(inputs['sort_by']) + result = reminders.get_all(inputs['sort_by']) return return_api([r.todict() for r in result]) elif request.method == 'POST': @@ -324,11 +324,11 @@ def api_get_reminder(inputs: Dict[str, Any], r_id: int): ) if request.method == 'GET': - result = reminders.fetchone(r_id).get() + result = reminders.get_one(r_id).get() return return_api(result.todict()) elif request.method == 'PUT': - result = reminders.fetchone(r_id).update( + result = reminders.get_one(r_id).update( title=inputs['title'], time=inputs['time'], notification_services=inputs['notification_services'], @@ -343,7 +343,7 @@ def api_get_reminder(inputs: Dict[str, Any], r_id: int): return return_api(result.todict()) elif request.method == 'DELETE': - reminders.fetchone(r_id).delete() + reminders.get_one(r_id).delete() return return_api({})