diff --git a/package.json b/package.json index 42e3db5..f3d046f 100644 --- a/package.json +++ b/package.json @@ -27,14 +27,15 @@ "@nestjs/config": "^2.3.0", "@nestjs/core": "^8.4.7", "@nestjs/platform-express": "^8.0.0", - "@nestjs/typeorm": "^8.1.4", "@nestjs/swagger": "^7.3.1", + "@nestjs/typeorm": "^8.1.4", "@types/amqplib": "^0.10.5", "amqp-connection-manager": "^4.1.14", "axios": "^1.6.5", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "dotenv": "^16.0.1", + "firebase-admin": "^12.6.0", "notifme-sdk": "^1.11.0", "pg": "^8.11.3", "reflect-metadata": "^0.1.13", diff --git a/src/app.module.ts b/src/app.module.ts index 6f7b0c7..99dfbcf 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -5,7 +5,7 @@ import { NotificationEventsModule } from './modules/notification_events/notifica import { NotificationModule } from './modules/notification/notification.module'; import { DatabaseModule } from './common/database-modules'; import { ConfigModule, ConfigService } from '@nestjs/config'; -import { LoggerModule } from './logger/logger.module'; +// import { LoggerModule } from './logger/logger.module'; import { NotificationQueueModule } from './modules/notification-queue/notificationQueue.module'; import { RabbitmqModule } from './modules/rabbitmq/rabbitmq.module'; @@ -15,7 +15,7 @@ import { RabbitmqModule } from './modules/rabbitmq/rabbitmq.module'; ConfigModule.forRoot({ isGlobal: true }), DatabaseModule, RabbitmqModule, - NotificationEventsModule, NotificationModule, LoggerModule, NotificationQueueModule], + NotificationEventsModule, NotificationModule, NotificationQueueModule], controllers: [AppController], providers: [AppService, ConfigService], }) diff --git a/src/common/utils/constant.util.ts b/src/common/utils/constant.util.ts new file mode 100644 index 0000000..e4f05d7 --- /dev/null +++ b/src/common/utils/constant.util.ts @@ -0,0 +1,27 @@ +export const SUCCESS_MESSAGES = { + NOTIFICATION_COMPLETED: 'Notification process completed', + SEND_NOTIFICATION: '/send Notification', + NOTIFICATION_QUEUE_SAVE_SUCCESSFULLY: 'Notification saved in queue successfully', + NOTIFICATION_SENT_SUCCESSFULLY: 'Notification sent successfully', + PUSH_NOTIFICATION_SEND_SUCCESSFULLY: 'Push notification sent successfully', + + + +}; +export const ERROR_MESSAGES = { + INVALID_REQUEST: "Invalid request", + NOT_FOUND: "Not found", + UNAUTHORIZED: "Unauthorized", + FORBIDDEN: "Forbidden", + BAD_REQUEST: "Bad request", + INVALID_REQUEST_BODY: "Invalid request body", + INTERNAL_SERVER_ERROR: "Internal Server Error", + TEMPLATE_NOTFOUND: 'Template not found', + NOTIFICATION_FAILED: `Failed to Send Notification`, + TEMPLATE_CONFIG_NOTFOUND: 'Template Config not found for this context: ', + NOTIFICATION_QUEUE_SAVE_FAILED: 'Failed to save notifications in queue', + NOTIFICATION_LOG_SAVE_FAILED: 'Failed to save Log of notification', + TOPIC_NOTIFICATION_FAILED: 'Failed to send topic notification', + PUSH_NOTIFICATION_FAILED: 'Failed to send push notification' + +} \ No newline at end of file diff --git a/src/logger/logger.module.ts b/src/logger/logger.module.ts deleted file mode 100644 index b5bf843..0000000 --- a/src/logger/logger.module.ts +++ /dev/null @@ -1,30 +0,0 @@ - -import { Module } from '@nestjs/common'; -import { createLogger, transports, format } from "winston"; -import { createWriteStream } from "fs"; - -@Module({ - - -}) -export class LoggerModule -{ - logger = createLogger({ - transports: [ - new transports.Stream({ - stream: createWriteStream("combined.log"), - }), - ], - format: format.combine( - format.timestamp(), - format.printf(({ timestamp, level, message, service }) => { - return `[${timestamp}] ${service} ${level}: ${message}`; - }) - ), - defaultMeta: { - service: "WinstonExample", - }, - }); -} - - diff --git a/src/modules/notification/adapters/pushService.adapter.ts b/src/modules/notification/adapters/pushService.adapter.ts index 03beaa0..d81926f 100644 --- a/src/modules/notification/adapters/pushService.adapter.ts +++ b/src/modules/notification/adapters/pushService.adapter.ts @@ -1,50 +1,51 @@ -import { BadRequestException, Inject, Injectable, forwardRef } from "@nestjs/common"; +import { Inject, Injectable, forwardRef } from "@nestjs/common"; import { NotificationServiceInterface } from "../interface/notificationService"; -import { NotificationDto } from "../dto/notificationDto.dto"; import axios from "axios"; +import * as admin from "firebase-admin"; import { ConfigService } from "@nestjs/config"; import { NotificationLog } from "../entity/notificationLogs.entity"; import { NotificationService } from "../notification.service"; -import { InjectRepository } from "@nestjs/typeorm"; -import { Repository } from "typeorm"; -import { NotificationActions } from "src/modules/notification_events/entity/notificationActions.entity"; -import { NotificationActionTemplates } from "src/modules/notification_events/entity/notificationActionTemplates.entity"; import { LoggerService } from "src/common/logger/logger.service"; +import { ERROR_MESSAGES, SUCCESS_MESSAGES } from "src/common/utils/constant.util"; + @Injectable() export class PushAdapter implements NotificationServiceInterface { - private fcmkey: string; - private fcmurl: string + private readonly fcmurl: string; constructor( @Inject(forwardRef(() => NotificationService)) private readonly notificationServices: NotificationService, private readonly configService: ConfigService, - @InjectRepository(NotificationActions) - private notificationEventsRepo: Repository, - @InjectRepository(NotificationActionTemplates) - private notificationTemplateConfigRepository: Repository, - private logger: LoggerService + private readonly logger: LoggerService ) { - this.fcmkey = this.configService.get('FCM_KEY'); - this.fcmurl = this.configService.get('FCM_URL') + this.fcmurl = this.configService.get('FCM_URL'); + + // Initialize Firebase Admin SDK with environment variables + const serviceAccount = { + projectId: this.configService.get('FIREBASE_PROJECT_ID'), + clientEmail: this.configService.get('FIREBASE_CLIENT_EMAIL'), + privateKey: this.configService.get('FIREBASE_PRIVATE_KEY').replace(/\\n/g, '\n'), // Replace escaped newlines + }; + + if (admin.apps.length === 0) { + admin.initializeApp({ + credential: admin.credential.cert(serviceAccount), + }); + } } async sendNotification(notificationDataArray) { const results = []; for (const notificationData of notificationDataArray) { try { const result = await this.send(notificationData); - // return result; - if (result.data.success === 1) { + if (result.status === 200 && result.data.name) { results.push({ recipient: notificationData.recipient, status: 200, - result: 'Push notification sent successfully' + result: SUCCESS_MESSAGES.PUSH_NOTIFICATION_SEND_SUCCESSFULLY }); - } else if (result.data.failure === 1) { - throw new Error('Invalid token'); } - } catch (error) { - this.logger.error('Failed to send push notification', error); + this.logger.error(ERROR_MESSAGES.PUSH_NOTIFICATION_FAILED, error.toString()); results.push({ recipient: notificationData.recipient, status: 'error', @@ -70,38 +71,48 @@ export class PushAdapter implements NotificationServiceInterface { const notificationLogs = this.createNotificationLog(notificationData.context, notificationData.subject, notificationData.key, notificationData.body, notificationData.recipient); try { const notification = { - notification: { - title: notificationData.subject, - body: notificationData.body, - }, - to: notificationData.recipient - }; + message: { + notification: { + title: notificationData.subject, + body: notificationData.body, + }, + token: notificationData.recipient + } + } + + // Retrieve OAuth 2.0 access token + const accessToken = await this.getAccessToken(); // Function to get token + const result = await axios.post(this.fcmurl, notification, { headers: { 'Content-Type': 'application/json', - Authorization: `key=${this.fcmkey}`, + Authorization: `Bearer ${accessToken}`, }, }); - if (result.data.success === 1) { - this.logger.log('Push notification sent successful') + + if (result.status === 200 && result.data.name) { + this.logger.log('Push notification sent successfully'); notificationLogs.status = true; await this.notificationServices.saveNotificationLogs(notificationLogs); return result; + } else { + this.logger.log(ERROR_MESSAGES.PUSH_NOTIFICATION_FAILED); + throw new Error(ERROR_MESSAGES.PUSH_NOTIFICATION_FAILED); } - if (result.data.failure === 1) { - throw new Error('Invalid token'); - } - throw new Error('Unknown response from FCM server'); } catch (error) { - this.logger.error( - `Failed to Send Push Notification for ${notificationData.context}`, - error, - '/Not able to send Notification', - ); notificationLogs.status = false; notificationLogs.error = error.toString(); await this.notificationServices.saveNotificationLogs(notificationLogs); throw error; } } + + async getAccessToken() { + const token = await admin.credential.cert({ + projectId: this.configService.get('FIREBASE_PROJECT_ID'), + clientEmail: this.configService.get('FIREBASE_CLIENT_EMAIL'), + privateKey: this.configService.get('FIREBASE_PRIVATE_KEY').replace(/\\n/g, '\n'), + }).getAccessToken(); + return token.access_token; + } } diff --git a/src/modules/notification/notification.service.ts b/src/modules/notification/notification.service.ts index 3b8f4b7..8cdf195 100644 --- a/src/modules/notification/notification.service.ts +++ b/src/modules/notification/notification.service.ts @@ -5,8 +5,8 @@ import axios from 'axios'; import { NotificationDto } from './dto/notificationDto.dto'; import { NotificationAdapterFactory } from './notificationadapters'; import APIResponse from 'src/common/utils/response'; -import * as FCM from 'fcm-node'; -import { SubscribeToDeviceTopicDto } from './dto/subscribtotopic.dto'; +// import * as FCM from 'fcm-node'; +// import { SubscribeToDeviceTopicDto } from './dto/subscribtotopic.dto'; import { ConfigService } from '@nestjs/config'; import { TopicNotification } from './dto/topicnotification .dto'; import { NotificationLog } from './entity/notificationLogs.entity'; @@ -18,22 +18,24 @@ import { NotificationQueue } from '../notification-queue/entities/notificationQu import { AmqpConnection, RabbitSubscribe } from '@nestjs-plus/rabbitmq'; import { NotificationQueueService } from '../notification-queue/notificationQueue.service'; import { APIID } from 'src/common/utils/api-id.config'; +import { SUCCESS_MESSAGES, ERROR_MESSAGES } from 'src/common/utils/constant.util'; + @Injectable() export class NotificationService { - private readonly fcm: FCM; + // private readonly fcm: FCM; private readonly fcmkey; private readonly fcmurl; constructor( @InjectRepository(NotificationLog) - private notificationLogRepository: Repository, + private readonly notificationLogRepository: Repository, @InjectRepository(NotificationActions) - private notificationActions: Repository, + private readonly notificationActions: Repository, @InjectRepository(NotificationActionTemplates) - private notificationActionTemplates: Repository, + private readonly notificationActionTemplates: Repository, @InjectRepository(NotificationQueue) - private notificationQueue: Repository, + private readonly notificationQueue: Repository, private readonly notificationQueueService: NotificationQueueService, private readonly adapterFactory: NotificationAdapterFactory, private readonly configService: ConfigService, @@ -42,7 +44,7 @@ export class NotificationService { ) { this.fcmkey = this.configService.get('FCM_KEY'); this.fcmurl = this.configService.get('FCM_URL') - this.fcm = new FCM(this.fcmkey); + // this.fcm = new FCM(this.fcmkey); } async sendNotification(notificationDto: NotificationDto, response: Response): Promise { @@ -53,8 +55,8 @@ export class NotificationService { const notification_event = await this.notificationActions.findOne({ where: { context, key } }); if (!notification_event) { - this.logger.error('/Send Notification', 'Template not found', context); - throw new BadRequestException('Template not found'); + this.logger.error(SUCCESS_MESSAGES.SEND_NOTIFICATION, ERROR_MESSAGES.TEMPLATE_NOTFOUND, context); + throw new BadRequestException(ERROR_MESSAGES.TEMPLATE_NOTFOUND); } // Handle email notifications if specified @@ -129,14 +131,15 @@ export class NotificationService { apiId, finalResponses, HttpStatus.OK, - 'Notification process completed' + SUCCESS_MESSAGES.NOTIFICATION_COMPLETED ); - } catch (e) { + } + catch (e) { this.logger.error( - `Failed to Send Notification`, + ERROR_MESSAGES.NOTIFICATION_FAILED, e, - '/Not able to send Notification', + SUCCESS_MESSAGES.SEND_NOTIFICATION, ); throw e; } @@ -147,8 +150,8 @@ export class NotificationService { if (recipients && recipients.length > 0 && Object.keys(recipients).length > 0) { const notification_details = await this.notificationActionTemplates.find({ where: { actionId: notification_event.actionId, type } }); if (notification_details.length === 0) { - this.logger.error(`/Send ${channel} Notification`, `Template Config not found for this context: ${notificationDto.context}`, 'Not Found'); - throw new BadRequestException(`Notification template config not defined for ${type}`); + this.logger.error(`/Send ${channel} Notification`, `${ERROR_MESSAGES.TEMPLATE_CONFIG_NOTFOUND} ${notificationDto.context}`, 'Not Found'); + throw new BadRequestException(`${ERROR_MESSAGES.TEMPLATE_CONFIG_NOTFOUND} ${type}`); } let bodyText; let subject; @@ -189,12 +192,12 @@ export class NotificationService { try { const saveQueue = await this.saveNotificationQueue(notificationDataArray); if (saveQueue.length === 0) { - throw new Error('Failed to save notifications in queue'); + throw new Error(ERROR_MESSAGES.NOTIFICATION_QUEUE_SAVE_FAILED); } - return { status: 200, message: 'Notification saved in queue successfully' }; + return { status: 200, message: SUCCESS_MESSAGES.NOTIFICATION_QUEUE_SAVE_SUCCESSFULLY }; } catch (error) { - this.logger.error('Error to save notifications in queue', error); - throw new Error('Failed to save notifications in queue'); + this.logger.error(ERROR_MESSAGES.NOTIFICATION_QUEUE_SAVE_FAILED, error); + throw new Error(ERROR_MESSAGES.NOTIFICATION_QUEUE_SAVE_FAILED); } } else { const adapter = this.adapterFactory.getAdapter(type); @@ -233,24 +236,20 @@ export class NotificationService { //Provider which store in Queue async saveNotificationQueue(notificationDataArray) { - try { - const arrayofResult = await this.notificationQueue.save(notificationDataArray); - if (arrayofResult) { - if (this.amqpConnection && arrayofResult) { - try { - for (const result of arrayofResult) { - this.amqpConnection.publish('notification.exchange', 'notification.route', result, { persistent: true }); - } - } - catch (e) { - throw e; + const arrayofResult = await this.notificationQueue.save(notificationDataArray); + if (arrayofResult) { + if (this.amqpConnection) { + try { + for (const result of arrayofResult) { + this.amqpConnection.publish('notification.exchange', 'notification.route', result, { persistent: true }); } } - return arrayofResult; + catch (e) { + this.logger.error('/error to save in notification in rabbitMq', e) + throw e; + } } - } - catch (e) { - throw e; + return arrayofResult; } } @@ -262,7 +261,7 @@ export class NotificationService { async handleNotification(notification, message: any, retryCount = 3) { try { const adapter = this.adapterFactory.getAdapter(notification.channel); - const result = await adapter.sendNotification([notification]) + await adapter.sendNotification([notification]) const updateQueueDTO = { status: true, retries: 3 - retryCount, last_attempted: new Date() }; await this.notificationQueueService.updateQueue(notification.id, updateQueueDTO) } @@ -340,18 +339,18 @@ export class NotificationService { }, }); return { - message: 'Notification sent successfully', + message: SUCCESS_MESSAGES.NOTIFICATION_SENT_SUCCESSFULLY, status: response.status } } catch (e) { this.logger.error( - `Failed to Send Notification for topic this: ${requestBody.topic_name}`, - e, - '/Not able to send topic Notification', + `Failed to Send Notification for this: ${requestBody.topic_name} topic`, + e.toString(), + ERROR_MESSAGES.TOPIC_NOTIFICATION_FAILED, ); return { - message: 'Failed to send topic notification', + message: ERROR_MESSAGES.TOPIC_NOTIFICATION_FAILED, status: e.response.status } } @@ -359,15 +358,15 @@ export class NotificationService { async saveNotificationLogs(notificationLogs: NotificationLog) { try { - const result = await this.notificationLogRepository.save(notificationLogs); + await this.notificationLogRepository.save(notificationLogs); } catch (e) { this.logger.error( - `/POST Save notification log for notification`, - e, - '/Failed to Save Log of Notification for notification', - ); - throw new Error('Failed to save notification logs'); + `/POST, + ${e}, + ${ERROR_MESSAGES.NOTIFICATION_LOG_SAVE_FAILED}, + `); + throw new Error(ERROR_MESSAGES.NOTIFICATION_LOG_SAVE_FAILED); } } @@ -385,7 +384,7 @@ export class NotificationService { // const message = await client.messages.create({ // body: notificationData.message, // from: 'whatsapp:+14155238886', - // to: `whatsapp:${notificationData.to}`, + // to: `whatsapp: ${ notificationData.to }`, // }); // this.logger.info('Message sent successfully to whatsapp');