diff --git a/api_docs/changelog.md b/api_docs/changelog.md index c3d953b460..0f8044608d 100644 --- a/api_docs/changelog.md +++ b/api_docs/changelog.md @@ -20,6 +20,17 @@ format used by the Zulip server that they are interacting with. ## Changes in Zulip 11.0 +**Feature level 399** + +* [`GET /events`](/api/get-events): + Added `reminders` events sent to clients when a user creates + or deletes scheduled messages. +* [`GET /reminders`](/api/get-reminders): + Clients can now request `/reminders` endpoint to fetch all + scheduled reminders. +* [`DELETE /reminders/{reminder_id}`](/api/delete-reminder): + Clients can now delete a scheduled reminder. + **Feature level 398** * [`POST /register`](/api/register-queue), [`PATCH /settings`](/api/update-settings), diff --git a/api_docs/include/rest-endpoints.md b/api_docs/include/rest-endpoints.md index c4d2d9d8ac..ce1fcd11a5 100644 --- a/api_docs/include/rest-endpoints.md +++ b/api_docs/include/rest-endpoints.md @@ -30,6 +30,8 @@ #### Message reminders * [Create a message reminder](/api/create-message-reminder) +* [Get reminders](/api/get-reminders) +* [Delete a reminder](/api/delete-reminder) #### Drafts diff --git a/version.py b/version.py index acd83c20da..4f5a725f94 100644 --- a/version.py +++ b/version.py @@ -34,7 +34,7 @@ DESKTOP_WARNING_VERSION = "5.9.3" # new level means in api_docs/changelog.md, as well as "**Changes**" # entries in the endpoint's documentation in `zulip.yaml`. -API_FEATURE_LEVEL = 398 +API_FEATURE_LEVEL = 399 # Bump the minor PROVISION_VERSION to indicate that folks should provision # only when going from an old version of the code to a newer version. Bump diff --git a/zerver/actions/reminders.py b/zerver/actions/reminders.py index 472ebfd587..d0b1b91310 100644 --- a/zerver/actions/reminders.py +++ b/zerver/actions/reminders.py @@ -1,11 +1,14 @@ import datetime +from django.db import transaction + from zerver.actions.message_send import check_message from zerver.actions.scheduled_messages import do_schedule_messages from zerver.lib.addressee import Addressee from zerver.lib.message import access_message from zerver.lib.reminders import get_reminder_formatted_content from zerver.models import Client, ScheduledMessage, UserProfile +from zerver.tornado.django_api import send_event_on_commit def schedule_reminder_for_message( @@ -34,6 +37,22 @@ def schedule_reminder_for_message( [send_request], current_user, read_by_sender=False, - skip_events=True, delivery_type=ScheduledMessage.REMIND, )[0] + + +def notify_remove_reminder(user_profile: UserProfile, reminder_id: int) -> None: + event = { + "type": "reminders", + "op": "remove", + "reminder_id": reminder_id, + } + send_event_on_commit(user_profile.realm, event, [user_profile.id]) + + +@transaction.atomic(durable=True) +def do_delete_reminder(user_profile: UserProfile, reminder: ScheduledMessage) -> None: + assert reminder.delivery_type == ScheduledMessage.REMIND + reminder_id = reminder.id + reminder.delete() + notify_remove_reminder(user_profile, reminder_id) diff --git a/zerver/actions/scheduled_messages.py b/zerver/actions/scheduled_messages.py index af2b48f63e..5ab928490e 100644 --- a/zerver/actions/scheduled_messages.py +++ b/zerver/actions/scheduled_messages.py @@ -90,6 +90,15 @@ def notify_new_scheduled_message( send_event_on_commit(user_profile.realm, event, [user_profile.id]) +def notify_new_reminder(user_profile: UserProfile, reminders: list[ScheduledMessage]) -> None: + event = { + "type": "reminders", + "op": "add", + "reminders": [reminder.to_reminder_dict() for reminder in reminders], + } + send_event_on_commit(user_profile.realm, event, [user_profile.id]) + + def do_schedule_messages( send_message_requests: Sequence[SendMessageRequest], sender: UserProfile, @@ -137,7 +146,10 @@ def do_schedule_messages( scheduled_message.save(update_fields=["has_attachment"]) if not skip_events: - notify_new_scheduled_message(sender, scheduled_message_objects) + if delivery_type == ScheduledMessage.REMIND: + notify_new_reminder(sender, scheduled_message_objects) + else: + notify_new_scheduled_message(sender, scheduled_message_objects) return [scheduled_message.id for scheduled_message, ignored in scheduled_messages] @@ -286,7 +298,6 @@ def delete_scheduled_message(user_profile: UserProfile, scheduled_message_id: in scheduled_message_object = access_scheduled_message(user_profile, scheduled_message_id) scheduled_message_id = scheduled_message_object.id scheduled_message_object.delete() - notify_remove_scheduled_message(user_profile, scheduled_message_id) diff --git a/zerver/lib/event_schema.py b/zerver/lib/event_schema.py index 690bb03932..af3ae63d29 100644 --- a/zerver/lib/event_schema.py +++ b/zerver/lib/event_schema.py @@ -65,6 +65,8 @@ from zerver.lib.event_types import ( EventRealmUserRemove, EventRealmUserSettingsDefaultsUpdate, EventRealmUserUpdate, + EventRemindersAdd, + EventRemindersRemove, EventRestart, EventSavedSnippetsAdd, EventSavedSnippetsRemove, @@ -197,6 +199,8 @@ check_realm_linkifiers = make_checker(EventRealmLinkifiers) check_realm_playgrounds = make_checker(EventRealmPlaygrounds) check_realm_user_add = make_checker(EventRealmUserAdd) check_realm_user_remove = make_checker(EventRealmUserRemove) +check_reminder_add = make_checker(EventRemindersAdd) +check_reminder_remove = make_checker(EventRemindersRemove) check_restart = make_checker(EventRestart) check_saved_snippets_add = make_checker(EventSavedSnippetsAdd) check_saved_snippets_remove = make_checker(EventSavedSnippetsRemove) diff --git a/zerver/lib/event_types.py b/zerver/lib/event_types.py index b1807f90b7..b2f04a3601 100644 --- a/zerver/lib/event_types.py +++ b/zerver/lib/event_types.py @@ -821,6 +821,29 @@ class EventScheduledMessagesUpdate(BaseEvent): scheduled_message: ScheduledMessageFields +class ReminderFields(BaseModel): + reminder_id: int + type: Literal["private"] + to: list[int] + content: str + rendered_content: str + scheduled_delivery_timestamp: int + failed: bool + reminder_target_message_id: int + + +class EventRemindersAdd(BaseEvent): + type: Literal["reminders"] + op: Literal["add"] + reminders: list[ReminderFields] + + +class EventRemindersRemove(BaseEvent): + type: Literal["reminders"] + op: Literal["remove"] + reminder_id: int + + class BasicStreamFields(BaseModel): is_archived: bool can_administer_channel_group: int | UserGroupMembersDict diff --git a/zerver/lib/events.py b/zerver/lib/events.py index e771c4164c..db8a24b753 100644 --- a/zerver/lib/events.py +++ b/zerver/lib/events.py @@ -51,7 +51,10 @@ from zerver.lib.onboarding_steps import get_next_onboarding_steps from zerver.lib.presence import get_presence_for_user, get_presences_for_realm from zerver.lib.realm_icon import realm_icon_url from zerver.lib.realm_logo import get_realm_logo_source, get_realm_logo_url -from zerver.lib.scheduled_messages import get_undelivered_scheduled_messages +from zerver.lib.scheduled_messages import ( + get_undelivered_reminders, + get_undelivered_scheduled_messages, +) from zerver.lib.soft_deactivation import reactivate_user_if_soft_deactivated from zerver.lib.sounds import get_available_notification_sounds from zerver.lib.stream_subscription import handle_stream_notifications_compatibility @@ -328,6 +331,9 @@ def fetch_initial_state_data( [] if user_profile is None else get_undelivered_scheduled_messages(user_profile) ) + if want("reminders"): + state["reminders"] = [] if user_profile is None else get_undelivered_reminders(user_profile) + if want("muted_topics") and ( # Suppress muted_topics data for clients that explicitly # support user_topic. This allows clients to request both the diff --git a/zerver/lib/reminders.py b/zerver/lib/reminders.py index 7d982fc6c6..18b57cbdf1 100644 --- a/zerver/lib/reminders.py +++ b/zerver/lib/reminders.py @@ -1,6 +1,7 @@ from django.conf import settings from django.utils.translation import gettext as _ +from zerver.lib.exceptions import ResourceNotFoundError from zerver.lib.markdown.fenced_code import get_unused_fence from zerver.lib.mention import silent_mention_syntax_for_user from zerver.lib.message import truncate_content @@ -8,6 +9,7 @@ from zerver.lib.message_cache import MessageDict from zerver.lib.topic_link_util import get_message_link_syntax from zerver.lib.url_encoding import message_link_url from zerver.models import Message, Stream, UserProfile +from zerver.models.scheduled_jobs import ScheduledMessage def get_reminder_formatted_content(message: Message, current_user: UserProfile) -> str: @@ -46,3 +48,12 @@ def get_reminder_formatted_content(message: Message, current_user: UserProfile) fence=fence, msg_content=msg_content, ) + + +def access_reminder(user_profile: UserProfile, reminder_id: int) -> ScheduledMessage: + try: + return ScheduledMessage.objects.get( + id=reminder_id, sender=user_profile, delivery_type=ScheduledMessage.REMIND + ) + except ScheduledMessage.DoesNotExist: + raise ResourceNotFoundError(_("Reminder does not exist")) diff --git a/zerver/lib/scheduled_messages.py b/zerver/lib/scheduled_messages.py index 83caed11fa..b7fdada9b7 100644 --- a/zerver/lib/scheduled_messages.py +++ b/zerver/lib/scheduled_messages.py @@ -3,6 +3,7 @@ from django.utils.translation import gettext as _ from zerver.lib.exceptions import ResourceNotFoundError from zerver.models import ScheduledMessage, UserProfile from zerver.models.scheduled_jobs import ( + APIReminderDirectMessageDict, APIScheduledDirectMessageDict, APIScheduledStreamMessageDict, ) @@ -12,7 +13,9 @@ def access_scheduled_message( user_profile: UserProfile, scheduled_message_id: int ) -> ScheduledMessage: try: - return ScheduledMessage.objects.get(id=scheduled_message_id, sender=user_profile) + return ScheduledMessage.objects.get( + id=scheduled_message_id, sender=user_profile, delivery_type=ScheduledMessage.SEND_LATER + ) except ScheduledMessage.DoesNotExist: raise ResourceNotFoundError(_("Scheduled message does not exist")) @@ -32,3 +35,20 @@ def get_undelivered_scheduled_messages( scheduled_message.to_dict() for scheduled_message in scheduled_messages ] return scheduled_message_dicts + + +def get_undelivered_reminders( + user_profile: UserProfile, +) -> list[APIReminderDirectMessageDict]: + reminders = ScheduledMessage.objects.filter( + realm_id=user_profile.realm_id, + sender=user_profile, + # Notably, we don't require failed=False, since we will want + # to display those to users. + delivered=False, + delivery_type=ScheduledMessage.REMIND, + ).order_by("scheduled_timestamp") + reminder_dicts: list[APIReminderDirectMessageDict] = [ + reminder.to_reminder_dict() for reminder in reminders + ] + return reminder_dicts diff --git a/zerver/models/scheduled_jobs.py b/zerver/models/scheduled_jobs.py index 4d39859a75..e91cdd47ab 100644 --- a/zerver/models/scheduled_jobs.py +++ b/zerver/models/scheduled_jobs.py @@ -138,6 +138,17 @@ class APIScheduledDirectMessageDict(TypedDict): failed: bool +class APIReminderDirectMessageDict(TypedDict): + reminder_id: int + to: list[int] + type: str + content: str + rendered_content: str + scheduled_delivery_timestamp: int + failed: bool + reminder_target_message_id: int + + class ScheduledMessage(models.Model): sender = models.ForeignKey(UserProfile, on_delete=CASCADE) recipient = models.ForeignKey(Recipient, on_delete=CASCADE) @@ -247,6 +258,21 @@ class ScheduledMessage(models.Model): failed=self.failed, ) + def to_reminder_dict(self) -> APIReminderDirectMessageDict: + assert self.reminder_target_message_id is not None + recipient, recipient_type_str = get_recipient_ids(self.recipient, self.sender.id) + assert recipient_type_str == "private" + return APIReminderDirectMessageDict( + reminder_id=self.id, + to=recipient, + type=recipient_type_str, + content=self.content, + rendered_content=self.rendered_content, + scheduled_delivery_timestamp=datetime_to_timestamp(self.scheduled_timestamp), + failed=self.failed, + reminder_target_message_id=self.reminder_target_message_id, + ) + EMAIL_TYPES = { "account_registered": ScheduledEmail.WELCOME, diff --git a/zerver/openapi/test_curl_examples.py b/zerver/openapi/test_curl_examples.py index 8dc8623152..d981c5f58e 100644 --- a/zerver/openapi/test_curl_examples.py +++ b/zerver/openapi/test_curl_examples.py @@ -28,6 +28,9 @@ UNTESTED_GENERATED_CURL_EXAMPLES = { # Would need push notification bouncer set up to test the # generated curl example for this endpoint. "test-notify", + # Having a message for a specific user available to test this endpoint + # is tricky for testing. + "delete-reminder", } diff --git a/zerver/openapi/zulip.yaml b/zerver/openapi/zulip.yaml index 0f31fe2f88..95800406fe 100644 --- a/zerver/openapi/zulip.yaml +++ b/zerver/openapi/zulip.yaml @@ -5765,6 +5765,78 @@ paths: "op": "remove", "saved_snippet_id": 17, } + - type: object + additionalProperties: false + description: | + Event sent to a user's clients when a reminder is scheduled. + + **Changes**: New in Zulip 11.0 (feature level 399). + properties: + id: + $ref: "#/components/schemas/EventIdSchema" + type: + allOf: + - $ref: "#/components/schemas/EventTypeSchema" + - enum: + - reminders + op: + type: string + enum: + - add + reminders: + type: array + description: | + An array of objects containing details of the newly created + reminders. + items: + $ref: "#/components/schemas/Reminder" + example: + { + "type": "reminders", + "op": "add", + "reminders": + [ + { + "reminder_id": 17, + "type": "private", + "to": [6], + "content": "Hello there!", + "rendered_content": "

