Skip to content

Latest commit

 

History

History
369 lines (258 loc) · 10.6 KB

README.md

File metadata and controls

369 lines (258 loc) · 10.6 KB

CNS 📧 📱

Table of Contents

Introduction

The primary purpose of this project is to develop a common notification service (CNS) as well as a notification API service that meets the specific business and technical requirements of SPTel.

💻 Technologies

Project is built with:

  • NodeJS Version: 20.10.0
  • Pnpm Version: 8.12.1
  • RabbitMQ Version: 3.12-management (Docker Image)
  • MongoDB
  • Postgres
  • NestJS Version: 10.3.1

Installation

$ pnpm install

Running the app 🚀

# watch mode
$ pnpm run start:dev

# notification service
$ pnpm run start:notification

# CNS
$ pnpm run start:cns

Modules

Microservices

Common Notification Service

alt CNS

File Directory:

CNS-Notification-Record Module (Link to the Swagger locally and UAT)

File Directory:

Notification Service

Overall Architecture of the Notification Service

alt NotificationService

  • CNS architecture mainly comprise of the Notification API service, Message Queues and Workers.

Dead-Letter-Exchange Notification API Module

Responsible for consuming message that are stuck in the queue or unconsume from the worker service

File Directory:

Notification API Modules

File Directory:

How to use the Notification API
  • Firstly, go to my cns-user swagger to generate the API-Key --> localhost:3070

  • In Postman, set up the secret key and post to http://localhost:3000/v1/api/notification-api/email (Email) or http://localhost:3000/v1/api/notification-api/SMS (SMS)

    NotificationAPI

  • Secondly, set up your message body to be like this

    • Email:

      Email Body

    • SMS:

      SMS Body

Worker Service Module

Responsible for consuming messages from the message queues and process the notifications

File Directory:

Library

  • Gurads
    • ApiKey
    • Authentication
    • Authorisation
  • Common
    • Constant
    • Entity
    • DTOs
    • Exception-Filter
    • Interceptors
    • RabbitMQ
  • Config
    • Mongoose Configuration
    • Postgres Configuration

Entities

Postgres

Entities Image

MongoDB

Mongodb Image

💂‍♂️ Guards

Authentication

  • The purpose of the local strategy is to verify and authenticate the credentials of a user during the sign-in process.

  • Example of using local Strategy Guard throughout the application:

    // apps/cns-user/src/user-auth/user-auth.controller.ts
    @Post('signIn')
    @UseGuards(UserAuthGuard)
    async signIn(@Request() req: any) {}
  • The Jwt Strategy is implemented to validate the authentication of users accessing an API, ensuring that only authenticated users are authorized to make API calls.

  • Example of using Jwt Strategy Guard throughout the application:

    // Can be seen across the different controllers
    @Get('listRoles')
    @UseGuards(JwtAuthGuard)
    async listRoles() {}
  • The Refresh Token strategy is designed to handle the renewal of authentication tokens for users, allowing them to obtain new access tokens without requiring them to re-enter their credentials.

  • Example of using Refresh Token Strategy Guard:

    // apps/cns-user/src/user-auth/user-auth.controller.ts
    @Get('refreshToken')
    @UseGuards(RefreshTokenGuard)
    async refreshToken() {}
  • The Reset Password Strategy is to facilitate the process of resetting a user's password when they have forgotten it or need to change it.

  • Example of using Reset Password Strategy Guard:

    // apps/cns-user/src/user-auth/user-auth.controller.ts
    @Patch('resetPassword')
    @UseGuards(ResetPasswordGuard)
    async resetPassword() {}

ApiKey Guard

  • The purpose of the ApiKey Strategy is to authenticate whether a user has the authorization to send emails.

  • Example of using ApiKey Strategy Guard:

    // apps/notification-api/src/modules/email-api/email-api.controller.ts
    @Post('/email')
    @UseGuards(ApiAuthGuard)
    async publishEmail() {}

Authorization

CASL

  • The purpose of CASL is to provide user-centric access control for application. CASL enable developers to define permissions and role for the application. To know more about CASL
  • The purpose is to create the Ability Object for that given user
  • Method:
    async defineAbilitiesFor(user: any)
  • The purpose is to obtains the user information from the incoming request, and creates an ability object using the caslAbilityFactory. It then proceeds to validate each policy handler against the user's abilities.
  • The PoliciesGuard acts as a middleware that enforces policies by checking if the user has the required abilities to access a particular route or resource.
  • Method:
    async canActivate(context: ExecutionContext): Promise<boolean>
  • You are able to use this authorisation guard throughout the application
    • Example:
      // Can be seen across the different controllers
      @Get('GroupUsersByOrganisation')
      @UseGuards(PolicyGuard)
      @CheckPolicies((ability: AppAbility) =>
          ability.can(Operation.Read, 'Organisation'),
      )
      async groupUsersByOrganisation() {}

RabbitMQ serves as a message broker, facilitating the exchange of messages between different components, systems, or services within an application or across multiple applications. To know more about RabbitMQ.

The library I use:

Setting up the connection

onModuleInit() {
    const rabbitmqUri = this.configService.get<string>('RABBITMQ_URI');
    this.connection = connect([rabbitmqUri]);
  • By setting up the connection, you will be able to access the rabbitMQ: localhost:15672 Login Image
  1. Username: guest
  2. Password: guest

    The password can be easily change under the Admin page You can even add user and set its access control

  • Once Access, you can view the entire overview of the rabbitMQ rabbitMQOverview

Setting up the Channel

this.channel = this.connection.createChannel({
    json: true,
    setup: async (channel) => await this.setupRabbitMQ(channel),
    confirm: true,
});

The code I written will automatically create the exchange, queues and simultaneously bind the queue and exchange together

First, create the exchange

await channel.assertExchange(EX_NOTIFICATION, 'direct', {
    durable: true,
});

Exchange

Second, create the queue

await channel.assertQueue(QUEUE_SMS, {
    durable: true,
    arguments: {
        'x-queue-type': 'quorum',
        'x-message-ttl': 10000,
        'x-dead-letter-exchange': DLX_EXCHANGE,
        'x-dead-letter-routing-key': RK_NOTIFICATION_SMS,
        'x-delivery-limit': 5,
    },
});

Queue

You can access the queue to gain more detailed insights and information about the specific queue.

Third, bind the queue to a routing key and the exchange

await channel.bindQueue(
    QUEUE_SMS,
    EX_NOTIFICATION,
    RK_NOTIFICATION_SMS,
);

Publish message to the broker

public async publish(routingkey: string, message: any) {
    const result = await this.channel.publish(
            EX_NOTIFICATION,
            routingkey,
            Buffer.from(JSON.stringify(message)),
            );
    return result;
}

Subscribing to the broker

public async subscribe(queue: string, onMessage: (msg) => void) {
    ...
}

🍂 Env variables

Environment files are avaliable in here

License

This project is licensed under the MIT License.

Authors

The one and only legendary CM 💪

swag