DEV Community

Cover image for How to integrate Alerta into your Business
Vincent Olagbemide
Vincent Olagbemide

Posted on

How to integrate Alerta into your Business

In this post, we'll be creating a Slack channel called #finance where we'll send alerts on the transfer of funds and also send a reply to the same message after the transfer has been delivered.

Prerequisite:

  1. You should be a user of Slack, or have your own slack.com account,
  2. Create Channels for your notifications
  3. Create an Alerta account on (app.usealerta.com)
  4. Create a basic fintech app to plug in our alerts

We are going to use a Sample Fintech app,

Create Slack Channel

On Slack, let's assume we have a #finance team, #marketing team, and #security team.

Create a channel for in slack

We'll go ahead and create a channel for each team and add each employee respectively.

Create Slack Channel for Alerta Integration

After this is done, you should have your channels created and ready to be integrated.

Create Alerta Account

Create an account to get your API key from the Alerta App Here
Create alerta account

After successfully creating an account, save your API key, then switch to the integrations page and select slack

alerta integration page

Select the channel you created from the Slack Oauth page. See below

alerta slack integration oauth page

After successful integration, the channel should be integrated

Alerta integration success

Note: This is how you can add any channels to your alerta account

Create the Sample Fintech App

Now that we have successfully added our channels to the slack workspace.

Let us see how to integrate Alerta into our code base. In case you want o see the full app, See GitHub to skip the step-by-step process

Also, you can watch the Youtube video

Otherwise, let us continue

Installing Nest JS and create a nest app using the following commands

❯ npm i -g @nestjs/cli
❯ nest new sample-fintech-app
❯ cd sample-fintech-app
Enter fullscreen mode Exit fullscreen mode

Let's create the wallet service, module and controller

❯ npm i @nestjs/config @nestjs/axios    
❯ nest g module wallet
❯ nest g service wallet
❯ nest g controller wallet
Enter fullscreen mode Exit fullscreen mode

Let's create the alerta service, module and controller

❯ nest g module alerta
❯ nest g service alerta
Enter fullscreen mode Exit fullscreen mode

Folder API Structure

After running all the commands above, your sample finance app which should have this folder structure, feel free to remove the .spec files.

sample-wallet-app/
├── src/
│   ├── alerta/
│   │   ├── alerta.module.ts        # Module for alerta functions
│   │   └── alerta.service.ts       # for alerta external api calls
│   ├── wallet/
│   │   ├── wallet.controller.ts    # Controller handling wallet
│   │   ├── wallet.module.ts        # Module for Wallet functions
│   │   └── wallet.service.ts       # handling business logic
│   ├── app.module.ts               # Main application module
│   └── main.ts                     # Entry point for the application
├── node_modules/                   # Node dependencies
├── package.json                    # Dependencies
├── package-lock.json               # Lock file extracting version of our dependencies
├── tsconfig.json                   # TypeScript configuration file
└── .eslintrc.js                    # ESLint configuration (optional)
Enter fullscreen mode Exit fullscreen mode

Add Wallet Services

We'll create two functions in our wallet.service.ts file. One to transfer funds and the second to withdraw funds.

import { Injectable } from '@nestjs/common';
import { AlertaService } from 'src/alerta/alerta.service';

@Injectable()
export class WalletService {
  constructor(private readonly alertaService: AlertaService) {}

  async walletTransfer(transferDto: {
    fromWalletID: string;
    toWalletID: string;
    amount: number;
  }) {
    const { fromWalletID, toWalletID, amount } = transferDto;
    const message = `Transferred ${amount} from wallet ${fromWalletID} to wallet ${toWalletID}`;

    return { message, success: true };
  }

  withdrawToBank(withDrawDto: {
    walletID: string;
    bankAccount: string;
    amount: number;
  }) {
    const { walletID, bankAccount, amount } = withDrawDto;
    const message = `Withdrawn ${amount} from wallet ${walletID} to bank account ${bankAccount}`;
    return { message, success: true };
  }
}
Enter fullscreen mode Exit fullscreen mode

Add Wallet Controller

We'll create two functions in our wallet.controller.ts file. One initiates the transfer of funds and the second to withdraws funds.