Hello there!

", + "scheduled_delivery_timestamp": 1681662420, + "failed": false, + "reminder_target_message_id": 42, + }, + ], + } + - type: object + additionalProperties: false + description: | + Event sent to a user's clients when a reminder + is deleted. + + **Changes**: New in Zulip 11.0 (feature level 399). + properties: + id: + $ref: "#/components/schemas/EventIdSchema" + type: + allOf: + - $ref: "#/components/schemas/EventTypeSchema" + - enum: + - reminders + op: + type: string + enum: + - remove + reminder_id: + type: integer + description: | + The ID of the reminder that was deleted. + example: + { + "type": "reminders", + "op": "remove", + "reminder_id": 17, + } - type: object additionalProperties: false description: | @@ -7020,6 +7092,56 @@ paths: "msg": "Saved snippet does not exist.", } /reminders: + get: + operationId: get-reminders + tags: ["reminders"] + summary: Get reminders + description: | + Fetch all [reminders](/help/schedule-a-reminder) for the + current user. + + Reminders are messages the user has scheduled to be sent in the + future to themself. + + **Changes**: New in Zulip 11.0 (feature level 399). + responses: + "200": + description: Success. + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/JsonSuccessBase" + - additionalProperties: false + properties: + result: {} + msg: {} + ignored_parameters_unsupported: {} + reminders: + type: array + description: | + Returns all of the current user's undelivered reminders, + ordered by `scheduled_delivery_timestamp` (ascending). + items: + $ref: "#/components/schemas/Reminder" + example: + { + "result": "success", + "msg": "", + "reminders": + [ + { + "reminder_id": 27, + "to": [6], + "type": "private", + "content": "Hi", + "rendered_content": "

