import { Injectable, Logger } from '@nestjs/common'; import { InjectQueue } from '@nestjs/bullmq'; import { Queue } from 'bullmq'; import { NotificationQueueNameEnum } from '../constants/queue'; import { InAppNotificationQueueJob, PushNotificationQueueJob, SmsNotificationQueueJob, } from '../interfaces/jobs-queue.interface'; @Injectable() export class NotificationQueueService { private readonly logger = new Logger(NotificationQueueService.name); constructor( @InjectQueue(NotificationQueueNameEnum.SMS) private readonly smsQueue: Queue, @InjectQueue(NotificationQueueNameEnum.PUSH) private readonly pushQueue: Queue, @InjectQueue(NotificationQueueNameEnum.IN_APP) private readonly inAppQueue: Queue, ) {} async addSmsNotification(job: SmsNotificationQueueJob): Promise { try { const jobId = `${job.templateId}-${this.generateRandomInt()}-${Date.now()}`; await this.smsQueue.add('sms-notification', job, { jobId, attempts: 3, backoff: { type: 'exponential', delay: 2000, }, removeOnComplete: { age: 24 * 3600, // Keep completed jobs for 24 hours count: 1000, }, removeOnFail: { age: 7 * 24 * 3600, // Keep failed jobs for 7 days }, }); this.logger.log(`SMS notification job added to queue: ${jobId}`); } catch (error) { this.logger.error(`Failed to add SMS notification to queue:`, error); throw error; } } async addPushNotification(job: PushNotificationQueueJob): Promise { try { const jobId = `${job.title}-${this.generateRandomInt()}-${Date.now()}`; await this.pushQueue.add('push-notification', job, { jobId, attempts: 3, backoff: { type: 'exponential', delay: 2000, }, removeOnComplete: { age: 24 * 3600, // Keep completed jobs for 24 hours count: 1000, }, removeOnFail: { age: 7 * 24 * 3600, // Keep failed jobs for 7 days }, }); this.logger.log(`Push notification job added to queue: ${jobId}`); } catch (error) { this.logger.error(`Failed to add push notification to queue:`, error); throw error; } } async addBulkSmsNotifications(jobs: SmsNotificationQueueJob[]): Promise { try { const queueJobs = jobs.map(job => ({ name: 'sms-notification', data: job, opts: { jobId: `${job.templateId}-${this.generateRandomInt()}`, attempts: 3, backoff: { type: 'exponential', delay: 2000, }, }, })); await this.smsQueue.addBulk(queueJobs); this.logger.log(`Added ${jobs.length} SMS notification jobs to queue`); } catch (error) { this.logger.error(`Failed to add bulk notifications to queue:`, error); throw error; } } async addBulkPushNotifications(jobs: PushNotificationQueueJob[]): Promise { try { const queueJobs = jobs.map(job => ({ name: 'push-notification', data: job, opts: { jobId: `${job.title}-${this.generateRandomInt()}-${Date.now()}`, attempts: 3, backoff: { type: 'exponential', delay: 2000, }, }, })); await this.pushQueue.addBulk(queueJobs); this.logger.log(`Added ${jobs.length} Push notification jobs to queue`); } catch (error) { this.logger.error(`Failed to add bulk notifications to queue:`, error); throw error; } } async addBulkInAppNotifications(jobs: InAppNotificationQueueJob[]): Promise { try { this.logger.log( `[addBulkInAppNotifications] queueing ${jobs.length} job(s): ${jobs .map(j => `admin=${j.recipient.adminId} rest=${j.recipient.restaurantId} notif=${j.notificationId}`) .join(' | ')}`, ); const queueJobs = jobs.map(job => ({ name: 'in-app-notification', data: job, opts: { jobId: `${job.subject}-${job.notificationId}-${Date.now()}`, attempts: 3, backoff: { type: 'exponential', delay: 2000, }, }, })); const added = await this.inAppQueue.addBulk(queueJobs); this.logger.log( `[addBulkInAppNotifications] added ${added.length} job(s) to queue "${NotificationQueueNameEnum.IN_APP}": ids=${added.map(j => j.id).join(', ')}`, ); } catch (error) { this.logger.error(`[addBulkInAppNotifications] failed to add jobs to queue:`, error); throw error; } } private generateRandomInt(): string { return Math.random().toString(36).substring(2, 15); } }