diff --git a/src/.env.sample b/src/.env.sample index da4e42b3e..35d04d684 100644 --- a/src/.env.sample +++ b/src/.env.sample @@ -182,3 +182,6 @@ SCHEDULER_SERVICE_BASE_URL= '/scheduler/' #Refresh interval for materialized views REFRESH_VIEW_INTERVAL= 540000 + +#Generic Email template for new users +GENERIC_INVITATION_EMAIL_TEMPLATE_CODE=generic_invite diff --git a/src/constants/common.js b/src/constants/common.js index 84b729162..cb67b64a6 100644 --- a/src/constants/common.js +++ b/src/constants/common.js @@ -95,6 +95,7 @@ module.exports = { INACTIVE_STATUS: 'INACTIVE', MENTOR_ROLE: 'mentor', MENTEE_ROLE: 'mentee', + SESSION_MANAGER_ROLE: 'session_manager', redisUserPrefix: 'user_', redisOrgPrefix: 'org_', location: 'location', @@ -120,4 +121,5 @@ module.exports = { materializedViewsPrefix: 'm_', DELETED_STATUS: 'DELETED', DEFAULT_ORG_VISIBILITY: 'PUBLIC', + ROLE_TYPE_NON_SYSTEM: 0, } diff --git a/src/database/migrations/20240122091311-create-generic-invitation-template.js b/src/database/migrations/20240122091311-create-generic-invitation-template.js new file mode 100644 index 000000000..78ba2a30f --- /dev/null +++ b/src/database/migrations/20240122091311-create-generic-invitation-template.js @@ -0,0 +1,32 @@ +'use strict' +const moment = require('moment') + +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + up: async (queryInterface, Sequelize) => { + const defaultOrgId = queryInterface.sequelize.options.defaultOrgId + if (!defaultOrgId) { + throw new Error('Default org ID is undefined. Please make sure it is set in sequelize options.') + } + let notificationTemplateData = [ + { + code: 'generic_invite', + subject: 'Welcome Aboard as a {roles}', + body: '

Dear {name},

We are delighted to inform you that you have been successfully onboarded as a {roles} for {orgName}. You can now explore {appName}.
We request you to register on our Mentoring Platform (if not already), to start your journey with us as a organization admin.

Click to register: {portalURL}', + status: 'ACTIVE', + type: 'email', + created_at: moment().format(), + updated_at: moment().format(), + email_header: 'email_header', + email_footer: 'email_footer', + organization_id: defaultOrgId, + }, + ] + + await queryInterface.bulkInsert('notification_templates', notificationTemplateData, {}) + }, + + down: async (queryInterface, Sequelize) => { + await queryInterface.bulkDelete('notification_templates', { code: 'generic_invite' }) + }, +} diff --git a/src/database/migrations/20240122092217-update-registration-template.js b/src/database/migrations/20240122092217-update-registration-template.js new file mode 100644 index 000000000..1a3d6ad31 --- /dev/null +++ b/src/database/migrations/20240122092217-update-registration-template.js @@ -0,0 +1,42 @@ +'use strict' + +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + up: async (queryInterface, Sequelize) => { + const defaultOrgId = queryInterface.sequelize.options.defaultOrgId + if (!defaultOrgId) { + throw new Error('Default org ID is undefined. Please make sure it is set in sequelize options.') + } + const registrationTemplate = await queryInterface.sequelize.query( + `SELECT * FROM notification_templates WHERE code = :code AND organization_id = :organization_id LIMIT 1`, + { + replacements: { + code: process.env.REGISTRATION_EMAIL_TEMPLATE_CODE, + organization_id: defaultOrgId, + }, + type: Sequelize.QueryTypes.SELECT, + } + ) + + if (registrationTemplate) { + const update = await queryInterface.sequelize.query( + `UPDATE notification_templates SET body = :body WHERE code = :code AND organization_id = :organization_id`, + { + replacements: { + code: process.env.REGISTRATION_EMAIL_TEMPLATE_CODE, + organization_id: defaultOrgId, + body: '

Dear {name},

Welcome to {appName} community! . We are excited for you to start your journey as a {roles}.

