/** * Notification Service * * Handles creating and sending notifications to users */ import { Injectable, Logger, Inject, BadRequestException } from '@nestjs/common'; import { v4 as uuidv4, validate as isUuid } from 'uuid'; import { Notification, NotificationType, NotificationPriority, } from '@domain/entities/notification.entity'; import { NotificationRepository, NOTIFICATION_REPOSITORY, NotificationFilters, } from '@domain/ports/out/notification.repository'; export interface CreateNotificationInput { userId: string; organizationId: string; type: NotificationType; priority: NotificationPriority; title: string; message: string; metadata?: Record; actionUrl?: string; } @Injectable() export class NotificationService { private readonly logger = new Logger(NotificationService.name); constructor( @Inject(NOTIFICATION_REPOSITORY) private readonly notificationRepository: NotificationRepository ) {} /** * Create and send a notification */ async createNotification(input: CreateNotificationInput): Promise { try { const notification = Notification.create({ id: uuidv4(), ...input, }); await this.notificationRepository.save(notification); this.logger.log( `Notification created: ${input.type} for user ${input.userId} - ${input.title}` ); return notification; } catch (error: any) { this.logger.error( `Failed to create notification: ${error?.message || 'Unknown error'}`, error?.stack ); throw error; } } /** * Get notifications with filters */ async getNotifications(filters: NotificationFilters): Promise<{ notifications: Notification[]; total: number; }> { const [notifications, total] = await Promise.all([ this.notificationRepository.findByFilters(filters), this.notificationRepository.count(filters), ]); return { notifications, total }; } /** * Get notification by ID */ async getNotificationById(id: string): Promise { return this.notificationRepository.findById(id); } /** * Get unread notifications for a user */ async getUnreadNotifications(userId: string, limit: number = 50): Promise { return this.notificationRepository.findUnreadByUser(userId, limit); } /** * Get unread count for a user */ async getUnreadCount(userId: string): Promise { return this.notificationRepository.countUnreadByUser(userId); } /** * Get recent notifications for a user */ async getRecentNotifications(userId: string, limit: number = 50): Promise { return this.notificationRepository.findRecentByUser(userId, limit); } /** * Mark notification as read */ async markAsRead(id: string, userId: string): Promise { if (typeof id !== 'string' || !isUuid(id) || typeof userId !== 'string' || !isUuid(userId)) { throw new BadRequestException('Invalid notification or user ID'); } await this.notificationRepository.markAsRead(id, userId); this.logger.log(`Notification marked as read: ${id}`); } /** * Mark all notifications as read for a user */ async markAllAsRead(userId: string): Promise { await this.notificationRepository.markAllAsReadForUser(userId); this.logger.log(`All notifications marked as read for user: ${userId}`); } /** * Delete notification */ async deleteNotification(id: string): Promise { await this.notificationRepository.delete(id); this.logger.log(`Notification deleted: ${id}`); } /** * Cleanup old read notifications */ async cleanupOldNotifications(olderThanDays: number = 30): Promise { const deleted = await this.notificationRepository.deleteOldReadNotifications(olderThanDays); this.logger.log(`Cleaned up ${deleted} old read notifications`); return deleted; } /** * Helper methods for creating specific notification types */ async notifyBookingCreated( userId: string, organizationId: string, bookingNumber: string, bookingId: string ): Promise { return this.createNotification({ userId, organizationId, type: NotificationType.BOOKING_CREATED, priority: NotificationPriority.MEDIUM, title: 'Booking Created', message: `Your booking ${bookingNumber} has been created successfully.`, metadata: { bookingId, bookingNumber }, }); } async notifyBookingUpdated( userId: string, organizationId: string, bookingNumber: string, bookingId: string, status: string ): Promise { return this.createNotification({ userId, organizationId, type: NotificationType.BOOKING_UPDATED, priority: NotificationPriority.MEDIUM, title: 'Booking Updated', message: `Booking ${bookingNumber} status changed to ${status}.`, metadata: { bookingId, bookingNumber, status }, }); } async notifyBookingConfirmed( userId: string, organizationId: string, bookingNumber: string, bookingId: string ): Promise { return this.createNotification({ userId, organizationId, type: NotificationType.BOOKING_CONFIRMED, priority: NotificationPriority.HIGH, title: 'Booking Confirmed', message: `Your booking ${bookingNumber} has been confirmed by the carrier.`, metadata: { bookingId, bookingNumber }, }); } async notifyDocumentUploaded( userId: string, organizationId: string, documentName: string, bookingId: string ): Promise { return this.createNotification({ userId, organizationId, type: NotificationType.DOCUMENT_UPLOADED, priority: NotificationPriority.LOW, title: 'Document Uploaded', message: `Document "${documentName}" has been uploaded for your booking.`, metadata: { documentName, bookingId }, }); } }