Hi

", + "scheduled_delivery_timestamp": 1681662420, + "failed": false, + "reminder_target_message_id": 42, + }, + ], + } post: operationId: create-message-reminder tags: ["reminders"] @@ -7064,6 +7186,48 @@ paths: description: | Unique ID of the scheduled message reminder. example: {"msg": "", "reminder_id": 42, "result": "success"} + + /reminders/{reminder_id}: + delete: + operationId: delete-reminder + tags: ["reminders"] + summary: Delete a reminder + description: | + Delete, and therefore cancel sending, a previously [scheduled + reminder](/help/schedule-a-reminder). + + **Changes**: New in Zulip 11.0 (feature level 399). + parameters: + - name: reminder_id + in: path + schema: + type: integer + description: | + The ID of the reminder to delete. + + This is different from the unique ID that the message would have + after being sent. + required: true + example: 1 + responses: + "200": + $ref: "#/components/responses/SimpleSuccess" + "404": + description: Not Found. + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/CodedError" + - description: | + A typical failed JSON response for when no reminder exists + with the provided ID: + example: + { + "code": "BAD_REQUEST", + "result": "error", + "msg": "Reminder does not exist", + } /scheduled_messages: get: operationId: get-scheduled-messages @@ -15875,6 +16039,17 @@ paths: **Changes**: New in Zulip 7.0 (feature level 179). items: $ref: "#/components/schemas/ScheduledMessage" + reminders: + type: array + description: | + Present if `reminders` is present in `fetch_event_types`. + + An array of all undelivered reminders scheduled by the user. + + **Changes**: New in Zulip 11.0 (feature level 399). + items: + $ref: "#/components/schemas/ScheduledMessage" + muted_topics: type: array deprecated: true @@ -26272,6 +26447,71 @@ components: rendered_content: {} scheduled_delivery_timestamp: {} failed: {} + Reminder: + type: object + additionalProperties: false + description: | + Object containing details of the scheduled message. + properties: + reminder_id: + type: integer + description: | + The unique ID of the reminder, which can be used to + delete the reminder. + + This is different from the unique ID that the message would have + after being sent. + type: + type: string + description: | + The type of the reminder. Always set to `"private"`. + enum: + - private + to: + type: array + items: + type: integer + description: | + Contains the ID of the user who scheduled the reminder, + and to which the reminder will be sent. + content: + type: string + description: | + The content/body of the reminder, in text/markdown format. + rendered_content: + type: string + description: | + The content/body of the reminder rendered in HTML. + scheduled_delivery_timestamp: + type: integer + description: | + The UNIX timestamp for when the message will be sent + by the server, in UTC seconds. + example: 1595479019 + failed: + type: boolean + description: | + Whether the server has tried to send the reminder + and it failed to successfully send. + + Clients that support unscheduling reminders + should display scheduled messages with `"failed": true` with an + indicator that the server failed to send the message at the + scheduled time, so that the user is aware of the failure and can + get the content of the scheduled message. + reminder_target_message_id: + type: integer + description: | + The ID of the message that the reminder is created for. + required: + - reminder_id + - type + - to + - content + - rendered_content + - scheduled_delivery_timestamp + - failed + - reminder_target_message_id GroupPermissionSetting: description: | Configuration for a group permission setting specifying the groups diff --git a/zerver/tests/test_event_system.py b/zerver/tests/test_event_system.py index d04ab98073..5133d4fcb1 100644 --- a/zerver/tests/test_event_system.py +++ b/zerver/tests/test_event_system.py @@ -1241,7 +1241,7 @@ class FetchQueriesTest(ZulipTestCase): self.login_user(user) with ( - self.assert_database_query_count(46), + self.assert_database_query_count(47), mock.patch("zerver.lib.events.always_want") as want_mock, ): fetch_initial_state_data(user, realm=user.realm) @@ -1277,6 +1277,7 @@ class FetchQueriesTest(ZulipTestCase): realm_user_groups=2, realm_user_settings_defaults=1, recent_private_conversations=1, + reminders=1, saved_snippets=1, scheduled_messages=1, starred_messages=1, diff --git a/zerver/tests/test_home.py b/zerver/tests/test_home.py index 86220b141b..a74c8beafb 100644 --- a/zerver/tests/test_home.py +++ b/zerver/tests/test_home.py @@ -223,6 +223,7 @@ class HomeTest(ZulipTestCase): "realm_zulip_update_announcements_stream_id", "realm_moderation_request_channel_id", "recent_private_conversations", + "reminders", "saved_snippets", "scheduled_messages", "server_avatar_changes_disabled", @@ -284,7 +285,7 @@ class HomeTest(ZulipTestCase): # Verify succeeds once logged-in with ( - self.assert_database_query_count(56), + self.assert_database_query_count(57), patch("zerver.lib.cache.cache_set") as cache_mock, ): result = self._get_home_page(stream="Denmark") @@ -592,7 +593,7 @@ class HomeTest(ZulipTestCase): # Verify number of queries for Realm admin isn't much higher than for normal users. self.login("iago") with ( - self.assert_database_query_count(55), + self.assert_database_query_count(56), patch("zerver.lib.cache.cache_set") as cache_mock, ): result = self._get_home_page() @@ -624,7 +625,7 @@ class HomeTest(ZulipTestCase): self._get_home_page() # Then for the second page load, measure the number of queries. - with self.assert_database_query_count(51): + with self.assert_database_query_count(52): result = self._get_home_page() # Do a sanity check that our new streams were in the payload. diff --git a/zerver/tests/test_reminders.py b/zerver/tests/test_reminders.py index 508188b575..de530648ba 100644 --- a/zerver/tests/test_reminders.py +++ b/zerver/tests/test_reminders.py @@ -289,3 +289,58 @@ class RemindersTest(ZulipTestCase): self.get_dm_reminder_content(content, reminder.reminder_target_message_id), ) self.assertEqual(delivered_message.date_sent, more_than_scheduled_delivery_datetime) + + def test_delete_reminder(self) -> None: + hamlet = self.example_user("hamlet") + cordelia = self.example_user("cordelia") + + response = self.api_get(hamlet, "/api/v1/reminders") + self.assert_json_success(response) + response_data = response.json() + self.assertEqual(response_data["reminders"], []) + + # Create a test message to schedule a reminder for. + message_id = self.send_stream_message( + hamlet, + "Denmark", + ) + + # Schedule a reminder for the created message. + deliver_at = int(time.time() + 86400) + + response = self.do_schedule_reminder( + message_id=message_id, + scheduled_delivery_timestamp=deliver_at, + ) + self.assert_json_success(response) + response_data = response.json() + self.assertIn("reminder_id", response_data) + reminder_id = response_data["reminder_id"] + + # Verify that the reminder was scheduled correctly. + reminders_response = self.api_get(hamlet, "/api/v1/reminders") + self.assert_json_success(reminders_response) + reminders_data = reminders_response.json() + self.assert_length(reminders_data["reminders"], 1) + reminder = reminders_data["reminders"][0] + self.assertEqual(reminder["reminder_id"], reminder_id) + self.assertEqual(reminder["reminder_target_message_id"], message_id) + + # Test deleting the reminder with the wrong user. + result = self.api_delete(cordelia, f"/api/v1/reminders/{reminder_id}") + self.assert_json_error(result, "Reminder does not exist", status_code=404) + + # Test deleting the reminder. + result = self.client_delete(f"/json/reminders/{reminder_id}") + self.assert_json_success(result) + + # Verify that the reminder was deleted. + self.assertEqual(response.status_code, 200) + reminders_response = self.api_get(hamlet, "/api/v1/reminders") + self.assert_json_success(reminders_response) + reminders_data = reminders_response.json() + self.assert_length(reminders_data["reminders"], 0) + + # Try deleting again to trigger failure. + result = self.client_delete(f"/json/reminders/{reminder_id}") + self.assert_json_error(result, "Reminder does not exist", status_code=404) diff --git a/zerver/views/reminders.py b/zerver/views/reminders.py index 698da57331..46b4431b54 100644 --- a/zerver/views/reminders.py +++ b/zerver/views/reminders.py @@ -1,13 +1,14 @@ from django.http import HttpRequest, HttpResponse from django.utils.timezone import now as timezone_now -from pydantic import Json +from pydantic import Json, NonNegativeInt -from zerver.actions.reminders import schedule_reminder_for_message +from zerver.actions.reminders import do_delete_reminder, schedule_reminder_for_message from zerver.lib.exceptions import DeliveryTimeNotInFutureError +from zerver.lib.reminders import access_reminder from zerver.lib.request import RequestNotes from zerver.lib.response import json_success from zerver.lib.timestamp import timestamp_to_datetime -from zerver.lib.typed_endpoint import typed_endpoint +from zerver.lib.typed_endpoint import PathOnly, typed_endpoint from zerver.models import UserProfile @@ -33,3 +34,15 @@ def create_reminders_message_backend( deliver_at, ) return json_success(request, data={"reminder_id": reminder_id}) + + +@typed_endpoint +def delete_reminder( + request: HttpRequest, + user_profile: UserProfile, + *, + reminder_id: PathOnly[NonNegativeInt], +) -> HttpResponse: + reminder = access_reminder(user_profile, reminder_id) + do_delete_reminder(user_profile, reminder) + return json_success(request) diff --git a/zerver/views/scheduled_messages.py b/zerver/views/scheduled_messages.py index c27bec786e..7403713665 100644 --- a/zerver/views/scheduled_messages.py +++ b/zerver/views/scheduled_messages.py @@ -14,7 +14,10 @@ from zerver.lib.exceptions import DeliveryTimeNotInFutureError, JsonableError from zerver.lib.recipient_parsing import extract_direct_message_recipient_ids, extract_stream_id from zerver.lib.request import RequestNotes from zerver.lib.response import json_success -from zerver.lib.scheduled_messages import get_undelivered_scheduled_messages +from zerver.lib.scheduled_messages import ( + get_undelivered_reminders, + get_undelivered_scheduled_messages, +) from zerver.lib.timestamp import timestamp_to_datetime from zerver.lib.typed_endpoint import ( ApiParamConfig, @@ -34,6 +37,11 @@ def fetch_scheduled_messages(request: HttpRequest, user_profile: UserProfile) -> ) +@typed_endpoint_without_parameters +def fetch_reminders(request: HttpRequest, user_profile: UserProfile) -> HttpResponse: + return json_success(request, data={"reminders": get_undelivered_reminders(user_profile)}) + + @typed_endpoint def delete_scheduled_messages( request: HttpRequest, diff --git a/zproject/urls.py b/zproject/urls.py index dc70da6aef..52125bbdee 100644 --- a/zproject/urls.py +++ b/zproject/urls.py @@ -164,7 +164,7 @@ from zerver.views.registration import ( realm_register, signup_send_confirm, ) -from zerver.views.reminders import create_reminders_message_backend +from zerver.views.reminders import create_reminders_message_backend, delete_reminder from zerver.views.report import report_csp_violations from zerver.views.saved_snippets import ( create_saved_snippet, @@ -175,6 +175,7 @@ from zerver.views.saved_snippets import ( from zerver.views.scheduled_messages import ( create_scheduled_message_backend, delete_scheduled_messages, + fetch_reminders, fetch_scheduled_messages, update_scheduled_message_backend, ) @@ -378,7 +379,11 @@ v1_api_and_json_patterns = [ DELETE=delete_saved_snippet, PATCH=edit_saved_snippet, ), - rest_path("reminders", POST=create_reminders_message_backend), + rest_path("reminders", GET=fetch_reminders, POST=create_reminders_message_backend), + rest_path( + "reminders/", + DELETE=delete_reminder, + ), rest_path( "scheduled_messages", GET=fetch_scheduled_messages, POST=create_scheduled_message_backend ),