Skip to content
This repository was archived by the owner on Aug 7, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/channels/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export * from './fcm';
export * from './mail';
export * from './slack';
35 changes: 35 additions & 0 deletions lib/channels/mail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {
NotificationChannel,
NotificationReport,
NotificationTemplate,
Notifiable,
} from '../generics';
import { Mailman } from '@squareboat/nest-mailman';

export class MailChannel extends NotificationChannel {
async send(
notifiable: Notifiable,
notification: NotificationTemplate
): Promise<NotificationReport> {
if (!('toMail' in notification)) {
return new NotificationReport(false);
}

const mailRecipients = notifiable.getMailRecipients();
const notificationData = await notification['toMail']();
const { subject, payload, view } = notificationData;
if (!view) return new NotificationReport(false);

try {
await Mailman.init()
.to(mailRecipients)
.subject(subject)
.view(view, payload)
.send();

return new NotificationReport(true);
} catch (err) {
return new NotificationReport(false);
}
}
}
34 changes: 34 additions & 0 deletions lib/channels/slack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {
NotificationChannel,
NotificationReport,
NotificationTemplate,
Notifiable,
} from '../generics';
import { MessageOptions, SlackMessaging } from '../internal/slack';

export class SlackChannel extends NotificationChannel {
async send(
notifiable: Notifiable,
notification: NotificationTemplate
): Promise<NotificationReport> {

if (!('toSlack' in notification)) {
return new NotificationReport(false);
}

const slackChannel = notifiable.getSlackChannel!();
const notificationData = await notification['toSlack']();
if (!slackChannel) return new NotificationReport(false);

try {
await SlackMessaging.init()
.to(slackChannel)
.options(<MessageOptions>notificationData)
.send();

return new NotificationReport(true);
} catch (err) {
return new NotificationReport(false);
}
}
}
4 changes: 3 additions & 1 deletion lib/generics/notifiable.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export interface Notifiable {
getFcmTokens(): string | string[];
getFcmTokens?(): string | string[];
getMailRecipients?(): string | string[];
getSlackChannel?(): string;
}
1 change: 1 addition & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './generics';
export * from './channels';
export * from './interfaces';
export * from './notification';
export * from './internal/slack'
9 changes: 7 additions & 2 deletions lib/interfaces/options.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { ModuleMetadata, Type } from '@nestjs/common/interfaces';

import { MailmanOptions } from '@squareboat/nest-mailman/dist/interfaces';
import { SlackOptions } from '../internal/slack';

export interface NotificationOptions {
channels: {
fcm: {
fcm?: {
credentialsPath: string
}
},
mail?: MailmanOptions,
slack?: SlackOptions,
};
}

Expand Down
3 changes: 3 additions & 0 deletions lib/internal/slack/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const SLACK_QUEUE = 'SLACK_QUEUE';
export const SLACK_OPTIONS = 'SLACK_OPTIONS';
export const SEND_SLACK_MESSAGE='SEND_SLACK_MESSAGE';
4 changes: 4 additions & 0 deletions lib/internal/slack/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './module';
export * from './service';
export * from './slack';
export * from './interfaces';
2 changes: 2 additions & 0 deletions lib/internal/slack/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './options';
export * from './messages';
22 changes: 22 additions & 0 deletions lib/internal/slack/interfaces/messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export interface MessageBasics {
channel: string;
}

export interface MessageToken {
token: string;
}

interface WithText {
text: string;
}

interface WithAttachments {
attachments: Record<string, any>;
}

interface WithBlocks{
blocks: Record<string, any>;
}

// Atleast one of ['text', 'blocks', 'attachments' ] has to be defined in order to post message to slack
export type MessageOptions = WithText | WithBlocks | WithAttachments;
17 changes: 17 additions & 0 deletions lib/internal/slack/interfaces/options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ModuleMetadata, Type } from '@nestjs/common/interfaces';

export interface SlackOptions {
token: string;
}

export interface SlackOptionsFactory {
createSlackOptions(): Promise<SlackOptions> | SlackOptions;
}

