diff --git a/api/src/constants/notifications.ts b/api/src/constants/notifications.ts index 0db0d8711d..f236b566f1 100644 --- a/api/src/constants/notifications.ts +++ b/api/src/constants/notifications.ts @@ -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 = { diff --git a/api/src/models/gcnotify.ts b/api/src/models/gcnotify.ts deleted file mode 100644 index 61e45de0e0..0000000000 --- a/api/src/models/gcnotify.ts +++ /dev/null @@ -1,16 +0,0 @@ -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; -} diff --git a/api/src/models/user.ts b/api/src/models/user.ts index 587b1288e7..27f4d92e92 100644 --- a/api/src/models/user.ts +++ b/api/src/models/user.ts @@ -1,7 +1,3 @@ -import { getLogger } from '../utils/logger'; - -const defaultLog = getLogger('models/user'); - export class UserObject { id: number; user_identifier: string; @@ -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; @@ -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) || []; diff --git a/api/src/paths/gcnotify/send.test.ts b/api/src/paths/gcnotify/send.test.ts index c80f4a4d0c..6089dc8d25 100644 --- a/api/src/paths/gcnotify/send.test.ts +++ b/api/src/paths/gcnotify/send.test.ts @@ -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'; @@ -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(); diff --git a/api/src/paths/gcnotify/send.ts b/api/src/paths/gcnotify/send.ts index 451d3f94d2..c8fd1a4621 100644 --- a/api/src/paths/gcnotify/send.ts +++ b/api/src/paths/gcnotify/send.ts @@ -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 { @@ -42,17 +42,7 @@ POST.apiDoc = { properties: { recipient: { type: 'object', - oneOf: [ - { - required: ['emailAddress'] - }, - { - required: ['phoneNumber'] - }, - { - required: ['userId'] - } - ], + required: ['emailAddress', 'userId'], properties: { emailAddress: { type: 'string' @@ -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' @@ -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(); @@ -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) { diff --git a/api/src/services/gcnotify-service.test.ts b/api/src/services/gcnotify-service.test.ts index cc035361df..71d117a843 100644 --- a/api/src/services/gcnotify-service.test.ts +++ b/api/src/services/gcnotify-service.test.ts @@ -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); diff --git a/api/src/services/gcnotify-service.ts b/api/src/services/gcnotify-service.ts index 6ec439d21b..a0dd59835e 100644 --- a/api/src/services/gcnotify-service.ts +++ b/api/src/services/gcnotify-service.ts @@ -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 || ''; @@ -24,7 +63,7 @@ export class GCNotifyService { * @returns {IgcNotifyPostReturn} */ async sendEmailGCNotification(emailAddress: string, message: IgcNotifyGenericMessage): Promise { - const data = { + const data: ISendGCNotifyEmailMessage = { email_address: emailAddress, template_id: EMAIL_TEMPLATE, personalisation: { @@ -56,7 +95,7 @@ export class GCNotifyService { * @returns {IgcNotifyPostReturn} */ async sendPhoneNumberGCNotification(sms: string, message: IgcNotifyGenericMessage): Promise { - const data = { + const data: ISendGCNotifySMSMessage = { phone_number: sms, template_id: SMS_TEMPLATE, personalisation: { diff --git a/app/src/features/admin/users/AccessRequestList.tsx b/app/src/features/admin/users/AccessRequestList.tsx index d99f91cdd9..4511b3a622 100644 --- a/app/src/features/admin/users/AccessRequestList.tsx +++ b/app/src/features/admin/users/AccessRequestList.tsx @@ -53,7 +53,6 @@ const AccessRequestList: React.FC = (props) => { const { accessRequests, codes, refresh } = props; const classes = useStyles(); - const biohubApi = useBiohubApi(); const [activeReviewDialog, setActiveReviewDialog] = useState<{ @@ -83,6 +82,29 @@ const AccessRequestList: React.FC = (props) => { setActiveReviewDialog({ open: false, request: null }); + try { + await biohubApi.admin.sendGCNotification( + { + emailAddress: updatedRequest.data.email, + phoneNumber: '', + userId: updatedRequest.id + }, + { + subject: 'SIMS: Your request for access has been approved.', + header: 'Your request for access to the Species Inventory Management System has been approved.', + body1: 'This is an automated message from the BioHub Species Inventory Management System', + body2: '', + footer: '' + } + ); + } catch (error) { + dialogContext.setErrorDialog({ + ...defaultErrorDialogProps, + open: true, + dialogErrorDetails: (error as APIError).errors + }); + } + try { await biohubApi.admin.approveAccessRequest( updatedRequest.id, @@ -106,6 +128,29 @@ const AccessRequestList: React.FC = (props) => { setActiveReviewDialog({ open: false, request: null }); + try { + await biohubApi.admin.sendGCNotification( + { + emailAddress: updatedRequest.data.email, + phoneNumber: '', + userId: updatedRequest.id + }, + { + subject: 'SIMS: Your request for access has been denied.', + header: 'Your request for access to the Species Inventory Management System has been denied.', + body1: 'This is an automated message from the BioHub Species Inventory Management System', + body2: '', + footer: '' + } + ); + } catch (error) { + dialogContext.setErrorDialog({ + ...defaultErrorDialogProps, + open: true, + dialogErrorDetails: (error as APIError).errors + }); + } + try { await biohubApi.admin.denyAccessRequest(updatedRequest.id); diff --git a/app/src/interfaces/useAdminApi.interface.ts b/app/src/interfaces/useAdminApi.interface.ts index ebb0accd95..41246b9923 100644 --- a/app/src/interfaces/useAdminApi.interface.ts +++ b/app/src/interfaces/useAdminApi.interface.ts @@ -28,6 +28,7 @@ export interface IGetAccessRequestsListResponse { } export interface IgcNotifyGenericMessage { + subject: string; header: string; body1: string; body2: string;