Add reported-issues, Goodreads sync & notifs

Introduce user-reported-issues and Goodreads shelf sync features and wire them into notifications. Adds Prisma migrations and schema changes (ReportedIssue, GoodreadsShelf, GoodreadsBookMapping), API endpoints for reporting (POST /audiobooks/[asin]/report-issue) and admin management (list, resolve/dismiss, replace), and an admin UI section to view/dismiss/replace reported issues. Adds a new notification event (issue_reported) with updates to notification schemas, docs and provider handling, plus a notification-events constants file. Refactors request creation to use createRequestForUser service, adds a Goodreads sync processor/service/hooks/UI modals, a scrape-resilience util, and related tests and minor integration updates.
This commit is contained in:
kikootwo
2026-02-11 16:49:55 -05:00
parent b013538b63
commit 20c8fb0898
69 changed files with 4167 additions and 766 deletions
+82
View File
@@ -0,0 +1,82 @@
/**
* Component: Notification Event Constants
* Documentation: documentation/backend/services/notifications.md
*
* Single source of truth for all notification event types and metadata.
* Add new events here — all providers, API schemas, and UI labels derive from this.
*/
export type NotificationSeverity = 'info' | 'success' | 'error' | 'warning';
export type NotificationPriority = 'normal' | 'high';
/**
* Central registry of notification events.
*
* Each entry defines:
* - `label`: Human-readable name shown in the UI
* - `title`: Title used in notification messages
* - `emoji`: Emoji prefix for notification titles
* - `severity`: Drives provider formatting (colors, Apprise types, ntfy tags)
* - `priority`: Drives notification urgency (Pushover/ntfy priority levels)
*/
export const NOTIFICATION_EVENTS = {
request_pending_approval: {
label: 'Request Pending Approval',
title: 'New Request Pending Approval',
emoji: '\u{1F4EC}',
severity: 'info' as const,
priority: 'normal' as const,
},
request_approved: {
label: 'Request Approved',
title: 'Request Approved',
emoji: '\u2705',
severity: 'success' as const,
priority: 'normal' as const,
},
request_available: {
label: 'Audiobook Available',
title: 'Audiobook Available',
emoji: '\u{1F389}',
severity: 'success' as const,
priority: 'high' as const,
},
request_error: {
label: 'Request Error',
title: 'Request Error',
emoji: '\u274C',
severity: 'error' as const,
priority: 'high' as const,
},
issue_reported: {
label: 'Issue Reported',
title: 'Issue Reported',
emoji: '\u{1F6A9}',
severity: 'warning' as const,
priority: 'high' as const,
},
} as const;
/** Union type of all valid notification event keys */
export type NotificationEvent = keyof typeof NOTIFICATION_EVENTS;
/** Ordered array of all notification event keys (for Zod schemas, iteration) */
export const NOTIFICATION_EVENT_KEYS = Object.keys(NOTIFICATION_EVENTS) as [NotificationEvent, ...NotificationEvent[]];
/** Metadata shape for a single notification event */
export type NotificationEventMeta = (typeof NOTIFICATION_EVENTS)[NotificationEvent];
/** Helper: get event metadata by key */
export function getEventMeta(event: NotificationEvent) {
return NOTIFICATION_EVENTS[event];
}
/** Helper: get the human-readable label for an event */
export function getEventLabel(event: NotificationEvent): string {
return NOTIFICATION_EVENTS[event].label;
}
/** Record mapping all event keys to their labels (for UI dropdowns, etc.) */
export const EVENT_LABELS: Record<NotificationEvent, string> = Object.fromEntries(
Object.entries(NOTIFICATION_EVENTS).map(([key, meta]) => [key, meta.label])
) as Record<NotificationEvent, string>;