Skip to content
1 change: 1 addition & 0 deletions app/config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const nconf = require('nconf');
const {merge} = require('lodash');
require('dotenv').config();

const ENV = process.env.NODE_ENV || 'local';

Expand Down
7 changes: 4 additions & 3 deletions app/libs/email.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,15 @@ const getEmailList = async (triggers) => {
const emailList = [];
for (const notif of notifs) {
if (notif.user) {
if (!emailList.includes(notif.user.email)
if (!emailList.some((el) => {return el.toEmail === notif.user.email;})
&& notif.user.email.endsWith(domain)
&& notif.user.allowNotifications) {
emailList.push({toEmail: notif.user.email, notifId: notif.id, eventType: notif.eventType});
}
} else if (notif.userGroup) {
}
if (notif.userGroup) {
for (const groupUser of notif.userGroup.users) {
if (!emailList.includes(groupUser.email)
if (!emailList.some((el) => {return el.toEmail === groupUser.email;})
&& groupUser.email.endsWith(domain)
&& groupUser.allowNotifications) {
emailList.push({toEmail: groupUser.email, notifId: notif.id, eventType: notif.eventType});
Expand Down
18 changes: 2 additions & 16 deletions app/queue.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ const addJobToEmailQueue = async (data) => {
logger.info('adding email to queue: ', data);
const job = await emailQueue.add('job', data, EMAIL_REMOVE_CONFIG);
try {
await db.models.notificationTrack.create({
await db.models.notificationTrack.findOrCreate({where: {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this means a new record won't be created each time a notification is required - a new one is needed each time there's a new task because this table lets us track whether the queue is working. We should replace this with eg Arena for monitoring bullmq tasks - and clear out the backlog in this table - but in the meantime using findOrCreate here instead of create means the record is not useful.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree. it should use create instead of findOrCreate.

notificationId: data.notifId,
jobId: job.id,
recipient: data.mailOptions.to,
reason: data.eventType,
outcome: 'created',
});
}});
} catch (error) {
logger.error(`Unable to create notification track ${error}`);
throw new Error(error);
Expand Down Expand Up @@ -152,20 +152,6 @@ const emailProcessor = async (job) => {
});

try {
try {
const notification = await db.models.notificationTrack.findOne({
where: {
notificationId: job.data.notifId,
jobId: job.id,
},
});
await notification.update({
outcome: 'processing',
});
} catch (error) {
logger.error(`Unable to add process notification track ${error}`);
throw new Error(error);
}
await transporter.sendMail(job.data.mailOptions);
} catch (err) {
logger.error(JSON.stringify(job.data.mailOptions));
Expand Down
29 changes: 23 additions & 6 deletions app/routes/notification/notification.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const db = require('../../models');
const logger = require('../../log');

const {getUserProjects, isAdmin} = require('../../libs/helperFunctions');
const {NOTIFICATION_EVENT} = require('../../constants');

const router = express.Router({mergeParams: true});

Expand All @@ -18,6 +19,9 @@ const pairs = {
// for each entry in pairs, assumes the key-named value in
// req.body is the ident, and gets the id of the corresponding object.
router.use(async (req, res, next) => {
if (req.method === 'GET') {
req.body = req.query;
}
const operations = [];

for (const [key, value] of Object.entries(pairs)) {
Expand Down Expand Up @@ -95,9 +99,10 @@ router.route('/')
}
})
.post(async (req, res) => {
// TODO: Delete admin check once notification is properly implemented/tested
if (!isAdmin(req.user)) {
return res.status(HTTP_STATUS.FORBIDDEN);
if (req.user.id !== req.body.user_id && !isAdmin(req.user)) {
return res.status(HTTP_STATUS.FORBIDDEN).json({
error: {message: 'only user self and admin can create notification'},
});
}

if (req.body.user_id && req.body.user_group_id) {
Expand All @@ -124,12 +129,18 @@ router.route('/')
});
}

if (!req.body.event_type) {
if (!req.body.eventType) {
return res.status(HTTP_STATUS.CONFLICT).json({
error: {message: 'Event type must be specified'},
});
}

if (!Object.values(NOTIFICATION_EVENT).includes(req.body.eventType)) {
return res.status(HTTP_STATUS.CONFLICT).json({
error: {message: 'Event type must be reportCreated or userBound'},
});
}

// check the given user, if any, is bound to the given project (or is admin)
if (req.body.user_id) {
let notifUser;
Expand Down Expand Up @@ -174,7 +185,7 @@ router.route('/')
projectId: req.body.project_id,
userId: req.body.user_id ? req.body.user_id : null,
userGroupId: req.body.user_group_id ? req.body.user_group_id : null,
eventType: req.body.event_type,
eventType: req.body.eventType,
templateId: req.body.template_id,
},
});
Expand All @@ -197,7 +208,7 @@ router.route('/')
try {
notification = await db.models.notification.findOne({
where: {ident: req.body.ident},
attributes: ['id', 'ident'],
attributes: ['id', 'ident', 'userId'],
});
} catch (error) {
logger.error(`Error while trying to find notification ${error}`);
Expand All @@ -213,6 +224,12 @@ router.route('/')
});
}

if (req.user.id !== notification.userId && !isAdmin(req.user)) {
return res.status(HTTP_STATUS.FORBIDDEN).json({
error: {message: 'only user self and admin can delete notification'},
});
}

try {
await notification.destroy();
return res.status(HTTP_STATUS.NO_CONTENT).send();
Expand Down
17 changes: 6 additions & 11 deletions app/routes/report/report.js
Original file line number Diff line number Diff line change
Expand Up @@ -333,27 +333,22 @@ router.route('/')
projectIdArray.push(project.project_id);
});

await db.models.notification.findOrCreate({
where: {
userId: req.user.id,
eventType: NOTIFICATION_EVENT.REPORT_CREATED,
templateId: report.templateId,
projectId: report.projects[0].project_id,
},
});

await email.notifyUsers(
`Report Created: ${req.body.patientId} ${req.body.template}`,
`New report:
Ident: ${report.ident}
Created by: ${req.user.firstName} ${req.user.lastName}
Project: ${req.body.project}
Template: ${req.body.template}
Patient: ${req.body.patientId}`,
Patient: ${req.body.patientId}

You're receiving this email because you have email notifications enabled in your account settings.
If you no longer wish to receive these notifications, you can unsubscribe at any time by visiting your User Profile and turning off the "Allow email notifications" option.`,
{
userId: req.user.id,
eventType: NOTIFICATION_EVENT.REPORT_CREATED,
templateId: report.templateId,
projectId: projectIdArray,
projectId: report.projects[0].project_id,
},
);

Expand Down
25 changes: 12 additions & 13 deletions app/routes/report/reportUser.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,30 +132,29 @@ router.route('/')

const report = await db.models.report.findOne({where: {id: req.report.id}});
const reportProject = await db.models.reportProject.findOne({where: {report_id: req.report.id}});
const notif = await db.models.notification.findOrCreate({
where: {
userId: req.user.id,
eventType: NOTIFICATION_EVENT.USER_BOUND,
templateId: report.templateId,
projectId: reportProject.project_id,
},
});
const project = await db.models.project.findOne({where: {id: reportProject.project_id}});
const template = await db.models.template.findOne({where: {id: report.templateId}});

// Try sending email
try {
await email.notifyUsers(
`${bindUser.firstName} ${bindUser.lastName} has been bound to a report`,
`User ${bindUser.firstName} ${bindUser.lastName} has been bound to report ${req.report.ident} as ${role}`,
`User ${bindUser.firstName} ${bindUser.lastName} has been bound to report ${req.report.ident} as ${role}.
Report Type: ${template.name}
Patient Id: ${report.patientId}
Project: ${project.name}

You're receiving this email because you have email notifications enabled in your account settings.
If you no longer wish to receive these notifications, you can unsubscribe at any time by visiting your User Profile and turning off the "Allow email notifications" option.`,
{
userId: [req.user.id, bindUser.id],
eventType: NOTIFICATION_EVENT.USER_BOUND,
templateId: req.report.templateId,
templateId: report.templateId,
projectId: reportProject.project_id,
},
);

await notif[0].update({status: 'Success'}, {userId: req.user.id});
logger.info('Email sent successfully');
} catch (error) {
await notif[0].update({status: 'Unsuccess'}, {userId: req.user.id});
logger.error(`Email not sent successfully: ${error}`);
}

Expand Down
38 changes: 37 additions & 1 deletion app/routes/swagger/schemas.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const schemas = {};
const ID_FIELDS = [
'germlineReportId', 'user_id', 'createdBy_id', 'addedBy_id', 'variantId',
'gene1Id', 'gene2Id', 'reviewerId', 'biofxAssignedId', 'logoId', 'headerId', 'templateId',
'product_id', 'addedById',
'product_id', 'addedById', 'userId', 'projectId', 'userGroupId',
];
const PUBLIC_VIEW_EXCLUDE = [...ID_FIELDS, 'id', 'reportId', 'geneId', 'deletedAt', 'updatedBy'];
const GENERAL_EXCLUDE = REPORT_EXCLUDE.concat(ID_FIELDS);
Expand Down Expand Up @@ -207,6 +207,42 @@ Object.assign(schemas.germlineReportUserCreate.properties, {
},
});

// notification create
schemas.notificationAssociations = schemaGenerator(db.models.notification, {
isJsonSchema: false, title: 'notificationAssociations', exclude: [...GENERAL_EXCLUDE], associations: true, includeAssociations: ['project', 'template', 'user'],
});

Object.assign(schemas.notificationCreate.properties, {
eventType: {
type: 'string',
description: 'either reportCreated or userBound',
},
user: {
type: 'string',
format: 'uuid',
description: 'ident of user to notify, either user or user_group should be specified',
},
user_group: {
type: 'string',
format: 'uuid',
description: 'ident of user group to notify, either user or user_group should be specified',
},
project: {
type: 'string',
format: 'uuid',
description: 'ident of project that user or user group want to be notified',
Comment thread
sshugsc marked this conversation as resolved.
Outdated
},
template: {
type: 'string',
format: 'uuid',
description: 'ident of template that user or user group want to be notified',
Comment thread
sshugsc marked this conversation as resolved.
Outdated
},
});

Object.assign(schemas.notificationCreate, {
required: ['project', 'template', 'eventType'],
});

// *PUT request body*

// add template name to report update
Expand Down
Loading
Loading