Login to MentorED to start your journey
Click to register: {portalURL}', + }, + type: Sequelize.QueryTypes.UPDATE, + } + ) + console.log(update, 'update') + } else { + console.log('Registration template not found') + } + }, + + down: async (queryInterface, Sequelize) => { + //not required + }, +} diff --git a/src/database/queries/orgUserInvite.js b/src/database/queries/orgUserInvite.js index ce812b53e..eebed553a 100644 --- a/src/database/queries/orgUserInvite.js +++ b/src/database/queries/orgUserInvite.js @@ -1,6 +1,6 @@ 'use strict' const organizationUserInvite = require('../models/index').OrganizationUserInvite -const { UniqueConstraintError, ValidationError } = require('sequelize') +const { ValidationError } = require('sequelize') exports.create = async (data) => { try { diff --git a/src/database/queries/userCredential.js b/src/database/queries/userCredential.js index e941f4f4c..64a724af7 100644 --- a/src/database/queries/userCredential.js +++ b/src/database/queries/userCredential.js @@ -1,5 +1,6 @@ 'use strict' const UserCredential = require('@database/models/index').UserCredential +const { UniqueConstraintError, ValidationError } = require('sequelize') exports.create = async (data) => { try { @@ -7,7 +8,7 @@ exports.create = async (data) => { return res.get({ plain: true }) } catch (error) { if (error instanceof UniqueConstraintError) { - return 'USER_ALREADY_EXISTS' + return 'User already exist' } else if (error instanceof ValidationError) { let message error.errors.forEach((err) => { diff --git a/src/envVariables.js b/src/envVariables.js index fbcc51fac..44d5f78da 100644 --- a/src/envVariables.js +++ b/src/envVariables.js @@ -252,6 +252,10 @@ let enviromentVariables = { optional: false, default: 'aes-256-cbc', }, + GENERIC_INVITATION_EMAIL_TEMPLATE_CODE: { + message: 'Required generic invitation email template code', + optional: false, + }, } let success = true diff --git a/src/generics/utils.js b/src/generics/utils.js index bf97d2bbf..df2658c34 100644 --- a/src/generics/utils.js +++ b/src/generics/utils.js @@ -423,6 +423,13 @@ const generateWhereClause = (tableName) => { return whereClause } +const getRoleTitlesFromId = (roleIds = [], roleList = []) => { + return roleIds.map((roleId) => { + const role = roleList.find((r) => r.id === roleId) + return role ? role.title : null + }) +} + module.exports = { generateToken, hashPassword, @@ -452,4 +459,5 @@ module.exports = { isValidEmail, isValidName, generateWhereClause, + getRoleTitlesFromId, } diff --git a/src/sample.csv b/src/sample.csv index 98b1e86ee..f1c308291 100644 --- a/src/sample.csv +++ b/src/sample.csv @@ -1,3 +1,4 @@ name,email,roles Sarah,sarah@tunerlabs.com,mentee John,john@tunerlabs.com,mentor +Pradeep,pradeep@tunerlabs.com,"mentor,session_manager" diff --git a/src/services/account.js b/src/services/account.js index d7a363551..0b33be187 100644 --- a/src/services/account.js +++ b/src/services/account.js @@ -104,7 +104,7 @@ module.exports = class AccountHelper { if (invitedUserMatch) { bodyData.organization_id = invitedUserMatch.organization_id roles = invitedUserMatch.roles - role = await roleQueries.findOne( + role = await roleQueries.findAll( { id: invitedUserMatch.roles }, { attributes: { @@ -113,7 +113,7 @@ module.exports = class AccountHelper { } ) - if (!role) { + if (!role.length > 0) { return common.failureResponse({ message: 'ROLE_NOT_FOUND', statusCode: httpStatusCode.not_acceptable, @@ -121,20 +121,21 @@ module.exports = class AccountHelper { }) } - if (role.title === common.ORG_ADMIN_ROLE) { - isOrgAdmin = true - - const defaultRole = await roleQueries.findOne( - { title: process.env.DEFAULT_ROLE }, - { - attributes: { - exclude: ['created_at', 'updated_at', 'deleted_at'], - }, - } - ) + role.forEach(async (eachRole) => { + if (eachRole.title === common.ORG_ADMIN_ROLE) { + const defaultRole = await roleQueries.findOne( + { title: process.env.DEFAULT_ROLE }, + { + attributes: { + exclude: ['created_at', 'updated_at', 'deleted_at'], + }, + } + ) - roles.push(defaultRole.id) - } + roles.push(defaultRole.id) + isOrgAdmin = true + } + }) bodyData.roles = roles } else { //find organization from email domain @@ -234,6 +235,23 @@ module.exports = class AccountHelper { user.user_roles = roleData + // format the roles for email template + let roleArray = [] + if (roleData.length > 0) { + const mentorRoleExists = roleData.some((role) => role.title === common.MENTOR_ROLE) + if (mentorRoleExists) { + const updatedRoleList = roleData.filter((role) => role.title !== common.MENTEE_ROLE) + roleArray = _.map(updatedRoleList, 'title') + } + } + + let roleToString = + roleArray.length > 0 + ? roleArray + .map((role) => role.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase())) + .join(' and ') + : '' + const accessToken = utilsHelper.generateToken( tokenDetail, process.env.ACCESS_TOKEN_SECRET, @@ -290,6 +308,8 @@ module.exports = class AccountHelper { body: utilsHelper.composeEmailBody(templateData.body, { name: bodyData.name, appName: process.env.APP_NAME, + roles: roleToString || '', + portalURL: process.env.PORTAL_URL, }), }, } diff --git a/src/services/org-admin.js b/src/services/org-admin.js index ef583ddb2..c53e5e5b8 100644 --- a/src/services/org-admin.js +++ b/src/services/org-admin.js @@ -55,6 +55,7 @@ module.exports = class OrgAdminHelper { } const result = await fileUploadQueries.create(creationData) + if (!result?.id) { return common.successResponse({ responseCode: 'CLIENT_ERROR', @@ -421,12 +422,6 @@ function updateRoleForApprovedRequest(requestDetails, user) { { attributes: ['title', 'id', 'user_type', 'status'] } ) - const systemRoleIds = userRoles - .filter((role) => role.user_type === common.ROLE_TYPE_SYSTEM) - .map((role) => role.id) - - let rolesToUpdate = [...systemRoleIds] - const newRole = await roleQueries.findOne( { id: requestDetails.role, status: common.ACTIVE_STATUS }, { attributes: ['title', 'id', 'user_type', 'status'] } @@ -440,7 +435,16 @@ function updateRoleForApprovedRequest(requestDetails, user) { }, }) - rolesToUpdate.push(requestDetails.role) + let rolesToUpdate = [...requestDetails.role] + let currentUserRoleIds = _.map(userRoles, 'id') + + //remove mentee role from roles array + const menteeRoleId = userRoles.find((role) => role.title === common.MENTEE_ROLE)?.id + if (menteeRoleId && currentUserRoleIds.includes(menteeRoleId)) { + _.pull(currentUserRoleIds, menteeRoleId) + } + rolesToUpdate.push(...currentUserRoleIds) + const roles = _.uniq(rolesToUpdate) await userQueries.updateUser( diff --git a/src/services/userInvite.js b/src/services/userInvite.js index 782368ce4..816912c6c 100644 --- a/src/services/userInvite.js +++ b/src/services/userInvite.js @@ -42,7 +42,6 @@ module.exports = class UserInviteHelper { // extract data from csv const parsedFileData = await this.extractDataFromCSV(response.result.downloadPath) if (!parsedFileData.success) throw new Error('FAILED_TO_READ_CSV') - const invitees = parsedFileData.result.data // create outPut file and create invites @@ -51,8 +50,8 @@ module.exports = class UserInviteHelper { // upload output file to cloud const uploadRes = await this.uploadFileToCloud(outputFilename, inviteeFileDir, data.user.id) - const output_path = uploadRes.result.uploadDest + const update = { output_path, updated_by: data.user.id, @@ -60,7 +59,7 @@ module.exports = class UserInviteHelper { createResponse.result.isErrorOccured == true ? common.FAILED_STATUS : common.PROCESSED_STATUS, } - // //update output path in file uploads + //update output path in file uploads const rowsAffected = await fileUploadQueries.update( { id: data.fileDetails.id, organization_id: data.user.organization_id }, update @@ -69,7 +68,7 @@ module.exports = class UserInviteHelper { throw new Error('FILE_UPLOAD_MODIFY_ERROR') } - // // send email to admin + // send email to admin const templateCode = process.env.ADMIN_INVITEE_UPLOAD_EMAIL_TEMPLATE_CODE if (templateCode) { const templateData = await notificationTemplateQueries.findOneEmailTemplate( @@ -92,6 +91,7 @@ module.exports = class UserInviteHelper { message: 'CSV_UPLOADED_SUCCESSFULLY', }) } catch (error) { + console.log(error, 'CSV PROCESSING ERROR') return reject({ success: false, message: error.message, @@ -139,6 +139,15 @@ module.exports = class UserInviteHelper { static async extractDataFromCSV(csvFilePath) { try { const csvToJsonData = await csv().fromFile(csvFilePath) + const header = Object.keys(csvToJsonData[0]) + + if (header.map((column) => column.toLowerCase()).includes('roles')) { + // Process the data, split roles, and handle unquoted roles + csvToJsonData.forEach((row) => { + const roles = row.roles.replace(/"/g, '').split(',') + row.roles = roles + }) + } return { success: true, result: { @@ -156,30 +165,46 @@ module.exports = class UserInviteHelper { static async createUserInvites(csvData, user, fileUploadId) { try { const outputFileName = utils.generateFileName(common.inviteeOutputFile, common.csvExtension) - const allRoles = _.uniq(_.map(csvData, 'roles').map((role) => role.toLowerCase())) - const roleList = await roleQueries.findAll({ title: allRoles }) + let menteeRoleId, mentorRoleId + + //get all the roles and map title and id + const roleList = await roleQueries.findAll({ + user_type: common.ROLE_TYPE_NON_SYSTEM, + status: common.ACTIVE_STATUS, + }) const roleTitlesToIds = {} roleList.forEach((role) => { roleTitlesToIds[role.title] = [role.id] + if (role.title === common.MENTEE_ROLE) { + menteeRoleId = role.id + } + if (role.title === common.MENTOR_ROLE) { + mentorRoleId = role.id + } }) //get all existing user const emailArray = _.uniq(_.map(csvData, 'email')).map((email) => emailEncryption.encrypt(email.toLowerCase()) ) + const userCredentials = await UserCredentialQueries.findAll( { email: { [Op.in]: emailArray } }, { - attributes: ['user_id'], + attributes: ['user_id', 'email'], } - ) //This is valid since UserCredentials Already Store The Encrypted Email ID + ) + + //This is valid since UserCredentials Already Store The Encrypted Email ID const userIds = _.map(userCredentials, 'user_id') const existingUsers = await userQueries.findAll( { id: userIds }, { attributes: ['id', 'email', 'organization_id', 'roles'], } - ) //Get All The Users From Database based on UserIds From UserCredentials + ) + + //Get All The Users From Database based on UserIds From UserCredentials const existingEmailsMap = new Map(existingUsers.map((eachUser) => [eachUser.email, eachUser])) //Figure Out Who Are The Existing Users //find default org id @@ -190,24 +215,13 @@ module.exports = class UserInviteHelper { let isErrorOccured = false let isOrgUpdate = false - //fetch email template - const mentorTemplateCode = process.env.MENTOR_INVITATION_EMAIL_TEMPLATE_CODE || null - const menteeTemplateCode = process.env.MENTEE_INVITATION_EMAIL_TEMPLATE_CODE || null - - const [mentorTemplateData, menteeTemplateData] = await Promise.all([ - mentorTemplateCode - ? notificationTemplateQueries.findOneEmailTemplate(mentorTemplateCode, user.organization_id) - : null, - menteeTemplateCode - ? notificationTemplateQueries.findOneEmailTemplate(menteeTemplateCode, user.organization_id) - : null, - ]) - - const templates = { - [common.MENTOR_ROLE]: mentorTemplateData, - [common.MENTEE_ROLE]: menteeTemplateData, - } + //fetch generic email template + const emailTemplate = await notificationTemplateQueries.findOneEmailTemplate( + process.env.GENERIC_INVITATION_EMAIL_TEMPLATE_CODE, + user.organization_id + ) + //find already invited users const emailList = await userInviteQueries.findAll({ email: emailArray }) const existingInvitees = {} emailList.forEach((userInvitee) => { @@ -216,43 +230,59 @@ module.exports = class UserInviteHelper { // process csv data for (const invitee of csvData) { - //convert the fields to lower case - invitee.roles = invitee.roles.toLowerCase() invitee.email = invitee.email.toLowerCase() const encryptedEmail = emailEncryption.encrypt(invitee.email.toLowerCase()) - //validate the fields + //find the invalid fields and generate error message + let invalidFields = [] if (!utils.isValidName(invitee.name)) { - invitee.statusOrUserId = 'NAME_INVALID' - input.push(invitee) - continue + invalidFields.push('name') } - if (!utils.isValidEmail(invitee.email)) { - invitee.statusOrUserId = 'EMAIL_INVALID' - input.push(invitee) - continue + invalidFields.push('email') + } + + const invalidRoles = invitee.roles.filter((role) => !roleTitlesToIds.hasOwnProperty(role.toLowerCase())) + if (invalidRoles.length > 0) { + invalidFields.push('roles') } - if (!roleTitlesToIds.hasOwnProperty(invitee.roles)) { - invitee.statusOrUserId = 'ROLE_INVALID' + //merge all error message + if (invalidFields.length > 0) { + const errorMessage = `${ + invalidFields.length > 2 + ? invalidFields.slice(0, -1).join(', ') + ', and ' + invalidFields.slice(-1) + : invalidFields.join(' and ') + } ${invalidFields.length > 1 ? 'are' : 'is'} invalid.` + + invitee.statusOrUserId = errorMessage + invitee.roles = invitee.roles.length > 0 ? invitee.roles.join(',') : '' input.push(invitee) continue } - //update user details if the user exist and in default org const existingUser = existingEmailsMap.get(encryptedEmail) + //return error for already invited user + if (!existingUser && existingInvitees.hasOwnProperty(encryptedEmail)) { + invitee.statusOrUserId = 'User already exist' + invitee.roles = invitee.roles.length > 0 ? invitee.roles.join(',') : '' + input.push(invitee) + continue + } + // Update user details if the user exists and belongs to the default organization if (existingUser) { - invitee.statusOrUserId = 'USER_ALREADY_EXISTS' + invitee.statusOrUserId = 'User already exist' isErrorOccured = true const isOrganizationMatch = existingUser.organization_id === defaultOrgId || existingUser.organization_id === user.organization_id + if (isOrganizationMatch) { let userUpdateData = {} + //update user organization if (existingUser.organization_id != user.organization_id) { await userQueries.changeOrganization( existingUser.id, @@ -262,23 +292,44 @@ module.exports = class UserInviteHelper { organization_id: user.organization_id, } ) + isOrgUpdate = true userUpdateData.refresh_tokens = [] } - const areAllElementsInArray = _.every(roleTitlesToIds[invitee.roles], (element) => - _.includes(existingUser.roles, element) + + //find the new roles + const elementsNotInArray = _.difference( + _.map(invitee.roles, (role) => roleTitlesToIds[role.toLowerCase()]).flat(), + existingUser.roles ) - if (!areAllElementsInArray) { - userUpdateData.roles = roleTitlesToIds[invitee.roles] + + //update the user roles and handle downgrade of role + if (elementsNotInArray.length > 0) { + userUpdateData.roles = [] + if (existingUser.roles.includes(menteeRoleId)) { + if (existingUser.roles.length === 1) { + userUpdateData.roles.push(...elementsNotInArray, menteeRoleId) + } else { + userUpdateData.roles.push(...elementsNotInArray, ...existingUser.roles) + } + } else { + userUpdateData.roles.push(...elementsNotInArray, ...existingUser.roles) + } + + if (userUpdateData.roles.includes(mentorRoleId)) { + userUpdateData.roles = _.pull(userUpdateData.roles, menteeRoleId) + } + userUpdateData.refresh_tokens = [] } + //update user and user credentials table with new role organization if (isOrgUpdate || userUpdateData.roles) { - const userCredentials = await UserCredentialQueries.findOne({ + const userCred = await UserCredentialQueries.findOne({ email: encryptedEmail, }) - await userQueries.updateUser({ id: userCredentials.user_id }, userUpdateData) + await userQueries.updateUser({ id: userCred.user_id }, userUpdateData) await UserCredentialQueries.updateUser( { email: encryptedEmail, @@ -286,70 +337,100 @@ module.exports = class UserInviteHelper { { organization_id: user.organization_id } ) - const userRoles = await roleQueries.findAll({ id: existingUser.roles }) - //call event to update in mentoring - if (!userUpdateData?.roles) { + const currentRoles = utils.getRoleTitlesFromId(existingUser.roles, roleList) + let newRoles = [] + newRoles = utils.getRoleTitlesFromId( + _.difference(userUpdateData.roles, existingUser.roles), + roleList + ) + //remove session_manager role because the mentee role is enough to change role in mentoring side + newRoles = newRoles.filter((role) => role !== common.SESSION_MANAGER_ROLE) + + //call event to update organization in mentoring + if (isOrgUpdate) { eventBroadcaster('updateOrganization', { requestBody: { user_id: existingUser.id, organization_id: user.organization_id, - roles: _.map(userRoles, 'title'), + roles: currentRoles, }, }) - } else { + } + + if (newRoles.length > 0) { + //call event to update role and organization in mentoring let requestBody = { user_id: existingUser.id, - new_roles: [invitee.roles], - current_roles: _.map(userRoles, 'title'), + new_roles: newRoles, + current_roles: currentRoles, } if (isOrgUpdate) requestBody.organization_id = user.organization_id eventBroadcaster('roleChange', { requestBody, }) } + + //remove user data from redis const redisUserKey = common.redisUserPrefix + existingUser.id.toString() await utils.redisDel(redisUserKey) + invitee.statusOrUserId = 'success' + } else { + invitee.statusOrUserId = 'No updates needed. User details are already up to date' } + } else { + //user doesn't have access to update user data + invitee.statusOrUserId = 'Unauthorized to update user details in a different organization.' } } else { + //new user invitee creation const inviteeData = { ...invitee, status: common.UPLOADED_STATUS, organization_id: user.organization_id, file_id: fileUploadId, - roles: roleTitlesToIds[invitee.roles] || [], - } - inviteeData.email = encryptedEmail - - if (existingInvitees.hasOwnProperty(encryptedEmail)) { - invitee.statusOrUserId = 'USER_ALREADY_EXISTS' - input.push(invitee) - continue + roles: (invitee.roles || []).map((roleTitle) => roleTitlesToIds[roleTitle.toLowerCase()] || []), + email: encryptedEmail, } + inviteeData.email = encryptedEmail const newInvitee = await userInviteQueries.create(inviteeData) - if (newInvitee.id) { + + if (newInvitee?.id) { + console.log(newInvitee.roles, 'newInvitee.roles') invitee.statusOrUserId = newInvitee.id + + //create user credentials entry const newUserCred = await UserCredentialQueries.create({ email: newInvitee.email, organization_id: newInvitee.organization_id, organization_user_invite_id: newInvitee.id, }) - if (newUserCred.id) { - const { name, email, roles } = invitee + + if (newUserCred?.id) { + const { name, email } = invitee + const roles = utils.getRoleTitlesFromId(newInvitee.roles, roleList) + const roleToString = + roles.length > 0 + ? roles + .map((role) => + role.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()) + ) + .join(' and ') + : '' + const userData = { name, email, - role: roles, + role: roleToString, org_name: user.org_name, } - const templateData = templates[roles] - //send email invitation for user - if (templateData && Object.keys(templateData).length > 0) { - await this.sendInviteeEmail(templateData, userData) + //send email invitation for new user + if (emailTemplate) { + await this.sendInviteeEmail(emailTemplate, userData, null, { roles: roleToString }) } } else { + //delete invitation entry isErrorOccured = true await userInviteQueries.deleteOne(newInvitee.id) invitee.statusOrUserId = newUserCred @@ -360,12 +441,14 @@ module.exports = class UserInviteHelper { } } + //convert roles array to string + invitee.roles = invitee.roles.length > 0 ? invitee.roles.join(',') : '' input.push(invitee) } + //generate output csv const csvContent = utils.generateCSVContent(input) const outputFilePath = path.join(inviteeFileDir, outputFileName) - fs.writeFileSync(outputFilePath, csvContent) return { @@ -418,13 +501,16 @@ module.exports = class UserInviteHelper { } } - static async sendInviteeEmail(templateData, userData, inviteeUploadURL = null) { + static async sendInviteeEmail(templateData, userData, inviteeUploadURL = null, subjectComposeData = {}) { try { const payload = { type: common.notificationEmailType, email: { to: userData.email, - subject: templateData.subject, + subject: + subjectComposeData && Object.keys(subjectComposeData).length > 0 + ? utils.composeEmailBody(templateData.subject, subjectComposeData) + : templateData.subject, body: utils.composeEmailBody(templateData.body, { name: userData.name, role: userData.role || '', @@ -432,12 +518,12 @@ module.exports = class UserInviteHelper { appName: process.env.APP_NAME, inviteeUploadURL, portalURL: process.env.PORTAL_URL, + roles: userData.roles || '', }), }, } await kafkaCommunication.pushEmailToKafka(payload) - return { success: true, }