From 0bc08b9825168aae3bf313e22e4ffe8c10ab6338 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Mar 2026 18:25:21 +0000 Subject: [PATCH] feat: add Apprise alerting capabilities with user and admin notification channels Agent-Logs-Url: https://github.com/christianlouis/pop_puller_to_gmail/sessions/858e72a6-5282-4ce8-b38a-db4b9e2e6f65 Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com> --- backend/app/models/schemas.py | 5 ++++- backend/app/services/notification_service.py | 4 ++-- docs/TODO.md | 5 ++++- frontend/src/components/NotificationWizard.tsx | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/backend/app/models/schemas.py b/backend/app/models/schemas.py index 6ec3f1b..0aace87 100644 --- a/backend/app/models/schemas.py +++ b/backend/app/models/schemas.py @@ -225,7 +225,10 @@ class NotificationConfigBase(BaseModel): channel: NotificationChannel apprise_url: Optional[str] = Field(None, description="Apprise notification URL") is_enabled: bool = True - config: Dict[str, Any] = Field(default_factory=dict) + config: Dict[str, Any] = Field( + default_factory=dict, + description="Legacy channel-specific configuration (deprecated in favour of apprise_url)", + ) notify_on_errors: bool = True notify_on_success: bool = False notify_threshold: int = Field(default=3, gt=0, le=100) diff --git a/backend/app/services/notification_service.py b/backend/app/services/notification_service.py index c017e53..5927199 100644 --- a/backend/app/services/notification_service.py +++ b/backend/app/services/notification_service.py @@ -60,7 +60,7 @@ async def send_user_notification( continue try: - success = await _send_apprise(str(config.apprise_url), title, body) + success = await _send_apprise(config.apprise_url or "", title, body) if success: sent += 1 except Exception as exc: @@ -99,7 +99,7 @@ async def send_admin_notification( sent = 0 for config in configs: try: - success = await _send_apprise(str(config.apprise_url), title, body) + success = await _send_apprise(config.apprise_url or "", title, body) if success: sent += 1 except Exception as exc: diff --git a/docs/TODO.md b/docs/TODO.md index a71b347..939bab0 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -197,7 +197,7 @@ Comprehensive task breakdown for repository improvements and production readines - [x] Message deduplication (POP3 UIDL + IMAP \Seen flag + DB tracking) - [x] **Debug email**: "Send Debug Email" button in Settings injects a test message (from christian@docuelevate.org, dated today, labelled `test` + `imported`, placed in inbox) to verify end-to-end Gmail API delivery - [ ] Implement GDPR data export endpoint -- [ ] Complete notification service integration (Apprise) +- [x] Complete notification service integration (Apprise) - [ ] Add advanced email filtering - [ ] Implement OAuth2 for Gmail (instead of App Passwords) - [ ] Add attachment handling improvements @@ -238,6 +238,9 @@ because the API client layer is missing. - [ ] Error boundary components - [ ] Loading skeletons / proper loading states - [ ] Notification preferences UI +- [x] Notification channels page (`/notifications`) with full CRUD, wizard, and test button +- [x] Apprise-powered notification wizard for Telegram, Discord, Slack, Email, Webhook, and custom URLs +- [x] Admin system alert channels section (`/admin` page) with full CRUD and test - [ ] Subscription management / billing UI ### Admin Interface ✅ diff --git a/frontend/src/components/NotificationWizard.tsx b/frontend/src/components/NotificationWizard.tsx index 749e31b..1a7e17c 100644 --- a/frontend/src/components/NotificationWizard.tsx +++ b/frontend/src/components/NotificationWizard.tsx @@ -115,7 +115,7 @@ function buildAppriseUrl(channel: string, fields: Record): strin } case 'email': if (!fields.username || !fields.password || !fields.host) return ''; - return `mailtos://${encodeURIComponent(fields.username)}:${encodeURIComponent(fields.password)}@${fields.host}${ + return `mailto://${encodeURIComponent(fields.username)}:${encodeURIComponent(fields.password)}@${fields.host}${ fields.port ? `:${fields.port}` : '' }`; case 'webhook':