import { Body, Controller, Post } from '@nestjs/common';
import { WalletService } from './wallet.service';

@Controller('wallet')
export class WalletController {
  constructor(private readonly walletService: WalletService) {}
  @Post('transfer')
  walletTransfer(
    @Body()
    transferDto: {
      fromWalletID: string;
      toWalletID: string;
      amount: number;
    },
  ) {
    return this.walletService.walletTransfer(transferDto);
  }

  @Post('withdraw')
  withdrawToBank(
    @Body()
    withDrawDto: {
      walletID: string;
      bankAccount: string;
      amount: number;
    },
  ) {
    return this.walletService.withdrawToBank(withDrawDto);
  }
}
Enter fullscreen mode Exit fullscreen mode

Now that we have created our service and controller for the wallet service, lets add the module

for wallet.module.ts

import { Module } from '@nestjs/common';
import { WalletController } from './wallet.controller';
import { WalletService } from './wallet.service';

@Module({
  controllers: [WalletController],
  providers: [WalletService],
})
export class WalletModule {}
Enter fullscreen mode Exit fullscreen mode

Add Env Variables

To secure our API keys, we'll add a .env file and specify the api key and url there

ALERTA_URL=place your api url
ALERTA_KEY=place your api key here
Enter fullscreen mode Exit fullscreen mode

Add Alerta Service

After setting up our wallet and our env file, we'll create two functions in our alerta.service.ts file. One to send the message and the second to reply to messages.

import { HttpService } from '@nestjs/axios';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { catchError, lastValueFrom } from 'rxjs';

@Injectable()
export class AlertaService {
  private alertaUrl = this.config.get('ALERTA_URL');
  private alertaKey = this.config.get('ALERTA_KEY');

  constructor(
    private config: ConfigService,
    private httpService: HttpService,
  ) {}
  async sendAlert(alertaDto: {
    message: string;
    channel: string;
    replyTo: boolean;
  }): Promise<any> {
    const { message, channel, replyTo } = alertaDto;

    try {
      const headers = {
        secretKey: `secret ${this.alertaKey}`,
        'Content-Type': 'application/json',
      };

      const res = await lastValueFrom(
        this.httpService
          .post(
            `${this.alertaUrl}/send`,
            { message, channel, replyTo },
            {
              headers,
            },
          )
          .pipe(
            catchError((error) => {
              throw new HttpException(
                `Error fetching data from external API: ${error.message}`,
                HttpStatus.BAD_REQUEST,
              );
            }),
          ),
      );
      return res.data;
      return res.data;
    } catch (error) {
      return error;
    }
  }

  async replyAlert(alertaDto: {
    channelId: string;
    threadId: string;
    channelRef: string;
    message: string;
  }): Promise<any> {
    const { channelId, threadId, channelRef, message } = alertaDto;

    try {
      const headers = {
        secretKey: `secret ${this.alertaKey}`,
        'Content-Type': 'application/json',
      };

      const res = await lastValueFrom(
        this.httpService
          .post(
            `${this.alertaUrl}/reply`,
            { channelId, threadId, channelRef, message },
            { headers },
          )
          .pipe(
            catchError((error) => {
              throw new HttpException(
                `Error processing data from API ${error}`,
                HttpStatus.BAD_REQUEST,
              );
            }),
          ),
      );
      return res.data;
    } catch (error) {}
  }
}
Enter fullscreen mode Exit fullscreen mode

Add Alerta Module

After setting up our alerta service, we'll update our module file alerta.module.ts file.

import { HttpModule } from '@nestjs/axios';
import { Module } from '@nestjs/common';
import { AlertaService } from './alerta.service';
import { ConfigService } from '@nestjs/config';

@Module({
  imports: [HttpModule.register({ timeout: 5000, maxRedirects: 5 })],
  providers: [AlertaService, ConfigService],
  exports: [AlertaService],
})
export class AlertaModule {}
Enter fullscreen mode Exit fullscreen mode

We are almost there,
Before we test, lets us add the alerts to some places in our wallet service so we get the alerts when there is an operation.