export interface SlackAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
name?: string;
useExisting?: Type<SlackOptions>;
useClass?: Type<SlackOptionsFactory>;
useFactory?: (...args: any[]) => Promise<SlackOptions> | SlackOptions;
inject?: any[];
}
80 changes: 80 additions & 0 deletions lib/internal/slack/module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { Module, DynamicModule, Provider, Type } from '@nestjs/common';
import { SlackService } from './service';
import {
SlackOptions,
SlackAsyncOptions,
SlackOptionsFactory,
} from './interfaces';
import { BullModule } from '@nestjs/bull';
import { SLACK_QUEUE, SLACK_OPTIONS } from './constants';
import { SlackConsumer } from './queue/consumer';

@Module({
imports: [
BullModule.registerQueue({
name: SLACK_QUEUE,
defaultJobOptions: {
removeOnComplete: true,
removeOnFail: true,
},
}),
],
})
export class SlackModule {
/**
* Register options
* @param options
*/
static register(options: SlackOptions): DynamicModule {
return {
global: true,
module: SlackModule,
providers: [
SlackService,
SlackConsumer,
{
provide: SLACK_OPTIONS,
useValue: options,
},
],
};
}

/**
* Register Async Options
*/
static registerAsync(options: SlackAsyncOptions): DynamicModule {
return {
global: true,
module: SlackModule,
providers: [
SlackConsumer,
SlackService,
this.createStorageOptionsProvider(options),
],
};
}

private static createStorageOptionsProvider(
options: SlackAsyncOptions,
): Provider {
if (options.useFactory) {
return {
provide: SLACK_OPTIONS,
useFactory: options.useFactory,
inject: options.inject || [],
};
}

const inject = [
(options.useClass || options.useExisting) as Type<SlackOptions>,
];

return {
provide: SLACK_OPTIONS,
useFactory: async (optionsFactory: SlackOptionsFactory) =>
await optionsFactory.createSlackOptions(),
inject,
};
}
}
34 changes: 34 additions & 0 deletions lib/internal/slack/queue/consumer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Job } from 'bull';
import {
Processor,
Process,
OnQueueActive,
OnQueueCompleted,
} from '@nestjs/bull';
import { Injectable } from '@nestjs/common';
import { SLACK_QUEUE, SEND_SLACK_MESSAGE } from '../constants';
import { SlackService } from '../service';

@Injectable()
@Processor(SLACK_QUEUE)
export class SlackConsumer {
@OnQueueActive()
onActive(job: Job) {
console.log(`🧑‍🏭 ${SLACK_QUEUE} [${job.id}] :::: 📧 Processing `);
}

@OnQueueCompleted()
onComplete(job: Job) {
console.log(`🧑‍🏭 ${SLACK_QUEUE} [${job.id}] :::: 📧 Processed`);
}

@Process(SEND_SLACK_MESSAGE)
async sendMessage(job: Job<any>): Promise<any> {
try {
await SlackService.send(job['data']);
return true;
} catch(Err) {
return false;
}
}
}
35 changes: 35 additions & 0 deletions lib/internal/slack/service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Injectable, Inject } from '@nestjs/common';
import { Queue } from 'bull';
import { InjectQueue } from '@nestjs/bull';
import { WebClient } from '@slack/web-api';

import { SlackOptions } from './interfaces';
import { SEND_SLACK_MESSAGE, SLACK_OPTIONS, SLACK_QUEUE } from './constants';

@Injectable()
export class SlackService {
private static options: SlackOptions;
private static queueProvider: any;
private static client: any;

constructor(
@Inject(SLACK_OPTIONS) private options: SlackOptions,
@InjectQueue(SLACK_QUEUE) queueProvider: Queue
) {
SlackService.options = options;
SlackService.queueProvider = queueProvider;
SlackService.client = new WebClient(options.token);
}

static getConfig(): SlackOptions {
return SlackService.options;
}

static queue(options: Record<string, any>) {
SlackService.queueProvider.add(SEND_SLACK_MESSAGE, options);
}

static async send(options: Record<string, any>) {
await SlackService.client.chat.postMessage(options);
}
}
35 changes: 35 additions & 0 deletions lib/internal/slack/slack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { SlackService } from './service';

export class SlackMessaging {

private messageChannel: string | undefined;
private messageOptions: Record<string, any> | undefined;

to(channel: string): this {
this.messageChannel = channel;
return this;
}

options(options: Record<string, any> ): this {
this.messageOptions = options;
return this;
}

async send() {
await SlackService.send({
channel: this.messageChannel,
...this.messageOptions,
});
}

queue() {
SlackService.queue({
channel: this.messageChannel,
options: this.messageOptions,
});
}

static init() {
return new SlackMessaging();
}
}
Loading