Introduces a full notification system with support for Discord and Pushover backends, event triggers, and message formatting. Adds backend services, processors, and API endpoints for managing notifications, as well as a new Notifications tab in the admin settings UI. Updates documentation, database schema, and tests to cover notification features and approval workflow improvements. Also changes project license from MIT to AGPL v3.
6.5 KiB
Notification System
Status: ✅ Implemented | Extensible notification system with Discord and Pushover support
Overview
Sends notifications for audiobook request events (pending approval, approved, available, error) to configured backends. Non-blocking, atomic per-backend failure handling. Proper notification timing for all request flows including interactive search.
Key Details
- Backends: Discord (webhooks), Pushover (API)
- Events: request_pending_approval, request_approved, request_available, request_error
- Encryption: AES-256-GCM for sensitive config (webhook URLs, API keys)
- Delivery: Async via Bull job queue (priority 5)
- Failure Handling: Non-blocking, Promise.allSettled (one backend fails, others succeed)
Database Schema
model NotificationBackend {
id String @id @default(uuid())
type String // 'discord' | 'pushover'
name String // User-friendly label
config Json // Encrypted sensitive values
events Json // Array of subscribed events
enabled Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
Event Types
| Event | Trigger | Notification Sent When |
|---|---|---|
| request_pending_approval | User creates request | Request needs admin approval |
| request_approved | Admin approves OR auto-approval | Request approved (manual or auto) |
| request_available | Plex/ABS scan completes | Audiobook available in library |
| request_error | Download/import fails | Request failed at any stage |
Notification Triggers
Request Creation (POST /api/requests)
- Automatic search, approval needed:
status === 'awaiting_approval'→ request_pending_approval - Automatic search, auto-approved:
status === 'pending'→ request_approved - Interactive search: NO notification yet (deferred until torrent selection)
BookDate Swipe (POST /api/bookdate/swipe)
- Right swipe, approval needed:
status === 'awaiting_approval'→ request_pending_approval - Right swipe, auto-approved:
status === 'pending'→ request_approved
Request with Pre-Selected Torrent (POST /api/audiobooks/request-with-torrent)
- Approval needed:
status === 'awaiting_approval'→ request_pending_approval - Auto-approved:
status === 'downloading'→ request_approved
Torrent Selection for Existing Request (POST /api/requests/[id]/select-torrent)
- Approval needed:
status === 'awaiting_approval'→ request_pending_approval - Auto-approved:
status === 'downloading'→ request_approved
Admin Approval (POST /api/admin/requests/[id]/approve)
- Approve (with or without pre-selected torrent): After job triggered → request_approved
- Deny: No notification
Request Available (processors: scan-plex, plex-recently-added)
- After
status: 'available'update → request_available - Includes user info in query (plexUsername)
Request Error (processors: monitor-download, organize-files)
- After
status: 'failed'orstatus: 'warn'update → request_error - Includes error message in payload
Configuration Encryption
Encrypted Values:
- Discord:
webhookUrl - Pushover:
userKey,appToken
Pattern: iv:authTag:encryptedData (base64)
Masking: Sensitive values returned as •••••••• in API responses
Preservation: Masked values preserved on update (if value === '••••••••', use existing encrypted value)
Message Formatting
Discord (Rich Embeds):
- Color-coded by event (yellow=pending, green=approved, blue=available, red=error)
- Fields: Title, Author, Requested By, Error (if applicable)
- Footer: Request ID
- Timestamp: Event time
Pushover (Plain Text with Emojis):
- Emojis: 📬 📬 🎉 ❌
- Priority: Normal (0) for pending/approved, High (1) for available/error
- Format: Event title + book details + user + error (if applicable)
API Endpoints
GET /api/admin/notifications
- Returns all backends (sensitive values masked)
POST /api/admin/notifications
- Create backend (encrypts sensitive values)
- Body:
{type, name, config, events, enabled}
GET /api/admin/notifications/[id]
- Get single backend (sensitive values masked)
PUT /api/admin/notifications/[id]
- Update backend (preserves masked values, encrypts new values)
DELETE /api/admin/notifications/[id]
- Delete backend
POST /api/admin/notifications/test
- Test notification (synchronous, not via job queue)
- Body:
{type, config}(plaintext for testing) - Sends test payload: "The Hitchhiker's Guide to the Galaxy" by Douglas Adams
UI Components
NotificationsTab (src/app/admin/settings/tabs/NotificationsTab)
- Type selector cards (Discord: indigo "D", Pushover: blue "P")
- Configured backends grid (3 columns)
- Backend cards: type icon, name, enabled status, event count, edit/delete actions
- Modal: type-specific forms, event checkboxes, enable toggle, test button
Modal Features:
- Type-first selection (user clicks "Add Discord" or "Add Pushover")
- Password inputs for sensitive values
- Event subscription checkboxes (4 events, default: available + error)
- Test button (sends synchronous test notification)
- Save button (validates and creates/updates backend)
Job Queue Integration
Job Type: send_notification (priority 5, concurrency 5)
Payload:
{
jobId?: string,
event: string,
requestId: string,
title: string,
author: string,
userName: string,
message?: string,
timestamp: Date
}
Processor: src/lib/processors/send-notification.processor.ts
- Calls NotificationService.sendNotification()
- Non-blocking error handling (logs but doesn't throw)
Queue Method: addNotificationJob(event, requestId, title, author, userName, message?)
Extensibility
Adding New Backend (e.g., Email):
- Add 'email' to NotificationBackendType enum
- Create EmailConfig interface
- Add encryption logic for smtpPassword
- Implement sendEmail() method in NotificationService
- Add email card to type selector (green "E" badge)
- Add email form fields to modal
Adding New Event (e.g., download_complete):
- Add 'download_complete' to NotificationEvent enum
- Add to event labels in UI
- Add trigger point in processor
- Add message formatting in Discord/Pushover formatters
Tech Stack
- Bull (job queue)
- Node.js crypto (AES-256-GCM encryption)
- Discord webhooks, Pushover API
- React (UI), Tailwind CSS (styling)