From 130b2cb9a2caf2e3c801b312356711f5ac18a202 Mon Sep 17 00:00:00 2001 From: Abhilash Dubey <124042593+AbhilashKD@users.noreply.github.com> Date: Fri, 7 Mar 2025 15:58:39 +0530 Subject: [PATCH 1/8] Create dev-PYM.yaml --- .github/workflows/dev-PYM.yaml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/dev-PYM.yaml diff --git a/.github/workflows/dev-PYM.yaml b/.github/workflows/dev-PYM.yaml new file mode 100644 index 0000000..b919fc1 --- /dev/null +++ b/.github/workflows/dev-PYM.yaml @@ -0,0 +1,28 @@ +name: DEVPYM-Deployment Pratham-OU Mumbai Region +on: + push: + branches: + - youth-management +jobs: + DEVPYM-DEPLOYMENT: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Extract Branch Name + run: echo "BRANCH_NAME=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV + + - name: Deploy Stack + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.HOST_NAME_DEVPYM }} + username: ${{ secrets.USERNAME_DEVPYM }} + key: ${{ secrets.EC2_SSH_KEY_DEVPYM }} + port: ${{ secrets.PORT_DEVPYM }} + script: | + cd ${{ secrets.TARGET_DIR_DEVPYM }} + if [ -f .env ]; then + rm .env + fi + echo "${{ secrets.DEVPYM_ENV }}" > .env + ls -ltra + ./deploy.sh ${{ env.BRANCH_NAME }} From f72d0342864972eb8e622a9669c4d181510cc9e0 Mon Sep 17 00:00:00 2001 From: Abhilash Dubey <124042593+AbhilashKD@users.noreply.github.com> Date: Wed, 12 Mar 2025 14:20:47 +0530 Subject: [PATCH 2/8] Update dev-PYM.yaml --- .github/workflows/dev-PYM.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dev-PYM.yaml b/.github/workflows/dev-PYM.yaml index b919fc1..a28fd72 100644 --- a/.github/workflows/dev-PYM.yaml +++ b/.github/workflows/dev-PYM.yaml @@ -2,7 +2,7 @@ name: DEVPYM-Deployment Pratham-OU Mumbai Region on: push: branches: - - youth-management + - east-africa-ym jobs: DEVPYM-DEPLOYMENT: runs-on: ubuntu-latest From e62552825c8c04734cfda12a4a42e0255408d4f8 Mon Sep 17 00:00:00 2001 From: Abhilash Dubey <124042593+AbhilashKD@users.noreply.github.com> Date: Wed, 12 Mar 2025 14:25:27 +0530 Subject: [PATCH 3/8] Update dev-PYM.yaml --- .github/workflows/dev-PYM.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dev-PYM.yaml b/.github/workflows/dev-PYM.yaml index a28fd72..2feb5ce 100644 --- a/.github/workflows/dev-PYM.yaml +++ b/.github/workflows/dev-PYM.yaml @@ -23,6 +23,6 @@ jobs: if [ -f .env ]; then rm .env fi - echo "${{ secrets.DEVPYM_ENV }}" > .env + echo '${{ secrets.DEVPYM_ENV }}"' > .env ls -ltra ./deploy.sh ${{ env.BRANCH_NAME }} From 71e8ea5d4aebeffed1074664c96a474d02cbafa8 Mon Sep 17 00:00:00 2001 From: Prasad Harke Date: Wed, 14 May 2025 15:07:23 +0530 Subject: [PATCH 4/8] Create aspire-leader-dev.yaml --- .github/workflows/aspire-leader-dev.yaml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/aspire-leader-dev.yaml diff --git a/.github/workflows/aspire-leader-dev.yaml b/.github/workflows/aspire-leader-dev.yaml new file mode 100644 index 0000000..91326be --- /dev/null +++ b/.github/workflows/aspire-leader-dev.yaml @@ -0,0 +1,20 @@ +name: Deploy to dev +on: + push: + branches: + - aspire-leaders +jobs: + deploy: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Deploy Stack + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.HOST_AL }} + username: ${{ secrets.USERNAME_AL }} + key: ${{ secrets.SSH_PRIVATE_KEY_AL }} + port: ${{ secrets.PORT_AL }} + script: | + cd /home/ubuntu/Aspire-leader/MICROSERVICES/NOTIFICATION + ./deploy.sh From b6ef4530252d8faa6fb5465cbbc8138c4f1b664f Mon Sep 17 00:00:00 2001 From: vaishali K Date: Fri, 11 Jul 2025 15:50:38 +0530 Subject: [PATCH 5/8] Sendgrid email integration --- package.json | 22 +++--- src/common/utils/constant.util.ts | 4 +- .../adapters/emailService.adapter.ts | 73 ++++++++++++++++--- .../adapters/pushService.adapter.ts | 42 ++++++++--- .../notification/notification.service.ts | 2 +- .../notificationActionTemplates.entity.ts | 5 +- src/modules/rabbitmq/rabbitmq.module.ts | 2 +- 7 files changed, 111 insertions(+), 39 deletions(-) diff --git a/package.json b/package.json index 8f2f674..1336087 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "crud-app", + "name": "notification", "version": "0.0.1", "description": "", "author": "", @@ -21,16 +21,15 @@ "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { - "@golevelup/nestjs-rabbitmq": "^5.3.0", "@aws-sdk/client-sns": "^3.750.0", - "@nestjs-plus/rabbitmq": "^1.4.4", - "@nestjs/common": "^9.0.0", - "@nestjs/config": "^2.3.0", - "@nestjs/core": "^8.4.7", - "jwt-decode": "^4.0.0", - "@nestjs/platform-express": "^8.0.0", + "@golevelup/nestjs-rabbitmq": "^5.7.0", + "@nestjs/common": "^10.0.0", + "@nestjs/config": "^3.0.0", + "@nestjs/core": "^10.0.0", + "@nestjs/platform-express": "^10.0.0", "@nestjs/swagger": "^7.3.1", - "@nestjs/typeorm": "^8.1.4", + "@nestjs/typeorm": "^10.0.0", + "@sendgrid/mail": "^8.1.5", "@types/amqplib": "^0.10.5", "amqp-connection-manager": "^4.1.14", "axios": "^1.6.5", @@ -38,9 +37,10 @@ "class-validator": "^0.14.1", "dotenv": "^16.0.1", "firebase-admin": "^12.6.0", + "jwt-decode": "^4.0.0", "notifme-sdk": "^1.11.0", "pg": "^8.11.3", - "reflect-metadata": "^0.1.13", + "reflect-metadata": "^0.2.2", "rimraf": "^3.0.2", "rxjs": "^7.2.0", "twilio": "^5.0.0-rc.1", @@ -51,7 +51,7 @@ "devDependencies": { "@nestjs/cli": "^8.0.0", "@nestjs/schematics": "^8.0.0", - "@nestjs/testing": "^8.0.0", + "@nestjs/testing": "^10.0.0", "@types/express": "^4.17.13", "@types/jest": "27.5.0", "@types/node": "^16.0.0", diff --git a/src/common/utils/constant.util.ts b/src/common/utils/constant.util.ts index 3f9ec06..d07655b 100644 --- a/src/common/utils/constant.util.ts +++ b/src/common/utils/constant.util.ts @@ -55,7 +55,9 @@ export const ERROR_MESSAGES = { SMS_NOTIFICATION_FAILED: 'Failed to Send SMS Notification', ALREADY_EXIST_KEY_FOR_CONTEXT: 'Already Exist key with this context', ALREADY_EXIST_KEY_FOR_CONTEXT_ENTER_ANOTHER: 'Key already exist for this context. Please enter another key', - NOT_EMPTY_SUBJECT_OR_BODY: 'Subject and body cannot be empty.' + NOT_EMPTY_SUBJECT_OR_BODY: 'Subject and body cannot be empty.', + PUSH_ADAPTER_NOT_INITIALIZED: 'PushAdapter is not initialized. Check Firebase configuration.', + EMAIL_ADAPTER_NOT_CONFIGURED: 'EmailAdapter is not configured. Check Email configuration.' } export const SMS_PROVIDER = { diff --git a/src/modules/notification/adapters/emailService.adapter.ts b/src/modules/notification/adapters/emailService.adapter.ts index 76d8f27..0ee460a 100644 --- a/src/modules/notification/adapters/emailService.adapter.ts +++ b/src/modules/notification/adapters/emailService.adapter.ts @@ -10,6 +10,8 @@ import { NotificationLog } from "../entity/notificationLogs.entity"; import { NotificationService } from "../notification.service"; import { LoggerUtil } from "src/common/logger/LoggerUtil"; import { ERROR_MESSAGES, SUCCESS_MESSAGES } from "src/common/utils/constant.util"; +import { ConfigService } from "@nestjs/config"; +import * as sgMail from '@sendgrid/mail'; /** * Interface for raw email data @@ -24,10 +26,44 @@ export interface RawEmailData { @Injectable() export class EmailAdapter implements NotificationServiceInterface { + private provider: 'smtp' | 'sendgrid' = 'smtp'; + private isConfigured = false; constructor( - @Inject(forwardRef(() => NotificationService)) private readonly notificationServices: NotificationService - ) { } + @Inject(forwardRef(() => NotificationService)) private readonly notificationServices: NotificationService, + private readonly configService: ConfigService + ) { + this.provider = (this.configService.get('EMAIL_PROVIDER') || 'smtp') as 'smtp' | 'sendgrid'; + if (this.provider === 'smtp' && this.hasSmtpConfig()) { + this.isConfigured = true; + } + + if (this.provider === 'sendgrid' && this.hasSendGridConfig()) { + sgMail.setApiKey(this.configService.get('SENDGRID_API_KEY')); + this.isConfigured = true; + } + + if (!this.isConfigured) { + console.warn('EmailAdapter not configured: Missing required settings.'); + LoggerUtil + } + } + + private hasSmtpConfig(): boolean { + return ( + !!this.configService.get('EMAIL_HOST') && + !!this.configService.get('EMAIL_PORT') && + !!this.configService.get('EMAIL_USER') && + !!this.configService.get('EMAIL_PASS') + ); + } + + private hasSendGridConfig(): boolean { + return !!( + this.configService.get('SENDGRID_API_KEY') && + this.configService.get('EMAIL_FROM') + ); + } /** * Sends notifications using template-based approach * @param notificationDataArray Array of notification data objects @@ -155,7 +191,7 @@ export class EmailAdapter implements NotificationServiceInterface { email: { providers: [ { - type: process.env.EMAIL_TYPE, + type: process.env.EMAIL_PROVIDER || 'smtp', host: process.env.EMAIL_HOST, port: process.env.EMAIL_PORT, secure: false, @@ -186,17 +222,34 @@ export class EmailAdapter implements NotificationServiceInterface { */ async send(notificationData) { const notificationLogs = this.createNotificationLog(notificationData, notificationData.subject, notificationData.key, notificationData.body, notificationData.recipient); + let result; try { - const emailConfig = this.getEmailConfig(notificationData.context); - const notifmeSdk = new NotifmeSdk(emailConfig); - const result = await notifmeSdk.send({ - email: { - from: emailConfig.email.from, + if (this.provider === 'smtp') { + const emailConfig = this.getEmailConfig(notificationData.context); + const notifmeSdk = new NotifmeSdk(emailConfig); + result = await notifmeSdk.send({ + email: { + from: emailConfig.email.from, + to: notificationData.recipient, + subject: notificationData.subject, + html: notificationData.body, + }, + }); + } + else if (this.provider === 'sendgrid') { + const msg = { to: notificationData.recipient, + from: this.configService.get('EMAIL_FROM'), subject: notificationData.subject, html: notificationData.body, - }, - }); + }; + const sgResponse = await sgMail.send(msg); + result = { + status: sgResponse[0]?.statusCode === 202 ? 'success' : 'error', + id: sgResponse[0]?.headers['x-message-id'] || null, + errors: sgResponse[0]?.statusCode !== 202 ? sgResponse[0]?.body : undefined, + }; + } if (result.status === 'success') { notificationLogs.status = true; await this.notificationServices.saveNotificationLogs(notificationLogs); diff --git a/src/modules/notification/adapters/pushService.adapter.ts b/src/modules/notification/adapters/pushService.adapter.ts index 284afc2..383637d 100644 --- a/src/modules/notification/adapters/pushService.adapter.ts +++ b/src/modules/notification/adapters/pushService.adapter.ts @@ -7,29 +7,47 @@ import { NotificationLog } from "../entity/notificationLogs.entity"; import { NotificationService } from "../notification.service"; import { ERROR_MESSAGES, SUCCESS_MESSAGES } from "src/common/utils/constant.util"; import { LoggerUtil } from "src/common/logger/LoggerUtil"; -@Injectable() export class PushAdapter implements NotificationServiceInterface { private readonly fcmurl: string; + private isInitialized = false; constructor( @Inject(forwardRef(() => NotificationService)) private readonly notificationServices: NotificationService, private readonly configService: ConfigService ) { this.fcmurl = this.configService.get('FCM_URL'); + if (this.hasRequiredConfig()) { + //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 + }; - // 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), - }); + if (admin.apps.length === 0) { + admin.initializeApp({ + credential: admin.credential.cert(serviceAccount), + }); + } + this.isInitialized = true; + } + else { + console.warn('PushAdapter not initialized: Missing Firebase config.'); + LoggerUtil.error(ERROR_MESSAGES.PUSH_ADAPTER_NOT_INITIALIZED); } } + + private hasRequiredConfig(): boolean { + return !!( + this.configService.get('FIREBASE_PROJECT_ID') && + this.configService.get('FIREBASE_CLIENT_EMAIL') && + this.configService.get('FIREBASE_PRIVATE_KEY') + ); + } async sendNotification(notificationDataArray) { + if (!this.isInitialized) { + LoggerUtil.error(ERROR_MESSAGES.PUSH_ADAPTER_NOT_INITIALIZED); + throw new Error('PushAdapter is not initialized. Check Firebase configuration.'); + } const results = []; for (const notificationData of notificationDataArray) { try { diff --git a/src/modules/notification/notification.service.ts b/src/modules/notification/notification.service.ts index 1ae8179..ebf4e63 100644 --- a/src/modules/notification/notification.service.ts +++ b/src/modules/notification/notification.service.ts @@ -14,7 +14,7 @@ import { Response } from "express"; import { NotificationActions } from "../notification_events/entity/notificationActions.entity"; import { NotificationActionTemplates } from "../notification_events/entity/notificationActionTemplates.entity"; import { NotificationQueue } from "../notification-queue/entities/notificationQueue.entity"; -import { AmqpConnection, RabbitSubscribe } from "@nestjs-plus/rabbitmq"; +import { AmqpConnection, RabbitSubscribe } from "@golevelup/nestjs-rabbitmq"; import { NotificationQueueService } from "../notification-queue/notificationQueue.service"; import { APIID } from "src/common/utils/api-id.config"; import {EmailAdapter} from "src/modules/notification/adapters/emailService.adapter"; diff --git a/src/modules/notification_events/entity/notificationActionTemplates.entity.ts b/src/modules/notification_events/entity/notificationActionTemplates.entity.ts index 76b4e15..5059ddd 100644 --- a/src/modules/notification_events/entity/notificationActionTemplates.entity.ts +++ b/src/modules/notification_events/entity/notificationActionTemplates.entity.ts @@ -28,13 +28,12 @@ export class NotificationActionTemplates { @Column({ type: 'uuid' }) createdBy: string; - @Column() + @Column({ nullable: true }) image: string; - @Column() + @Column({ nullable: true }) link: string; - @Column({ type: 'uuid', nullable: true }) updatedBy: string; diff --git a/src/modules/rabbitmq/rabbitmq.module.ts b/src/modules/rabbitmq/rabbitmq.module.ts index 288edd7..2e58579 100644 --- a/src/modules/rabbitmq/rabbitmq.module.ts +++ b/src/modules/rabbitmq/rabbitmq.module.ts @@ -1,5 +1,5 @@ import { Module } from '@nestjs/common'; -import { RabbitMQModule } from '@nestjs-plus/rabbitmq'; +import { RabbitMQModule } from '@golevelup/nestjs-rabbitmq'; import { ConfigService } from '@nestjs/config'; @Module({ From fff30b365260938aff54d217f890008088fa629f Mon Sep 17 00:00:00 2001 From: vaishali K Date: Fri, 11 Jul 2025 16:08:26 +0530 Subject: [PATCH 6/8] Sendgrid email integration --- src/modules/notification/adapters/emailService.adapter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/notification/adapters/emailService.adapter.ts b/src/modules/notification/adapters/emailService.adapter.ts index 0ee460a..4b76562 100644 --- a/src/modules/notification/adapters/emailService.adapter.ts +++ b/src/modules/notification/adapters/emailService.adapter.ts @@ -44,7 +44,7 @@ export class EmailAdapter implements NotificationServiceInterface { if (!this.isConfigured) { console.warn('EmailAdapter not configured: Missing required settings.'); - LoggerUtil + LoggerUtil.error(ERROR_MESSAGES.EMAIL_ADAPTER_NOT_CONFIGURED); } } From dda20060535934f54a9e13ae126a69a7c131731b Mon Sep 17 00:00:00 2001 From: Vaishali K Date: Fri, 11 Jul 2025 16:15:24 +0530 Subject: [PATCH 7/8] Delete .github/workflows/aspire-leader-dev.yaml --- .github/workflows/aspire-leader-dev.yaml | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 .github/workflows/aspire-leader-dev.yaml diff --git a/.github/workflows/aspire-leader-dev.yaml b/.github/workflows/aspire-leader-dev.yaml deleted file mode 100644 index 91326be..0000000 --- a/.github/workflows/aspire-leader-dev.yaml +++ /dev/null @@ -1,20 +0,0 @@ -name: Deploy to dev -on: - push: - branches: - - aspire-leaders -jobs: - deploy: - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - name: Deploy Stack - uses: appleboy/ssh-action@master - with: - host: ${{ secrets.HOST_AL }} - username: ${{ secrets.USERNAME_AL }} - key: ${{ secrets.SSH_PRIVATE_KEY_AL }} - port: ${{ secrets.PORT_AL }} - script: | - cd /home/ubuntu/Aspire-leader/MICROSERVICES/NOTIFICATION - ./deploy.sh From 1a70558cda8fc21cc752f9138dec854147921b28 Mon Sep 17 00:00:00 2001 From: Vaishali K Date: Fri, 11 Jul 2025 16:16:18 +0530 Subject: [PATCH 8/8] Delete .github/workflows/dev-PYM.yaml --- .github/workflows/dev-PYM.yaml | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 .github/workflows/dev-PYM.yaml diff --git a/.github/workflows/dev-PYM.yaml b/.github/workflows/dev-PYM.yaml deleted file mode 100644 index 2feb5ce..0000000 --- a/.github/workflows/dev-PYM.yaml +++ /dev/null @@ -1,28 +0,0 @@ -name: DEVPYM-Deployment Pratham-OU Mumbai Region -on: - push: - branches: - - east-africa-ym -jobs: - DEVPYM-DEPLOYMENT: - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - name: Extract Branch Name - run: echo "BRANCH_NAME=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV - - - name: Deploy Stack - uses: appleboy/ssh-action@master - with: - host: ${{ secrets.HOST_NAME_DEVPYM }} - username: ${{ secrets.USERNAME_DEVPYM }} - key: ${{ secrets.EC2_SSH_KEY_DEVPYM }} - port: ${{ secrets.PORT_DEVPYM }} - script: | - cd ${{ secrets.TARGET_DIR_DEVPYM }} - if [ -f .env ]; then - rm .env - fi - echo '${{ secrets.DEVPYM_ENV }}"' > .env - ls -ltra - ./deploy.sh ${{ env.BRANCH_NAME }}