Calendar reminders as push notifications
A job every minute finds the event reminders that fell due — repeating events and moved occurrences expanded as the calendar view does — and pushes each once to every device with notifications on (sdk.push, new: plugins can notify through the new-mail push delivery). A reminder missed while the worker was down is still sent within 10 minutes; cancelled events and declined invitations stay quiet. Title, then "In 15 minutes, 14:00 · Room 2" in the user's zone and clock format; it stays until dismissed and shows even with the app open; clicking it opens that day (/p/calendar?date=…, new deep link). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -179,6 +179,12 @@ class PluginSdk:
|
||||
CalDAV/CardDAV/ICS. Returns status + headers + body."""
|
||||
return PluginHttp()
|
||||
|
||||
# --- notifications ------------------------------------------------------
|
||||
@property
|
||||
def push(self) -> PluginPush:
|
||||
"""Notifications on the user's devices (Web Push) — calendar reminders."""
|
||||
return PluginPush(self.name)
|
||||
|
||||
# --- secret vault -------------------------------------------------------
|
||||
@property
|
||||
def vault(self) -> PluginVault:
|
||||
@@ -187,6 +193,39 @@ class PluginSdk:
|
||||
return PluginVault()
|
||||
|
||||
|
||||
class PluginPush:
|
||||
"""``sdk.push``: show a notification on every device where the user turned
|
||||
notifications on (Settings → Accounts → Notifications)."""
|
||||
|
||||
def __init__(self, plugin: str) -> None:
|
||||
self._plugin = plugin
|
||||
|
||||
async def notify(
|
||||
self,
|
||||
user_id: uuid.UUID,
|
||||
*,
|
||||
title: str,
|
||||
body: str = "",
|
||||
url: str = "/mail",
|
||||
tag: str | None = None,
|
||||
sticky: bool = False,
|
||||
) -> int:
|
||||
"""Send one notification; returns how many devices it went to. ``tag``
|
||||
replaces an earlier notification with the same tag (it is prefixed with
|
||||
the plugin's name); ``sticky`` keeps it on screen until dismissed."""
|
||||
from app.push.service import deliver
|
||||
|
||||
payload: dict[str, object] = {
|
||||
"type": f"plugin.{self._plugin}",
|
||||
"title": title[:200],
|
||||
"body": body[:500],
|
||||
"url": url if url.startswith("/") else "/mail",
|
||||
"tag": f"{self._plugin}-{tag}" if tag else self._plugin,
|
||||
"sticky": sticky,
|
||||
}
|
||||
return await deliver(user_id, [payload])
|
||||
|
||||
|
||||
def sdk(name: str) -> PluginSdk:
|
||||
"""Return the SDK facade bound to plugin ``name`` (SPEC §11)."""
|
||||
return PluginSdk(name)
|
||||
|
||||
Reference in New Issue
Block a user