diff --git a/services/notification-service/README.md b/services/notification-service/README.md index 2f191937b2..0811e9cabb 100644 --- a/services/notification-service/README.md +++ b/services/notification-service/README.md @@ -1,7 +1,6 @@ # notification-service -[![LoopBack](https://github.com/strongloop/loopback-next/raw/master/docs/site/imgs/branding/Powered-by-LoopBack-Badge-(blue)-@2x.png)](http://loopback.io/) - +[![LoopBack]()](http://loopback.io/) ## Overview @@ -15,235 +14,245 @@ npm i @sourceloop/notification-service ### Usage - - Create a new Loopback4 Application (If you don't have one already) - `lb4 testapp` - - Install the notification service - `npm i @sourceloop/notification-service` - - Install the loopback4-notifications module - - `npm i loopback4-notifications` - - Set the [environment variables](#environment-variables). - - Run the [migrations](#migrations). - - Add the `NotificationServiceComponent` to your Loopback4 Application (in `application.ts`). - ``` typescript - // add Component for AuthenticationService - this.component(NotificationServiceComponent); - ``` - - Set up a [Loopback4 Datasource](https://loopback.io/doc/en/lb4/DataSource.html) with `dataSourceName` property set to - `NotifDbSourceName`. You can see an example datasource [here](#setting-up-a-datasource). - - **Email Notifications with Amazon SES** - - - Bind the SES Config to the `SESBindings.Config` key - - ``` typescript - this.bind(SESBindings.Config).to({ - accessKeyId: process.env.SES_ACCESS_KEY_ID, - secretAccessKey: process.env.SES_SECRET_ACCESS_KEY, - region: process.env.SES_REGION, - }); - ``` - - Implement an SES Provider(refer [this](https://github.com/sourcefuse/loopback4-notifications/tree/master/src/providers/email/ses)) or you can import the default SES provider from the [loopback4-notifications](https://www.npmjs.com/package/loopback4-notifications) module and bind it to the `NotificationBindings.EmailProvider` key as described [here](https://github.com/sourcefuse/loopback4-notifications#email-notifications). - - - **Email Notifications with Nodemailer** - - - Bind the Nodemailer Config to the `NodemailerBindings.Config` key - - ``` typescript - this.bind(NodemailerBindings.Config).to({ - pool: true, - maxConnections: 100, - url:"", - host: "smtp.example.com", - port: 80, - secure: false, - auth: { - user: "username", - pass: "password" - }, - tls: { - rejectUnauthorized: true - } - }); - ``` - - Implement a Nodemailer Provider(refer [this](https://github.com/sourcefuse/loopback4-notifications/tree/master/src/providers/email/nodemailer)) or import the default Nodemailer provider from the [loopback4-notifications](https://www.npmjs.com/package/loopback4-notifications) module and bind it to the `NotificationBindings.EmailProvider` key as described [here](https://github.com/sourcefuse/loopback4-notifications#email-notifications), - - - **SMS Notification with Amazon SNS** - - - Bind the SNS Config to the `SNSBindings.Config` key - - ``` typescript - this.bind(SNSBindings.Config).to({ - accessKeyId: process.env.SNS_ACCESS_KEY_ID, - secretAccessKey: process.env.SNS_SECRET_ACCESS_KEY, - region: process.env.SNS_REGION, - }); - ``` - - Implement an SnsProvider(refer [this](https://github.com/sourcefuse/loopback4-notifications/tree/master/src/providers/sms/sns)) or import the default SNS provider from the [loopback4-notifications](https://www.npmjs.com/package/loopback4-notifications) module and bind it to the `NotificationBindings.SMSProvider` key - - ``` typescript - import { - NotificationBindings, - SnsProvider // or your own provider - } from 'loopback4-notifications'; - ... - this.bind(NotificationBindings.SMSProvider).toProvider(SnsProvider); - ... - ``` - - - **Push Notifications with Pubnub** - - - Bind the Pubnub Config to the `PubNubProvider.Config` key - - ``` typescript - this.bind(PubnubBindings.Config).to({ - subscribeKey: process.env.PUBNUB_SUBSCRIBE_KEY, - publishKey: process.env.PUBNUB_PUBLISH_KEY, - secretKey: process.env.PUBNUB_SECRET_KEY, - ssl: true, - logVerbosity: true, - uuid: 'my-app', - cipherKey: process.env.PUBNUB_CIPHER_KEY, - apns2Env: 'production', - apns2BundleId: 'com.app.myapp' - }); - ``` - - Implement a Pubnub Provider(refer [this](https://github.com/sourcefuse/loopback4-notifications/tree/master/src/providers/push/pubnuba)) or import the default Pubnub provider from the [loopback4-notifications](https://www.npmjs.com/package/loopback4-notifications) module and bind it to the `NotificationBindings.PushProvider` key - - ``` typescript - import { - NotificationBindings, - PubNubProvider //or your own provider - } from 'loopback4-notifications'; - ... - this.bind(NotificationBindings.PushProvider).toProvider(PubNubProvider); - ... - ``` - - **Push Notifications with Socket.io** - - - Bind the Socket.io Config to the `SocketBindings.Config` key - - ``` typescript - this.bind(SocketBindings.Config).to({ - url: process.env.SOCKETIO_SERVER_URL - }); - ``` - - Implement a SocketIO Provider(refer [this](https://github.com/sourcefuse/loopback4-notifications/tree/master/src/providers/push/socketio)) or import the default Socket.io provider from the [loopback4-notifications](https://www.npmjs.com/package/loopback4-notifications) module and bind it to the `NotificationBindings.PushProvider` key - - ``` typescript - import { - NotificationBindings, - SocketIOProvider // or your own provider - } from 'loopback4-notifications'; - ... - this.bind(NotificationBindings.PushProvider).toProvider(SocketIOProvider); - ... - ``` - - Start the application - `npm start` +- Create a new Loopback4 Application (If you don't have one already) + `lb4 testapp` +- Install the notification service + `npm i @sourceloop/notification-service` +- Install the loopback4-notifications module - + `npm i loopback4-notifications` +- Set the [environment variables](#environment-variables). +- Run the [migrations](#migrations). +- Add the `NotificationServiceComponent` to your Loopback4 Application (in `application.ts`). + ```typescript + // add Component for AuthenticationService + this.component(NotificationServiceComponent); + ``` +- Set up a [Loopback4 Datasource](https://loopback.io/doc/en/lb4/DataSource.html) with `dataSourceName` property set to + `NotifDbSourceName`. You can see an example datasource [here](#setting-up-a-datasource). +- **Email Notifications with Amazon SES** - + + - Bind the SES Config to the `SESBindings.Config` key - + + ```typescript + this.bind(SESBindings.Config).to({ + accessKeyId: process.env.SES_ACCESS_KEY_ID, + secretAccessKey: process.env.SES_SECRET_ACCESS_KEY, + region: process.env.SES_REGION, + }); + ``` + + - Implement an SES Provider(refer [this](https://github.com/sourcefuse/loopback4-notifications/tree/master/src/providers/email/ses)) or you can import the default SES provider from the [loopback4-notifications](https://www.npmjs.com/package/loopback4-notifications) module and bind it to the `NotificationBindings.EmailProvider` key as described [here](https://github.com/sourcefuse/loopback4-notifications#email-notifications). + +- **Email Notifications with Nodemailer** - + + - Bind the Nodemailer Config to the `NodemailerBindings.Config` key - + + ```typescript + this.bind(NodemailerBindings.Config).to({ + pool: true, + maxConnections: 100, + url: '', + host: 'smtp.example.com', + port: 80, + secure: false, + auth: { + user: 'username', + pass: 'password', + }, + tls: { + rejectUnauthorized: true, + }, + }); + ``` + + - Implement a Nodemailer Provider(refer [this](https://github.com/sourcefuse/loopback4-notifications/tree/master/src/providers/email/nodemailer)) or import the default Nodemailer provider from the [loopback4-notifications](https://www.npmjs.com/package/loopback4-notifications) module and bind it to the `NotificationBindings.EmailProvider` key as described [here](https://github.com/sourcefuse/loopback4-notifications#email-notifications), + +- **SMS Notification with Amazon SNS** - + - Bind the SNS Config to the `SNSBindings.Config` key - + ```typescript + this.bind(SNSBindings.Config).to({ + accessKeyId: process.env.SNS_ACCESS_KEY_ID, + secretAccessKey: process.env.SNS_SECRET_ACCESS_KEY, + region: process.env.SNS_REGION, + }); + ``` + - Implement an SnsProvider(refer [this](https://github.com/sourcefuse/loopback4-notifications/tree/master/src/providers/sms/sns)) or import the default SNS provider from the [loopback4-notifications](https://www.npmjs.com/package/loopback4-notifications) module and bind it to the `NotificationBindings.SMSProvider` key - + ```typescript + import { + NotificationBindings, + SnsProvider // or your own provider + } from 'loopback4-notifications'; + ... + this.bind(NotificationBindings.SMSProvider).toProvider(SnsProvider); + ... + ``` +- **Push Notifications with Pubnub** - + - Bind the Pubnub Config to the `PubNubProvider.Config` key - + ```typescript + this.bind(PubnubBindings.Config).to({ + subscribeKey: process.env.PUBNUB_SUBSCRIBE_KEY, + publishKey: process.env.PUBNUB_PUBLISH_KEY, + secretKey: process.env.PUBNUB_SECRET_KEY, + ssl: true, + logVerbosity: true, + uuid: 'my-app', + cipherKey: process.env.PUBNUB_CIPHER_KEY, + apns2Env: 'production', + apns2BundleId: 'com.app.myapp', + }); + ``` + - Implement a Pubnub Provider(refer [this](https://github.com/sourcefuse/loopback4-notifications/tree/master/src/providers/push/pubnuba)) or import the default Pubnub provider from the [loopback4-notifications](https://www.npmjs.com/package/loopback4-notifications) module and bind it to the `NotificationBindings.PushProvider` key - + ```typescript + import { + NotificationBindings, + PubNubProvider //or your own provider + } from 'loopback4-notifications'; + ... + this.bind(NotificationBindings.PushProvider).toProvider(PubNubProvider); + ... + ``` +- **Push Notifications with Socket.io** - + - Bind the Socket.io Config to the `SocketBindings.Config` key - + ```typescript + this.bind(SocketBindings.Config).to({ + url: process.env.SOCKETIO_SERVER_URL, + }); + ``` + - Implement a SocketIO Provider(refer [this](https://github.com/sourcefuse/loopback4-notifications/tree/master/src/providers/push/socketio)) or import the default Socket.io provider from the [loopback4-notifications](https://www.npmjs.com/package/loopback4-notifications) module and bind it to the `NotificationBindings.PushProvider` key - + ```typescript + import { + NotificationBindings, + SocketIOProvider // or your own provider + } from 'loopback4-notifications'; + ... + this.bind(NotificationBindings.PushProvider).toProvider(SocketIOProvider); + ... + ``` +- Start the application + `npm start` ### Create Notification Payload Structures #### Email Notification with SES -``` typescript - interface SESMessage { - receiver: { - to: { - id: string; //Email address - name?: string; - }[]; - }, - subject: string; - body: string; - sentDate: Date; - type: 1; //Email - options?: { - fromEmail: string, // We do not support attachments with SES Provider yet. - }; - } +```typescript +interface SESMessage { + receiver: { + to: { + id: string; //Email address + name?: string; + }[]; + }; + subject: string; + body: string; + sentDate: Date; + type: 1; //Email + options?: { + fromEmail: string; // We do not support attachments with SES Provider yet. + }; +} ``` + #### Email Notification with Nodemailer -``` typescript - interface NodemailerMessage { - receiver: { - to: { - id: string; //Email address - name?: string; - }[]; - }, - subject: string; - body: string; - sentDate: Date; - type: 1; //Email - options?: { - from: string, - subject?: string, - text?: string, - html?: string, - attachments?: { - filename?: string | false; - cid?: string; - encoding?: string; - contentType?: string; - contentTransferEncoding?: '7bit' | 'base64' | 'quoted-printable' | false; - contentDisposition?: 'attachment' | 'inline'; - headers?: Headers; - raw?: string | Buffer | Readable | { - content?: string | Buffer | Readable; - path?: string | Url; - }; - }[] - }; - } +```typescript +interface NodemailerMessage { + receiver: { + to: { + id: string; //Email address + name?: string; + }[]; + }; + subject: string; + body: string; + sentDate: Date; + type: 1; //Email + options?: { + from: string; + subject?: string; + text?: string; + html?: string; + attachments?: { + filename?: string | false; + cid?: string; + encoding?: string; + contentType?: string; + contentTransferEncoding?: '7bit' | 'base64' | 'quoted-printable' | false; + contentDisposition?: 'attachment' | 'inline'; + headers?: Headers; + raw?: + | string + | Buffer + | Readable + | { + content?: string | Buffer | Readable; + path?: string | Url; + }; + }[]; + }; +} ``` #### SMS Notification with SNS -``` typescript - interface SMSMessage { - receiver: { - to: { - id: string; // TopicArn or PhoneNumber - name?: string; - }[] - }; - subject: undefined; - body: string; - sentDate: Date; - type: 2; //SMS - options?: { - messageType: any, - }; - } +```typescript +interface SMSMessage { + receiver: { + to: { + id: string; // TopicArn or PhoneNumber + name?: string; + }[]; + }; + subject: undefined; + body: string; + sentDate: Date; + type: 2; //SMS + options?: { + messageType: any; + }; +} ``` #### Push Notification with Pubunb -``` typescript - interface PubNubMessage { - receiver: { - to: { - type: 0; //Channel Type - id: string; //Channel identifier - name?: string; - }[]; - }; - subject: string; - body: string; - sentDate: Date; - type: 0; //Push Notification - options?: { - sound: string, - }; - } +```typescript +interface PubNubMessage { + receiver: { + to: { + type: 0; //Channel Type + id: string; //Channel identifier + name?: string; + }[]; + }; + subject: string; + body: string; + sentDate: Date; + type: 0; //Push Notification + options?: { + sound: string; + }; +} ``` #### Push Notification with Socket.io -``` typescript - interface SocketMessage { - receiver: { - to: { - type: 0; //Channel Type - id: string; //Channel identifier - name?: string; - }[]; - }; - subject: string; - body: string; - sentDate: Date; - type: 0; //Push Notification - options?: { - path?: string, - }; - } +```typescript +interface SocketMessage { + receiver: { + to: { + type: 0; //Channel Type + id: string; //Channel identifier + name?: string; + }[]; + }; + subject: string; + body: string; + sentDate: Date; + type: 0; //Push Notification + options?: { + path?: string; + }; +} ``` ### Environment Variables @@ -263,7 +272,7 @@ npm i @sourceloop/notification-service ### Setting up a `DataSource` -Here is a sample Implementation `DataSource` implementation using environment variables and PostgreSQL as the data source. +Here is a sample Implementation `DataSource` implementation using environment variables and PostgreSQL as the data source. ```typescript import {inject, lifeCycleObserver, LifeCycleObserver} from '@loopback/core'; @@ -283,8 +292,10 @@ const config = { }; @lifeCycleObserver('datasource') -export class NotificationDbDataSource extends juggler.DataSource - implements LifeCycleObserver { +export class NotificationDbDataSource + extends juggler.DataSource + implements LifeCycleObserver +{ static dataSourceName = NotifDbSourceName; static readonly defaultConfig = config; @@ -298,6 +309,52 @@ export class NotificationDbDataSource extends juggler.DataSource } ``` +### Blacklisting Users + +Here is a sample implementation of how we can blacklist a user(s). + +Create a new provider: + +```typescript +import {BindingScope, injectable, Provider} from '@loopback/core'; +import { + INotificationFilterFunc, + Notification, +} from '@sourceloop/notification-service'; + +@injectable({scope: BindingScope.TRANSIENT}) +export class NotificationfilterProvider + implements Provider +{ + constructor() {} + + value() { + return async (notif: Notification) => this.notificationFilterFunc(notif); + } + + blacklistedUsers = ['id1', 'id2']; //ID's of blacklisted users + + notificationFilterFunc(notif: Notification) { + const receivers = notif!.receiver.to; + const result = receivers.filter(receiver => { + return this.blacklistedUsers.indexOf(receiver.id) === -1; + }); + notif.receiver.to = result; + return notif; + } +} +``` + +Add the following line to `src/application.ts` file + +```typescript +this.bind(NotifServiceBindings.NotificationFilter).toProvider( + NotificationfilterProvider, +); +``` + +Note: One can modify the provider according to the requirements + ### Migrations The migrations required for this service are processed during the installation automatically if you set the `NOTIF_MIGRATION` or `SOURCELOOP_MIGRATION` env variable. The migrations use [`db-migrate`](https://www.npmjs.com/package/db-migrate) with [`db-migrate-pg`](https://www.npmjs.com/package/db-migrate-pg) driver for migrations, so you will have to install these packages to use auto-migration. Please note that if you are using some pre-existing migrations or database, they may be effected. In such scenario, it is advised that you copy the migration files in your project root, using the `NOTIF_MIGRATION_COPY` or `SOURCELOOP_MIGRATION_COPY` env variables. You can customize or cherry-pick the migrations in the copied files according to your specific requirements and then apply them to the DB. diff --git a/services/notification-service/src/component.ts b/services/notification-service/src/component.ts index e35021fc8d..3c042589d1 100644 --- a/services/notification-service/src/component.ts +++ b/services/notification-service/src/component.ts @@ -38,7 +38,7 @@ import { } from './controllers'; import {NotifServiceBindings} from './keys'; import {Notification, NotificationAccess, NotificationUser} from './models'; -import {ChannelManagerProvider} from './providers'; +import {NotificationFilterProvider, ChannelManagerProvider} from './providers'; import {NotificationUserProvider} from './providers/notification-user.service'; import { NotificationAccessRepository, @@ -112,6 +112,7 @@ export class NotificationServiceComponent implements Component { [NotifServiceBindings.ChannelManager.key]: ChannelManagerProvider, [NotifServiceBindings.NotificationUserManager.key]: NotificationUserProvider, + [NotifServiceBindings.NotificationFilter.key]: NotificationFilterProvider, }; this.controllers = [ diff --git a/services/notification-service/src/controllers/notification.controller.ts b/services/notification-service/src/controllers/notification.controller.ts index 59b35fd518..c19fd2909a 100644 --- a/services/notification-service/src/controllers/notification.controller.ts +++ b/services/notification-service/src/controllers/notification.controller.ts @@ -34,7 +34,7 @@ import { NotificationRepository, NotificationUserRepository, } from '../repositories'; -import {INotificationUserManager} from '../types'; +import {INotificationFilterFunc, INotificationUserManager} from '../types'; const basePath = '/notifications'; const maxBodyLen = 1000; @@ -48,6 +48,8 @@ export class NotificationController { public notificationUserRepository: NotificationUserRepository, @inject(NotifServiceBindings.NotificationUserManager) private readonly notifUserService: INotificationUserManager, + @inject(NotifServiceBindings.NotificationFilter) + private readonly filterNotification: INotificationFilterFunc, ) {} @authenticate(STRATEGY.BEARER) @@ -73,6 +75,7 @@ export class NotificationController { }) notification: Omit, ): Promise { + notification = await this.filterNotification(notification); const provider = await this.notifProvider(); await provider.publish(notification); if (notification.body.length > maxBodyLen) { diff --git a/services/notification-service/src/keys.ts b/services/notification-service/src/keys.ts index acafdeb437..e8ddd1db13 100644 --- a/services/notification-service/src/keys.ts +++ b/services/notification-service/src/keys.ts @@ -2,6 +2,7 @@ import {BindingKey} from '@loopback/core'; import {BINDING_PREFIX} from '@sourceloop/core'; import { IChannelManager, + INotificationFilterFunc, INotificationUserManager, INotifServiceConfig, } from './types'; @@ -17,4 +18,9 @@ export namespace NotifServiceBindings { BindingKey.create( `${BINDING_PREFIX}.notification.notifUserMgr`, ); + + export const NotificationFilter = + BindingKey.create( + `${BINDING_PREFIX}.notification.notificationFilter`, + ); } diff --git a/services/notification-service/src/providers/index.ts b/services/notification-service/src/providers/index.ts index c078bcb228..074e94e649 100644 --- a/services/notification-service/src/providers/index.ts +++ b/services/notification-service/src/providers/index.ts @@ -1 +1,2 @@ export * from './channel-manager.service'; +export * from './notification-filter.service'; diff --git a/services/notification-service/src/providers/notification-filter.service.ts b/services/notification-service/src/providers/notification-filter.service.ts new file mode 100644 index 0000000000..f849488786 --- /dev/null +++ b/services/notification-service/src/providers/notification-filter.service.ts @@ -0,0 +1,16 @@ +import {bind, BindingScope, Provider} from '@loopback/core'; +import {Notification} from '../models'; +import {INotificationFilterFunc} from '../types'; + +@bind({scope: BindingScope.TRANSIENT}) +export class NotificationFilterProvider + implements Provider +{ + constructor() { + // do nothing + } + + value() { + return async (notif: Notification) => notif; + } +} diff --git a/services/notification-service/src/types.ts b/services/notification-service/src/types.ts index b7581641f0..84963b3b7d 100644 --- a/services/notification-service/src/types.ts +++ b/services/notification-service/src/types.ts @@ -19,5 +19,9 @@ export interface INotifServiceConfig extends IServiceConfig { useCustomPushProvider: boolean; } +export type INotificationFilterFunc = ( + notif: Notification, +) => Promise; + export const NotifDbSourceName = 'NotifDb'; export const NotifAccessCacheSourceName = 'NotifAccessCache';