First lets update the wallet.module.ts file to know our Alerta service

.
.
.
import { AlertaModule } from 'src/alerta/alerta.module';

@Module({
  imports: [AlertaModule],
  controllers: [WalletController],
  providers: [WalletService],
})
export class WalletModule {}
Enter fullscreen mode Exit fullscreen mode

also, let us update the app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { WalletService } from './wallet/wallet.service';
import { WalletModule } from './wallet/wallet.module';
import { WalletController } from './wallet/wallet.controller';
import { AlertaService } from './alerta/alerta.service';
import { AlertaModule } from './alerta/alerta.module';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { HttpModule } from '@nestjs/axios';

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    WalletModule,
    AlertaModule,
    HttpModule,
  ],
  controllers: [AppController],
  providers: [AppService, ConfigService],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

You will notice that we added the ConfigModule.forRoot({ isGlobal: true }) this will ensure that all env config is availble and can be used app-wide.

Plug in Alerta into the codebase

Now, in the wallet.service.ts, let us assume we want to know when any user does a transfer or does a withdraw, we can send a message to the #finance channel that we created and integrated earlier.
Lets update our code.

import { Injectable } from '@nestjs/common';
import { AlertaService } from 'src/alerta/alerta.service';

@Injectable()
export class WalletService {
// bring the service in by adding it the contructor
  constructor(private readonly alertaService: AlertaService) {}

  async walletTransfer(transferDto: {
    fromWalletID: string;
    toWalletID: string;
    amount: number;
  }) {
    const { fromWalletID, toWalletID, amount } = transferDto;
    const message = `Transferred ${amount} from wallet ${fromWalletID} to wallet ${toWalletID}`;

// Add the alert from the alerta service here. Also, because we want 
// to reply to it later, we'll set `replyTo` to true
    await this.alertaService.sendAlert({
      message,
      channel: 'finance',
      replyTo: true,
    });

    return { message, success: true };
  }
.
.
.

}
Enter fullscreen mode Exit fullscreen mode

Now we can send messages to Slack in the channel. Let's assume we want to reply to the same message that was sent earlier.
Usually, you can save the data from the response after you have sent a message so the message can be replied to in the future.

The data needed for the reply is threadId, channelId and channelRef

To add the Reply

Before adding the reply feature, we have to mention the @Alerta App in the channel

Add the bot to the channel

import { Injectable } from '@nestjs/common';
import { AlertaService } from 'src/alerta/alerta.service';

@Injectable()
export class WalletService {
  constructor(private readonly alertaService: AlertaService) {}

  async walletTransfer(transferDto: {
    fromWalletID: string;
    toWalletID: string;
    amount: number;
  }) {
    const { fromWalletID, toWalletID, amount } = transferDto;
    const message = `Transferred ${amount} from wallet ${fromWalletID} to wallet ${toWalletID}`;

    const alertRes = await this.alertaService.sendAlert({
      message,
      channel: 'finance',
      replyTo: true,
    });

// triggering the reply
    setTimeout(() => {
      this.alertaService.replyAlert({
        message: 'Transfer delivered!',
        threadId: alertRes.data.replyData.threadId,
        channelId: alertRes.data.replyData.channelId,
        channelRef: alertRes.data.replyData.channelRef,
      });
    }, 5000);

    return { message, success: true };
  }

  withdrawToBank(withDrawDto: {
    walletID: string;
    bankAccount: string;
    amount: number;
  }) {
    const { walletID, bankAccount, amount } = withDrawDto;
    const message = `Withdrawn ${amount} from wallet ${walletID} to bank account ${bankAccount}`;
    return { message, success: true };
  }
}

Enter fullscreen mode Exit fullscreen mode

Now let's test our implementation

Test transfer

Postman test of integration for alerta

The Slack Message

send message to slack

The Reply to the existing message

Reply to exiting message

In conclusion, we created a Slack channel called #finance where we'll send updates messages on the transfer of funds and also send a reply to the same message after the transfer has been delivered.

Thank you.

Top comments (1)

Collapse
 
adeyinka_adewusi_b03a6c69 profile image
Adeyinka Adewusi

Impressive