Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BHBC-1949: Email notification to Users that access has been granted or denied #829

Merged
merged 10 commits into from
Oct 4, 2022
Merged
2 changes: 1 addition & 1 deletion api/src/constants/notifications.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IgcNotifyGenericMessage } from '../models/gcnotify';
import { IgcNotifyGenericMessage } from '../services/gcnotify-service';

//admin email template for new access requests
export const ACCESS_REQUEST_ADMIN_EMAIL: IgcNotifyGenericMessage = {
Expand Down
16 changes: 0 additions & 16 deletions api/src/models/gcnotify.ts

This file was deleted.

8 changes: 0 additions & 8 deletions api/src/models/user.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
import { getLogger } from '../utils/logger';

const defaultLog = getLogger('models/user');

export class UserObject {
id: number;
user_identifier: string;
Expand All @@ -10,8 +6,6 @@ export class UserObject {
role_names: string[];

constructor(obj?: any) {
defaultLog.debug({ label: 'UserObject', message: 'params', obj });

this.id = obj?.system_user_id || null;
this.user_identifier = obj?.user_identifier || null;
this.record_end_date = obj?.record_end_date || null;
Expand All @@ -27,8 +21,6 @@ export class ProjectUserObject {
project_role_names: string[];

constructor(obj?: any) {
defaultLog.debug({ label: 'ProjectUserObject', message: 'params', obj });

this.project_id = obj?.project_id || null;
this.system_user_id = obj?.system_user_id || null;
this.project_role_ids = (obj?.project_role_ids?.length && obj.project_role_ids) || [];
Expand Down
120 changes: 0 additions & 120 deletions api/src/paths/gcnotify/send.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import chai, { expect } from 'chai';
import { describe } from 'mocha';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import { HTTPError } from '../../errors/custom-error';
import { getRequestHandlerMocks } from '../../__mocks__/db';
import * as notify from './send';

Expand Down Expand Up @@ -43,125 +42,6 @@ describe('gcnotify', () => {
}
};

it('should throw a 400 error when no req body', async () => {
const { mockReq, mockRes, mockNext } = getRequestHandlerMocks();

mockReq.params = sampleReq.params;
mockReq.body = null;

try {
const requestHandler = notify.sendNotification();

await requestHandler(mockReq, mockRes, mockNext);
expect.fail();
} catch (actualError) {
expect((actualError as HTTPError).status).to.equal(400);
expect((actualError as HTTPError).message).to.equal('Missing required param: body');
}
});

it('should throw a 400 error when no recipient', async () => {
const { mockReq, mockRes, mockNext } = getRequestHandlerMocks();

mockReq.params = sampleReq.params;
mockReq.body = { ...sampleReq.body, recipient: null };

try {
const requestHandler = notify.sendNotification();

await requestHandler(mockReq, mockRes, mockNext);
expect.fail();
} catch (actualError) {
expect((actualError as HTTPError).status).to.equal(400);
expect((actualError as HTTPError).message).to.equal('Missing required body param: recipient');
}
});

it('should throw a 400 error when no message', async () => {
const { mockReq, mockRes, mockNext } = getRequestHandlerMocks();

mockReq.params = sampleReq.params;
mockReq.body = { ...sampleReq.body, message: null };

try {
const requestHandler = notify.sendNotification();

await requestHandler(mockReq, mockRes, mockNext);
expect.fail();
} catch (actualError) {
expect((actualError as HTTPError).status).to.equal(400);
expect((actualError as HTTPError).message).to.equal('Missing required body param: message');
}
});

it('should throw a 400 error when no message.header', async () => {
const { mockReq, mockRes, mockNext } = getRequestHandlerMocks();

mockReq.params = sampleReq.params;
mockReq.body = { ...sampleReq.body, message: { ...sampleReq.body.message, header: null } };

try {
const requestHandler = notify.sendNotification();

await requestHandler(mockReq, mockRes, mockNext);
expect.fail();
} catch (actualError) {
expect((actualError as HTTPError).status).to.equal(400);
expect((actualError as HTTPError).message).to.equal('Missing required body param: message.header');
}
});

it('should throw a 400 error when no message.body1', async () => {
const { mockReq, mockRes, mockNext } = getRequestHandlerMocks();

mockReq.params = sampleReq.params;
mockReq.body = { ...sampleReq.body, message: { ...sampleReq.body.message, body1: null } };

try {
const requestHandler = notify.sendNotification();

await requestHandler(mockReq, mockRes, mockNext);
expect.fail();
} catch (actualError) {
expect((actualError as HTTPError).status).to.equal(400);
expect((actualError as HTTPError).message).to.equal('Missing required body param: message.body1');
}
});

it('should throw a 400 error when no message.body2', async () => {
const { mockReq, mockRes, mockNext } = getRequestHandlerMocks();

mockReq.params = sampleReq.params;
mockReq.body = { ...sampleReq.body, message: { ...sampleReq.body.message, body2: null } };

try {
const requestHandler = notify.sendNotification();

await requestHandler(mockReq, mockRes, mockNext);
expect.fail();
} catch (actualError) {
expect((actualError as HTTPError).status).to.equal(400);
expect((actualError as HTTPError).message).to.equal('Missing required body param: message.body2');
}
});

it('should throw a 400 error when no message.footer', async () => {
const { mockReq, mockRes, mockNext } = getRequestHandlerMocks();

mockReq.params = sampleReq.params;
mockReq.body = { ...sampleReq.body, message: { ...sampleReq.body.message, footer: null } };

try {
const requestHandler = notify.sendNotification();

await requestHandler(mockReq, mockRes, mockNext);
expect.fail();
} catch (actualError) {
expect((actualError as HTTPError).status).to.equal(400);
expect((actualError as HTTPError).message).to.equal('Missing required body param: message.footer');
}
});

it('sends email notification and returns 200 on success', async () => {
const { mockReq, mockRes, mockNext } = getRequestHandlerMocks();

Expand Down
61 changes: 13 additions & 48 deletions api/src/paths/gcnotify/send.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { RequestHandler } from 'express';
import { Operation } from 'express-openapi';
import { SYSTEM_ROLE } from '../../constants/roles';
import { HTTP400 } from '../../errors/custom-error';
import { IgcNotifyPostReturn } from '../../models/gcnotify';
import { authorizeRequestHandler } from '../../request-handlers/security/authorization';
import { GCNotifyService } from '../../services/gcnotify-service';
import { GCNotifyService, IgcNotifyPostReturn } from '../../services/gcnotify-service';
import { getLogger } from '../../utils/logger';

const defaultLog = getLogger('paths/gcnotify');

const APP_HOST = process.env.APP_HOST;

export const POST: Operation = [
authorizeRequestHandler(() => {
return {
Expand Down Expand Up @@ -42,17 +42,7 @@ POST.apiDoc = {
properties: {
recipient: {
type: 'object',
oneOf: [
{
required: ['emailAddress']
},
{
required: ['phoneNumber']
},
{
required: ['userId']
}
],
required: ['emailAddress', 'userId'],
properties: {
emailAddress: {
type: 'string'
Expand Down Expand Up @@ -107,10 +97,12 @@ POST.apiDoc = {
type: 'string'
},
reference: {
type: 'string'
type: 'string',
nullable: true
},
scheduled_for: {
type: 'string'
type: 'string',
nullable: true
},
template: {
type: 'object'
Expand Down Expand Up @@ -149,35 +141,7 @@ POST.apiDoc = {
export function sendNotification(): RequestHandler {
return async (req, res) => {
const recipient = req.body?.recipient || null;
const message = req.body?.message || null;

if (!req.body) {
throw new HTTP400('Missing required param: body');
}

if (!recipient) {
throw new HTTP400('Missing required body param: recipient');
}

if (!message) {
throw new HTTP400('Missing required body param: message');
}

if (!message.header) {
throw new HTTP400('Missing required body param: message.header');
}

if (!message.body1) {
throw new HTTP400('Missing required body param: message.body1');
}

if (!message.body2) {
throw new HTTP400('Missing required body param: message.body2');
}

if (!message.footer) {
throw new HTTP400('Missing required body param: message.footer');
}
const message = { ...req.body?.message, footer: `To access the site, ${APP_HOST}` } || null;

try {
const gcnotifyService = new GCNotifyService();
Expand All @@ -191,9 +155,10 @@ export function sendNotification(): RequestHandler {
response = await gcnotifyService.sendPhoneNumberGCNotification(recipient.phoneNumber, message);
}

if (recipient.userId) {
defaultLog.error({ label: 'send gcnotify', message: 'email and sms from Id not implemented yet' });
}
//TODO: send an email or sms depending on users ID and data
// if (recipient.userId) {
// defaultLog.error({ label: 'send gcnotify', message: 'email and sms from Id not implemented yet' });
// }

return res.status(200).json(response);
} catch (error) {
Expand Down
3 changes: 1 addition & 2 deletions api/src/services/gcnotify-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { describe } from 'mocha';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import { ApiError } from '../errors/custom-error';
import { IgcNotifyGenericMessage } from '../models/gcnotify';
import { GCNotifyService } from './gcnotify-service';
import { GCNotifyService, IgcNotifyGenericMessage } from './gcnotify-service';

chai.use(sinonChai);

Expand Down
45 changes: 42 additions & 3 deletions api/src/services/gcnotify-service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,45 @@
import axios from 'axios';
import { ApiError, ApiErrorType } from '../errors/custom-error';
import { IgcNotifyGenericMessage, IgcNotifyPostReturn } from '../models/gcnotify';

export interface IgcNotifyPostReturn {
content: object;
id: string;
reference: string;
scheduled_for: string;
template: object;
uri: string;
}

export interface IgcNotifyGenericMessage {
subject: string;
header: string;
body1: string;
body2: string;
footer: string;
}

export interface ISendGCNotifyEmailMessage {
email_address: string;
template_id: string;
personalisation: {
subject: string;
header: string;
main_body1: string;
main_body2: string;
footer: string;
};
}

export interface ISendGCNotifySMSMessage {
phone_number: string;
template_id: string;
personalisation: {
header: string;
main_body1: string;
main_body2: string;
footer: string;
};
}

const EMAIL_TEMPLATE = process.env.GCNOTIFY_ONBOARDING_REQUEST_EMAIL_TEMPLATE || '';
const SMS_TEMPLATE = process.env.GCNOTIFY_ONBOARDING_REQUEST_SMS_TEMPLATE || '';
Expand All @@ -24,7 +63,7 @@ export class GCNotifyService {
* @returns {IgcNotifyPostReturn}
*/
async sendEmailGCNotification(emailAddress: string, message: IgcNotifyGenericMessage): Promise<IgcNotifyPostReturn> {
const data = {
const data: ISendGCNotifyEmailMessage = {
email_address: emailAddress,
template_id: EMAIL_TEMPLATE,
personalisation: {
Expand Down Expand Up @@ -56,7 +95,7 @@ export class GCNotifyService {
* @returns {IgcNotifyPostReturn}
*/
async sendPhoneNumberGCNotification(sms: string, message: IgcNotifyGenericMessage): Promise<IgcNotifyPostReturn> {
const data = {
const data: ISendGCNotifySMSMessage = {
phone_number: sms,
template_id: SMS_TEMPLATE,
personalisation: {
Expand Down
Loading