import { prisma } from '../../config/database'; import type { NotificationType, Prisma } from '@prisma/client'; import { sseService } from './sse.service'; /** Maps NotificationType → NotificationPreferences field name */ const TYPE_TO_PREF: Record = { friend_request: 'enableFriendRequests', friend_accepted: 'enableFriendRequests', poke: 'enableFriendRequests', comment: 'enableComments', upload_approved: 'enableUploadApprovals', upload_rejected: 'enableUploadApprovals', achievement: 'enableAchievements', system: 'enableSystemUpdates', group_call: 'enableSystemUpdates', impact_story: 'enableSystemUpdates', referral_completed: 'enableSystemUpdates', challenge_update: 'enableSystemUpdates', shared_view_invite: 'enableFriendRequests', shared_view_accepted: 'enableFriendRequests', calendar_event_invite: 'enableFriendRequests', // Operational notification types shift_signup_confirmed: 'enableSystemUpdates', shift_reminder: 'enableSystemUpdates', shift_cancelled: 'enableSystemUpdates', canvass_session_summary: 'enableSystemUpdates', reengagement: 'enableSystemUpdates', }; export const notificationService = { /** Create a notification (respects user preferences) */ async createNotification( userId: string, type: NotificationType, title: string, message: string, metadata?: Record, ) { // Check user preferences — skip if they've disabled this type const prefs = await prisma.notificationPreferences.findUnique({ where: { userId } }); if (prefs) { const prefField = TYPE_TO_PREF[type]; if (prefField && (prefs as Record)[prefField] === false) { return null; // User opted out } } const notification = await prisma.notification.create({ data: { userId, type, title, message, metadata: metadata ? (metadata as Prisma.InputJsonValue) : undefined, }, }); // Push via SSE if user is connected (real-time delivery) sseService.sendToUser(userId, 'notification', { id: notification.id, type: notification.type, title: notification.title, message: notification.message, metadata: notification.metadata, createdAt: notification.createdAt, }); return notification; }, /** List notifications (paginated, optional unread filter) */ async listNotifications(userId: string, page: number, limit: number, unreadOnly: boolean) { const where: Prisma.NotificationWhereInput = { userId }; if (unreadOnly) where.isRead = false; const skip = (page - 1) * limit; const [notifications, total] = await Promise.all([ prisma.notification.findMany({ where, orderBy: { createdAt: 'desc' }, skip, take: limit, }), prisma.notification.count({ where }), ]); return { notifications, pagination: { page, limit, total, totalPages: Math.ceil(total / limit) }, }; }, /** Get unread notification count */ async getUnreadCount(userId: string): Promise { return prisma.notification.count({ where: { userId, isRead: false }, }); }, /** Mark a single notification as read */ async markAsRead(userId: string, notificationId: number) { const notification = await prisma.notification.findFirst({ where: { id: notificationId, userId }, }); if (!notification) { throw Object.assign(new Error('Notification not found'), { statusCode: 404 }); } return prisma.notification.update({ where: { id: notificationId }, data: { isRead: true, readAt: new Date() }, }); }, /** Mark all notifications as read */ async markAllAsRead(userId: string) { const result = await prisma.notification.updateMany({ where: { userId, isRead: false }, data: { isRead: true, readAt: new Date() }, }); return { marked: result.count }; }, /** Delete a notification */ async deleteNotification(userId: string, notificationId: number) { const notification = await prisma.notification.findFirst({ where: { id: notificationId, userId }, }); if (!notification) { throw Object.assign(new Error('Notification not found'), { statusCode: 404 }); } await prisma.notification.delete({ where: { id: notificationId } }); return { success: true }; }, /** Get notification preferences (auto-create with defaults) */ async getPreferences(userId: string) { let prefs = await prisma.notificationPreferences.findUnique({ where: { userId } }); if (!prefs) { prefs = await prisma.notificationPreferences.create({ data: { userId }, }); } return prefs; }, /** Update notification preferences */ async updatePreferences(userId: string, data: Record) { return prisma.notificationPreferences.upsert({ where: { userId }, update: { ...data, updatedAt: new Date() }, create: { userId, ...data }, }); }, };