diff --git a/src/configs/bull.js b/src/configs/bull.js index 874a37bb4..52ba9a58a 100644 --- a/src/configs/bull.js +++ b/src/configs/bull.js @@ -3,13 +3,13 @@ const { Worker } = require('bullmq') const fs = require('fs') const userInviteService = require('@services/userInvite') const common = require('@constants/common') -const utils = require('@generics/utils') +const cacheUtils = require('@utils/cache') const path = require('path') const { elevateLog } = require('elevate-logger') const logger = elevateLog.init() -const redisConfiguration = utils.generateRedisConfigForQueue() +const redisConfiguration = cacheUtils.generateRedisConfigForQueue() module.exports = function () { try { diff --git a/src/configs/kafka.js b/src/configs/kafka.js index 6e4017d6c..e52ae0e87 100644 --- a/src/configs/kafka.js +++ b/src/configs/kafka.js @@ -8,7 +8,7 @@ //Dependencies const { Kafka } = require('kafkajs') -const utils = require('@generics/utils') +const cacheUtils = require('@utils/cache') const { elevateLog } = require('elevate-logger') const logger = elevateLog.init() @@ -27,10 +27,10 @@ module.exports = async () => { await consumer.connect() producer.on('producer.connect', () => { - logger.info(`KafkaProvider: connected`) + logger.info('KafkaProvider: connected') }) producer.on('producer.disconnect', () => { - logger.error(`KafkaProvider: could not connect`, { + logger.error('KafkaProvider: could not connect', { triggerNotification: true, }) }) @@ -42,7 +42,7 @@ module.exports = async () => { try { let streamingData = JSON.parse(message.value) if (streamingData.type == 'CLEAR_INTERNAL_CACHE') { - utils.internalDel(streamingData.value) + cacheUtils.internalDel(streamingData.value) } } catch (error) { logger.error('Subscribe to consumer failed:' + error, { diff --git a/src/controllers/v1/admin.js b/src/controllers/v1/admin.js index 46f10fea5..bd17ee84c 100644 --- a/src/controllers/v1/admin.js +++ b/src/controllers/v1/admin.js @@ -9,7 +9,7 @@ const adminService = require('@services/admin') const common = require('@constants/common') const httpStatusCode = require('@generics/http-status') -const utilsHelper = require('@generics/utils') +const roleUtils = require('@utils/role') const responses = require('@helpers/responses') module.exports = class Admin { @@ -23,7 +23,7 @@ module.exports = class Admin { async deleteUser(req) { try { - if (!utilsHelper.validateRoleAccess(req.decodedToken.roles, common.ADMIN_ROLE)) { + if (!roleUtils.validateRoleAccess(req.decodedToken.roles, common.ADMIN_ROLE)) { throw responses.failureResponse({ message: 'USER_IS_NOT_A_ADMIN', statusCode: httpStatusCode.bad_request, @@ -97,7 +97,7 @@ module.exports = class Admin { async addOrgAdmin(req) { try { - if (!utilsHelper.validateRoleAccess(req.decodedToken.roles, common.ADMIN_ROLE)) { + if (!roleUtils.validateRoleAccess(req.decodedToken.roles, common.ADMIN_ROLE)) { throw responses.failureResponse({ message: 'USER_IS_NOT_A_ADMIN', statusCode: httpStatusCode.bad_request, @@ -126,7 +126,7 @@ module.exports = class Admin { */ async deactivateOrg(req) { try { - if (!utilsHelper.validateRoleAccess(req.decodedToken.roles, common.ADMIN_ROLE)) { + if (!roleUtils.validateRoleAccess(req.decodedToken.roles, common.ADMIN_ROLE)) { throw responses.failureResponse({ message: 'USER_IS_NOT_A_ADMIN', statusCode: httpStatusCode.bad_request, @@ -150,7 +150,7 @@ module.exports = class Admin { */ async deactivateUser(req) { try { - if (!utilsHelper.validateRoleAccess(req.decodedToken.roles, common.ADMIN_ROLE)) { + if (!roleUtils.validateRoleAccess(req.decodedToken.roles, common.ADMIN_ROLE)) { throw responses.failureResponse({ message: 'USER_IS_NOT_A_ADMIN', statusCode: httpStatusCode.bad_request, diff --git a/src/controllers/v1/notification.js b/src/controllers/v1/notification.js index 6e5a66f8f..ad024247e 100644 --- a/src/controllers/v1/notification.js +++ b/src/controllers/v1/notification.js @@ -7,7 +7,7 @@ // Dependencies const notificationService = require('@services/notification') -const utilsHelper = require('@generics/utils') +const roleUtils = require('@utils/role') const common = require('@constants/common') const httpStatusCode = require('@generics/http-status') const responses = require('@helpers/responses') @@ -29,7 +29,7 @@ module.exports = class NotificationTemplate { async template(req) { try { - if (!utilsHelper.validateRoleAccess(req.decodedToken.roles, [common.ADMIN_ROLE, common.ORG_ADMIN_ROLE])) { + if (!roleUtils.validateRoleAccess(req.decodedToken.roles, [common.ADMIN_ROLE, common.ORG_ADMIN_ROLE])) { throw responses.failureResponse({ message: 'USER_IS_NOT_A_ADMIN', statusCode: httpStatusCode.bad_request, diff --git a/src/controllers/v1/org-admin.js b/src/controllers/v1/org-admin.js index 6f739a6a4..7129d0b59 100644 --- a/src/controllers/v1/org-admin.js +++ b/src/controllers/v1/org-admin.js @@ -9,7 +9,7 @@ const orgAdminService = require('@services/org-admin') const common = require('@constants/common') const httpStatusCode = require('@generics/http-status') -const utilsHelper = require('@generics/utils') +const roleUtils = require('@utils/role') const responses = require('@helpers/responses') module.exports = class OrgAdmin { /** @@ -21,7 +21,7 @@ module.exports = class OrgAdmin { */ async bulkUserCreate(req) { try { - if (!utilsHelper.validateRoleAccess(req.decodedToken.roles, common.ORG_ADMIN_ROLE)) { + if (!roleUtils.validateRoleAccess(req.decodedToken.roles, common.ORG_ADMIN_ROLE)) { throw responses.failureResponse({ message: 'USER_IS_NOT_A_ADMIN', statusCode: httpStatusCode.bad_request, @@ -48,7 +48,7 @@ module.exports = class OrgAdmin { */ async getBulkInvitesFilesList(req) { try { - if (!utilsHelper.validateRoleAccess(req.decodedToken.roles, common.ORG_ADMIN_ROLE)) { + if (!roleUtils.validateRoleAccess(req.decodedToken.roles, common.ORG_ADMIN_ROLE)) { throw responses.failureResponse({ message: 'USER_IS_NOT_A_ADMIN', statusCode: httpStatusCode.bad_request, @@ -72,7 +72,7 @@ module.exports = class OrgAdmin { */ async getRequestDetails(req) { try { - if (!utilsHelper.validateRoleAccess(req.decodedToken.roles, common.ORG_ADMIN_ROLE)) { + if (!roleUtils.validateRoleAccess(req.decodedToken.roles, common.ORG_ADMIN_ROLE)) { throw responses.failureResponse({ message: 'USER_IS_NOT_A_ADMIN', statusCode: httpStatusCode.bad_request, @@ -101,7 +101,7 @@ module.exports = class OrgAdmin { */ async getRequests(req) { try { - if (!utilsHelper.validateRoleAccess(req.decodedToken.roles, common.ORG_ADMIN_ROLE)) { + if (!roleUtils.validateRoleAccess(req.decodedToken.roles, common.ORG_ADMIN_ROLE)) { throw responses.failureResponse({ message: 'USER_IS_NOT_A_ADMIN', statusCode: httpStatusCode.bad_request, @@ -128,7 +128,7 @@ module.exports = class OrgAdmin { */ async updateRequestStatus(req) { try { - if (!utilsHelper.validateRoleAccess(req.decodedToken.roles, common.ORG_ADMIN_ROLE)) { + if (!roleUtils.validateRoleAccess(req.decodedToken.roles, common.ORG_ADMIN_ROLE)) { throw responses.failureResponse({ message: 'USER_IS_NOT_A_ADMIN', statusCode: httpStatusCode.bad_request, @@ -152,7 +152,7 @@ module.exports = class OrgAdmin { */ async deactivateUser(req) { try { - if (!utilsHelper.validateRoleAccess(req.decodedToken.roles, common.ORG_ADMIN_ROLE)) { + if (!roleUtils.validateRoleAccess(req.decodedToken.roles, common.ORG_ADMIN_ROLE)) { throw responses.failureResponse({ message: 'USER_IS_NOT_A_ADMIN', statusCode: httpStatusCode.bad_request, @@ -185,7 +185,7 @@ module.exports = class OrgAdmin { async inheritEntityType(req) { try { - if (!utilsHelper.validateRoleAccess(req.decodedToken.roles, common.ORG_ADMIN_ROLE)) { + if (!roleUtils.validateRoleAccess(req.decodedToken.roles, common.ORG_ADMIN_ROLE)) { throw responses.failureResponse({ message: 'USER_IS_NOT_A_ADMIN', statusCode: httpStatusCode.bad_request, diff --git a/src/controllers/v1/organization.js b/src/controllers/v1/organization.js index b9d314845..a3075a44f 100644 --- a/src/controllers/v1/organization.js +++ b/src/controllers/v1/organization.js @@ -6,7 +6,7 @@ */ // Dependencies -const utilsHelper = require('@generics/utils') +const roleUtils = require('@utils/role') const common = require('@constants/common') const httpStatusCode = require('@generics/http-status') const orgService = require('@services/organization') @@ -31,7 +31,7 @@ module.exports = class Organization { let isAdmin = false const roles = req.decodedToken.roles if (roles && roles.length > 0) { - isAdmin = utilsHelper.validateRoleAccess(roles, common.ADMIN_ROLE) + isAdmin = roleUtils.validateRoleAccess(roles, common.ADMIN_ROLE) } if (!isAdmin) { @@ -67,8 +67,8 @@ module.exports = class Organization { const roles = req.decodedToken.roles if (roles && roles.length > 0) { - isAdmin = utilsHelper.validateRoleAccess(roles, common.ADMIN_ROLE) - isOrgAdmin = utilsHelper.validateRoleAccess(roles, common.ORG_ADMIN_ROLE) + isAdmin = roleUtils.validateRoleAccess(roles, common.ADMIN_ROLE) + isOrgAdmin = roleUtils.validateRoleAccess(roles, common.ORG_ADMIN_ROLE) } if (req.params.id != req.decodedToken.organization_id && isOrgAdmin) { diff --git a/src/generics/materializedViews.js b/src/generics/materializedViews.js index 325b5b3c2..5d838eb07 100644 --- a/src/generics/materializedViews.js +++ b/src/generics/materializedViews.js @@ -1,7 +1,7 @@ 'use strict' const entityTypeQueries = require('@database/queries/entityType') const { sequelize } = require('@database/models/index') -const utils = require('@generics/utils') +const whereClauseGeneratorHelper = require('@helpers/whereClauseGenerator') const common = require('@constants/common') const getDefaultOrgId = process.env.DEFAULT_ORG_ID let refreshInterval @@ -146,7 +146,7 @@ const materializedViewQueryBuilder = async (model, concreteFields, metaFields) = .join(',\n') : '' // Empty string if there are no meta fields - const whereClause = utils.generateWhereClause(tableName) + const whereClause = whereClauseGeneratorHelper.generateWhereClause(tableName) const materializedViewGenerationQuery = `CREATE MATERIALIZED VIEW ${temporaryMaterializedViewName} AS SELECT diff --git a/src/helpers/entity.js b/src/helpers/entity.js new file mode 100644 index 000000000..33300bd1a --- /dev/null +++ b/src/helpers/entity.js @@ -0,0 +1,248 @@ +const validateInput = (input, validationData, modelName) => { + const errors = [] + for (const field of validationData) { + const fieldValue = input[field.value] + if (modelName && !field.model_names.includes(modelName) && input[field.value]) { + errors.push({ + param: field.value, + msg: `${field.value} is not allowed for the ${modelName} model.`, + }) + } + + function addError(field, value, dataType, message) { + errors.push({ + param: field.value, + msg: `${value} is invalid for data type ${dataType}. ${message}`, + }) + } + + if (fieldValue !== undefined) { + const dataType = field.data_type + + switch (dataType) { + case 'ARRAY[STRING]': + if (Array.isArray(fieldValue)) { + fieldValue.forEach((element) => { + if (typeof element !== 'string') { + addError(field, element, dataType, 'It should be a string') + } else if (field.allow_custom_entities && /[^A-Za-z0-9\s_]/.test(element)) { + addError( + field, + element, + dataType, + 'It should not contain special characters except underscore.' + ) + } + }) + } else { + addError(field, field.value, dataType, '') + } + break + + case 'STRING': + if (typeof fieldValue !== 'string') { + addError(field, fieldValue, dataType, 'It should be a string') + } else if (field.allow_custom_entities && /[^A-Za-z0-9\s_]/.test(fieldValue)) { + addError( + field, + fieldValue, + dataType, + 'It should not contain special characters except underscore.' + ) + } + break + + case 'NUMBER': + console.log('Type of', typeof fieldValue) + if (typeof fieldValue !== 'number') { + addError(field, fieldValue, dataType, '') + } + break + + default: + //isValid = false + break + } + } + + if (!fieldValue || field.allow_custom_entities === true || field.has_entities === false) { + continue // Skip validation if the field is not present in the input or allow_custom_entities is true + } + + if (Array.isArray(fieldValue)) { + for (const value of fieldValue) { + if (!field.entities.some((entity) => entity.value === value)) { + errors.push({ + param: field.value, + msg: `${value} is not a valid entity.`, + }) + } + } + } else if (!field.entities.some((entity) => entity.value === fieldValue)) { + errors.push({ + param: field.value, + msg: `${fieldValue} is not a valid entity.`, + }) + } + } + + if (errors.length === 0) { + return { + success: true, + message: 'Validation successful', + } + } + + return { + success: false, + errors: errors, + } +} + +const entityTypeMapGenerator = (entityTypeData) => { + try { + const entityTypeMap = new Map() + entityTypeData.forEach((entityType) => { + const labelsMap = new Map() + const entities = entityType.entities.map((entity) => { + labelsMap.set(entity.value, entity.label) + return entity.value + }) + if (!entityTypeMap.has(entityType.value)) { + const entityMap = new Map() + entityMap.set('allow_custom_entities', entityType.allow_custom_entities) + entityMap.set('entities', new Set(entities)) + entityMap.set('labels', labelsMap) + entityTypeMap.set(entityType.value, entityMap) + } + }) + return entityTypeMap + } catch (err) { + console.log(err) + } +} + +const restructureBody = (requestBody, entityData, allowedKeys) => { + try { + const entityTypeMap = entityTypeMapGenerator(entityData) + const doesAffectedFieldsExist = Object.keys(requestBody).some((element) => entityTypeMap.has(element)) + if (!doesAffectedFieldsExist) return requestBody + requestBody.custom_entity_text = {} + if (!requestBody.meta) requestBody.meta = {} + for (const currentFieldName in requestBody) { + const [currentFieldValue, isFieldValueAnArray] = Array.isArray(requestBody[currentFieldName]) + ? [[...requestBody[currentFieldName]], true] //If the requestBody[currentFieldName] is array, make a copy in currentFieldValue than a reference + : [requestBody[currentFieldName], false] + const entityType = entityTypeMap.get(currentFieldName) + if (entityType && entityType.get('allow_custom_entities')) { + if (isFieldValueAnArray) { + requestBody[currentFieldName] = [] + const recognizedEntities = [] + const customEntities = [] + for (const value of currentFieldValue) { + if (entityType.get('entities').has(value)) recognizedEntities.push(value) + else customEntities.push({ value: 'other', label: value }) + } + if (recognizedEntities.length > 0) + if (allowedKeys.includes(currentFieldName)) requestBody[currentFieldName] = recognizedEntities + else requestBody.meta[currentFieldName] = recognizedEntities + if (customEntities.length > 0) { + requestBody[currentFieldName].push('other') //This should cause error at DB write + requestBody.custom_entity_text[currentFieldName] = customEntities + } + } else { + if (!entityType.get('entities').has(currentFieldValue)) { + requestBody.custom_entity_text[currentFieldName] = { + value: 'other', + label: currentFieldValue, + } + if (allowedKeys.includes(currentFieldName)) + requestBody[currentFieldName] = 'other' //This should cause error at DB write + else requestBody.meta[currentFieldName] = 'other' + } else if (!allowedKeys.includes(currentFieldName)) + requestBody.meta[currentFieldName] = currentFieldValue + } + } + } + if (Object.keys(requestBody.meta).length === 0) requestBody.meta = null + if (Object.keys(requestBody.custom_entity_text).length === 0) requestBody.custom_entity_text = null + return requestBody + } catch (error) { + console.error(error) + } +} + +const processDbResponse = (responseBody, entityType) => { + if (responseBody.meta) { + entityType.forEach((entity) => { + const entityTypeValue = entity.value + if (responseBody?.meta?.hasOwnProperty(entityTypeValue)) { + // Move the key from responseBody.meta to responseBody root level + responseBody[entityTypeValue] = responseBody.meta[entityTypeValue] + // Delete the key from responseBody.meta + delete responseBody.meta[entityTypeValue] + } + }) + } + + const output = { ...responseBody } // Create a copy of the responseBody object + + for (const key in output) { + if (entityType.some((entity) => entity.value === key) && output[key] !== null) { + const matchingEntity = entityType.find((entity) => entity.value === key) + const matchingValues = matchingEntity.entities + .filter((entity) => (Array.isArray(output[key]) ? output[key].includes(entity.value) : true)) + .map((entity) => ({ + value: entity.value, + label: entity.label, + })) + if (matchingValues.length > 0) + output[key] = Array.isArray(output[key]) + ? matchingValues + : matchingValues.find((entity) => entity.value === output[key]) + else if (Array.isArray(output[key])) output[key] = output[key].filter((item) => item.value && item.label) + } + + if (output.meta && output.meta[key] && entityType.some((entity) => entity.value === output.meta[key].value)) { + const matchingEntity = entityType.find((entity) => entity.value === output.meta[key].value) + output.meta[key] = { + value: matchingEntity.value, + label: matchingEntity.label, + } + } + } + + const data = output + + // Merge "custom_entity_text" into the respective arrays + for (const key in data.custom_entity_text) { + if (Array.isArray(data[key])) data[key] = [...data[key], ...data.custom_entity_text[key]] + else data[key] = data.custom_entity_text[key] + } + delete data.custom_entity_text + return data +} + +const removeParentEntityTypes = (data) => { + const parentIds = data.filter((item) => item.parent_id !== null).map((item) => item.parent_id) + return data.filter((item) => !parentIds.includes(item.id)) +} + +const removeDefaultOrgEntityTypes = (entityTypes, orgId) => { + const entityTypeMap = new Map() + entityTypes.forEach((entityType) => { + if (!entityTypeMap.has(entityType.value)) entityTypeMap.set(entityType.value, entityType) + else if (entityType.organization_id === orgId) entityTypeMap.set(entityType.value, entityType) + }) + return Array.from(entityTypeMap.values()) +} + +const entity = { + validateInput, + restructureBody, + processDbResponse, + removeParentEntityTypes, + removeDefaultOrgEntityTypes, +} + +module.exports = entity diff --git a/src/helpers/whereClauseGenerator.js b/src/helpers/whereClauseGenerator.js new file mode 100644 index 000000000..52ca4842d --- /dev/null +++ b/src/helpers/whereClauseGenerator.js @@ -0,0 +1,20 @@ +const generateWhereClause = (tableName) => { + let whereClause = '' + + switch (tableName) { + case 'users': + whereClause = `deleted_at IS NULL AND status = 'ACTIVE'` + break + + default: + whereClause = 'deleted_at IS NULL' + } + + return whereClause +} + +const whereClauseGenerator = { + generateWhereClause, +} + +module.exports = whereClauseGenerator diff --git a/src/services/account.js b/src/services/account.js index 2a2f4a143..0e32aaa8a 100644 --- a/src/services/account.js +++ b/src/services/account.js @@ -10,7 +10,12 @@ const bcryptJs = require('bcryptjs') const jwt = require('jsonwebtoken') const _ = require('lodash') -const utilsHelper = require('@generics/utils') +const cacheUtils = require('@utils/cache') +const authUtils = require('@utils/auth') +const emailUtils = require('@utils/email') +const cloudUtils = require('@utils/cloud') +const genericUtils = require('@utils/generic') +const entityHelper = require('@helpers/entity') const httpStatusCode = require('@generics/http-status') const common = require('@constants/common') @@ -22,9 +27,8 @@ const roleQueries = require('@database/queries/user-role') const orgDomainQueries = require('@database/queries/orgDomain') const userInviteQueries = require('@database/queries/orgUserInvite') const entityTypeQueries = require('@database/queries/entityType') -const utils = require('@generics/utils') + const { Op } = require('sequelize') -const { removeDefaultOrgEntityTypes } = require('@generics/utils') const UserCredentialQueries = require('@database/queries/userCredential') const emailEncryption = require('@utils/emailEncryption') const responses = require('@helpers/responses') @@ -64,7 +68,7 @@ module.exports = class AccountHelper { } if (process.env.ENABLE_EMAIL_OTP_VERIFICATION === 'true') { - const redisData = await utilsHelper.redisGet(encryptedEmailId) + const redisData = await cacheUtils.redisGet(encryptedEmailId) if (!redisData || redisData.otp != bodyData.otp) { return responses.failureResponse({ message: 'OTP_INVALID', @@ -74,7 +78,7 @@ module.exports = class AccountHelper { } } - bodyData.password = utilsHelper.hashPassword(bodyData.password) + bodyData.password = authUtils.hashPassword(bodyData.password) //check user exist in invitee list let role, @@ -143,7 +147,7 @@ module.exports = class AccountHelper { bodyData.roles = roles } else { //find organization from email domain - let emailDomain = utilsHelper.extractDomainFromEmail(plaintextEmailId) + let emailDomain = emailUtils.extractDomainFromEmail(plaintextEmailId) let domainDetails = await orgDomainQueries.findOne({ domain: emailDomain, }) @@ -256,12 +260,12 @@ module.exports = class AccountHelper { .join(' and ') : '' - const accessToken = utilsHelper.generateToken( + const accessToken = authUtils.generateToken( tokenDetail, process.env.ACCESS_TOKEN_SECRET, common.accessTokenExpiry ) - const refreshToken = utilsHelper.generateToken( + const refreshToken = authUtils.generateToken( tokenDetail, process.env.REFRESH_TOKEN_SECRET, common.refreshTokenExpiry @@ -280,7 +284,7 @@ module.exports = class AccountHelper { } await userQueries.updateUser({ id: user.id, organization_id: userCredentials.organization_id }, update) - await utilsHelper.redisDel(encryptedEmailId) + await cacheUtils.redisDel(encryptedEmailId) //make the user as org admin if (isOrgAdmin) { @@ -309,7 +313,7 @@ module.exports = class AccountHelper { email: { to: plaintextEmailId, subject: templateData.subject, - body: utilsHelper.composeEmailBody(templateData.body, { + body: emailUtils.composeEmailBody(templateData.body, { name: bodyData.name, appName: process.env.APP_NAME, roles: roleToString || '', @@ -336,8 +340,8 @@ module.exports = class AccountHelper { model_names: { [Op.contains]: [modelName] }, }) - const prunedEntities = removeDefaultOrgEntityTypes(validationData, user.organization_id) - result.user = utils.processDbResponse(result.user, prunedEntities) + const prunedEntities = entityHelper.removeDefaultOrgEntityTypes(validationData, user.organization_id) + result.user = entityHelper.processDbResponse(result.user, prunedEntities) result.user.email = plaintextEmailId return responses.successResponse({ @@ -428,12 +432,12 @@ module.exports = class AccountHelper { }, } - const accessToken = utilsHelper.generateToken( + const accessToken = authUtils.generateToken( tokenDetail, process.env.ACCESS_TOKEN_SECRET, common.accessTokenExpiry ) - const refreshToken = utilsHelper.generateToken( + const refreshToken = authUtils.generateToken( tokenDetail, process.env.REFRESH_TOKEN_SECRET, common.refreshTokenExpiry @@ -483,11 +487,11 @@ module.exports = class AccountHelper { model_names: { [Op.contains]: [modelName] }, }) - const prunedEntities = removeDefaultOrgEntityTypes(validationData, user.organization_id) - user = utils.processDbResponse(user, prunedEntities) + const prunedEntities = entityHelper.removeDefaultOrgEntityTypes(validationData, user.organization_id) + user = entityHelper.processDbResponse(user, prunedEntities) if (user && user.image) { - user.image = await utils.getDownloadableUrl(user.image) + user.image = await cloudUtils.getDownloadableUrl(user.image) } user.email = plaintextEmailId const result = { access_token: accessToken, refresh_token: refreshToken, user } @@ -605,7 +609,7 @@ module.exports = class AccountHelper { } /* Generate new access token */ - const accessToken = utilsHelper.generateToken( + const accessToken = authUtils.generateToken( { data: decodedToken.data }, process.env.ACCESS_TOKEN_SECRET, common.accessTokenExpiry @@ -664,7 +668,7 @@ module.exports = class AccountHelper { responseCode: 'CLIENT_ERROR', }) - const userData = await utilsHelper.redisGet(encryptedEmailId) + const userData = await cacheUtils.redisGet(encryptedEmailId) const [otp, isNew] = userData && userData.action === 'forgetpassword' ? [userData.otp, false] @@ -675,7 +679,7 @@ module.exports = class AccountHelper { action: 'forgetpassword', otp, } - const res = await utilsHelper.redisSet(encryptedEmailId, redisData, common.otpExpirationTime) + const res = await cacheUtils.redisSet(encryptedEmailId, redisData, common.otpExpirationTime) if (res !== 'OK') return responses.failureResponse({ message: 'UNABLE_TO_SEND_OTP', @@ -694,7 +698,7 @@ module.exports = class AccountHelper { email: { to: plaintextEmailId, subject: templateData.subject, - body: utilsHelper.composeEmailBody(templateData.body, { name: user.name, otp }), + body: emailUtils.composeEmailBody(templateData.body, { name: user.name, otp }), }, } await kafkaCommunication.pushEmailToKafka(payload) @@ -735,7 +739,7 @@ module.exports = class AccountHelper { responseCode: 'CLIENT_ERROR', }) - const userData = await utilsHelper.redisGet(encryptedEmailId) + const userData = await cacheUtils.redisGet(encryptedEmailId) const [otp, isNew] = userData && userData.action === 'signup' ? [userData.otp, false] @@ -746,7 +750,7 @@ module.exports = class AccountHelper { action: 'signup', otp, } - const res = await utilsHelper.redisSet(encryptedEmailId, redisData, common.otpExpirationTime) + const res = await cacheUtils.redisSet(encryptedEmailId, redisData, common.otpExpirationTime) if (res !== 'OK') { return responses.failureResponse({ message: 'UNABLE_TO_SEND_OTP', @@ -764,7 +768,7 @@ module.exports = class AccountHelper { email: { to: plaintextEmailId, subject: templateData.subject, - body: utilsHelper.composeEmailBody(templateData.body, { name: bodyData.name, otp }), + body: emailUtils.composeEmailBody(templateData.body, { name: bodyData.name, otp }), }, } await kafkaCommunication.pushEmailToKafka(payload) @@ -830,7 +834,7 @@ module.exports = class AccountHelper { } user.user_roles = roles - const redisData = await utilsHelper.redisGet(encryptedEmailId) + const redisData = await cacheUtils.redisGet(encryptedEmailId) if (!redisData || redisData.otp != bodyData.otp) { return responses.failureResponse({ message: 'RESET_OTP_INVALID', @@ -846,7 +850,7 @@ module.exports = class AccountHelper { responseCode: 'CLIENT_ERROR', }) } - bodyData.password = utilsHelper.hashPassword(bodyData.password) + bodyData.password = authUtils.hashPassword(bodyData.password) const tokenDetail = { data: { id: user.id, @@ -856,8 +860,8 @@ module.exports = class AccountHelper { }, } - const accessToken = utilsHelper.generateToken(tokenDetail, process.env.ACCESS_TOKEN_SECRET, '1d') - const refreshToken = utilsHelper.generateToken(tokenDetail, process.env.REFRESH_TOKEN_SECRET, '183d') + const accessToken = authUtils.generateToken(tokenDetail, process.env.ACCESS_TOKEN_SECRET, '1d') + const refreshToken = authUtils.generateToken(tokenDetail, process.env.REFRESH_TOKEN_SECRET, '183d') let currentToken = { token: refreshToken, @@ -890,13 +894,13 @@ module.exports = class AccountHelper { }, { password: bodyData.password } ) - await utilsHelper.redisDel(encryptedEmailId) + await cacheUtils.redisDel(encryptedEmailId) delete user.password delete user.otpInfo // Check if user and user.image exist, then fetch a downloadable URL for the image - if (user && user.image) user.image = await utils.getDownloadableUrl(user.image) + if (user && user.image) user.image = await cloudUtils.getDownloadableUrl(user.image) const result = { access_token: accessToken, refresh_token: refreshToken, user } return responses.successResponse({ statusCode: httpStatusCode.ok, @@ -936,7 +940,7 @@ module.exports = class AccountHelper { const userDetailsFoundInRedis = [] for (let i = 0; i < userIds.length; i++) { let userDetails = - (await utilsHelper.redisGet(common.redisUserPrefix + userIds[i].toString())) || false + (await cacheUtils.redisGet(common.redisUserPrefix + userIds[i].toString())) || false if (!userDetails) { userIdsNotFoundInRedis.push(userIds[i]) @@ -974,7 +978,7 @@ module.exports = class AccountHelper { if (user.roles && user.roles.length > 0) { let roleData = roles.filter((role) => user.roles.includes(role.id)) user['user_roles'] = roleData - // await utilsHelper.redisSet(element._id.toString(), element) + // await cacheUtils.redisSet(element._id.toString(), element) } user.email = emailEncryption.decrypt(user.email) }) @@ -1009,7 +1013,7 @@ module.exports = class AccountHelper { users.data.map(async (user) => { /* Assigned image url from the stored location */ if (user.image) { - user.image = await utilsHelper.getDownloadableUrl(user.image) + user.image = await cloudUtils.getDownloadableUrl(user.image) } return user }) @@ -1081,7 +1085,7 @@ module.exports = class AccountHelper { { id: userId, organization_id: orgId }, { has_accepted_terms_and_conditions: true } ) - await utilsHelper.redisDel(common.redisUserPrefix + userId.toString()) + await cacheUtils.redisDel(common.redisUserPrefix + userId.toString()) return responses.successResponse({ statusCode: httpStatusCode.ok, @@ -1182,7 +1186,7 @@ module.exports = class AccountHelper { searchText = params.searchText.split(',') } searchText.forEach((element) => { - if (utils.isValidEmail(element)) { + if (genericUtils.isValidEmail(element)) { emailIds.push(emailEncryption.encrypt(element.toLowerCase())) } }) @@ -1204,7 +1208,7 @@ module.exports = class AccountHelper { users.data.map(async (user) => { /* Assigned image url from the stored location */ if (user.image) { - user.image = await utilsHelper.getDownloadableUrl(user.image) + user.image = await cloudUtils.getDownloadableUrl(user.image) } return user }) diff --git a/src/services/admin.js b/src/services/admin.js index f367d071d..a6ae1f3e8 100644 --- a/src/services/admin.js +++ b/src/services/admin.js @@ -9,7 +9,11 @@ const common = require('@constants/common') const httpStatusCode = require('@generics/http-status') -const utils = require('@generics/utils') + +const cacheUtils = require('@utils/cache') +const authUtils = require('@utils/auth') +const genericUtils = require('@utils/generic') + const _ = require('lodash') const userQueries = require('@database/queries/users') const roleQueries = require('@database/queries/user-role') @@ -47,7 +51,7 @@ module.exports = class AdminHelper { delete update.id await UserCredentialQueries.forceDeleteUserWithEmail(user.email) - await utils.redisDel(common.redisUserPrefix + userId.toString()) + await cacheUtils.redisDel(common.redisUserPrefix + userId.toString()) //code for remove user folder from cloud @@ -102,7 +106,7 @@ module.exports = class AdminHelper { ) bodyData.organization_id = organization.id } - bodyData.password = utils.hashPassword(bodyData.password) + bodyData.password = authUtils.hashPassword(bodyData.password) bodyData.email = encryptedEmailId const createdUser = await userQueries.create(bodyData) const userCredentialsBody = { @@ -162,7 +166,7 @@ module.exports = class AdminHelper { responseCode: 'CLIENT_ERROR', }) } - const isPasswordCorrect = utils.comparePassword(bodyData.password, user.password) + const isPasswordCorrect = authUtils.comparePassword(bodyData.password, user.password) if (!isPasswordCorrect) { return responses.failureResponse({ message: 'PASSWORD_INVALID', @@ -198,8 +202,8 @@ module.exports = class AdminHelper { user.user_roles = roles - const accessToken = utils.generateToken(tokenDetail, process.env.ACCESS_TOKEN_SECRET, '1d') - const refreshToken = utils.generateToken(tokenDetail, process.env.REFRESH_TOKEN_SECRET, '183d') + const accessToken = authUtils.generateToken(tokenDetail, process.env.ACCESS_TOKEN_SECRET, '1d') + const refreshToken = authUtils.generateToken(tokenDetail, process.env.REFRESH_TOKEN_SECRET, '183d') delete user.password const result = { access_token: accessToken, refresh_token: refreshToken, user } @@ -337,7 +341,7 @@ module.exports = class AdminHelper { ) //delete from cache const redisUserKey = common.redisUserPrefix + userId.toString() - await utils.redisDel(redisUserKey) + await cacheUtils.redisDel(redisUserKey) const roleData = await roleQueries.findAll( { id: roles, status: common.ACTIVE_STATUS }, @@ -566,7 +570,7 @@ function _generateUpdateParams(userId) { const updateUser = { deleted_at: new Date(), name: 'Anonymous User', - email: utils.md5Hash(userId) + '@' + 'deletedUser', + email: genericUtils.md5Hash(userId) + '@' + 'deletedUser', refresh_tokens: [], preferred_language: 'en', location: '', diff --git a/src/services/entityType.js b/src/services/entityType.js index 81c0dbbdb..8142912e2 100644 --- a/src/services/entityType.js +++ b/src/services/entityType.js @@ -1,11 +1,10 @@ -// DependenciesI +// Dependencies const httpStatusCode = require('@generics/http-status') -const common = require('@constants/common') const entityTypeQueries = require('@database/queries/entityType') const { UniqueConstraintError } = require('sequelize') const organizationQueries = require('@database/queries/organization') const { Op } = require('sequelize') -const { removeDefaultOrgEntityTypes } = require('@generics/utils') +const entityHelper = require('@helpers/entity') const responses = require('@helpers/responses') module.exports = class EntityHelper { /** @@ -128,7 +127,7 @@ module.exports = class EntityHelper { } const entities = await entityTypeQueries.findUserEntityTypesAndEntities(filter) - const prunedEntities = removeDefaultOrgEntityTypes(entities, orgId) + const prunedEntities = entityHelper.removeDefaultOrgEntityTypes(entities, orgId) return responses.successResponse({ statusCode: httpStatusCode.ok, message: 'ENTITY_TYPE_FETCHED_SUCCESSFULLY', diff --git a/src/services/files.js b/src/services/files.js index a5ba06c16..e047c2de7 100644 --- a/src/services/files.js +++ b/src/services/files.js @@ -8,8 +8,7 @@ // Dependencies const cloudServices = require('@generics/cloud-services') const httpStatusCode = require('@generics/http-status') -const common = require('@constants/common') -const utils = require('@generics/utils') +const cloudUtils = require('@utils/cloud') const responses = require('@helpers/responses') module.exports = class FilesHelper { @@ -55,7 +54,7 @@ module.exports = class FilesHelper { static async getDownloadableUrl(path) { try { - let response = await utils.getDownloadableUrl(path) + let response = await cloudUtils.getDownloadableUrl(path) return responses.successResponse({ message: 'DOWNLOAD_URL_GENERATED_SUCCESSFULLY', statusCode: httpStatusCode.ok, diff --git a/src/services/form.js b/src/services/form.js index 56aaf9254..0afea311a 100644 --- a/src/services/form.js +++ b/src/services/form.js @@ -1,7 +1,6 @@ const httpStatusCode = require('@generics/http-status') -const common = require('@constants/common') const formQueries = require('@database/queries/form') -const utils = require('@generics/utils') +const cacheUtils = require('@utils/cache') const KafkaProducer = require('@generics/kafka-communication') const form = require('@generics/form') const organizationQueries = require('@database/queries/organization') @@ -28,7 +27,7 @@ module.exports = class FormsHelper { } bodyData['organization_id'] = orgId await formQueries.create(bodyData) - await utils.internalDel('formVersion') + await cacheUtils.internalDel('formVersion') await KafkaProducer.clearInternalCache('formVersion') return responses.successResponse({ statusCode: httpStatusCode.created, @@ -70,7 +69,7 @@ module.exports = class FormsHelper { }) } - await utils.internalDel('formVersion') + await cacheUtils.internalDel('formVersion') await KafkaProducer.clearInternalCache('formVersion') return responses.successResponse({ statusCode: httpStatusCode.accepted, diff --git a/src/services/notification.js b/src/services/notification.js index 2350a7005..c01c55fe7 100644 --- a/src/services/notification.js +++ b/src/services/notification.js @@ -1,7 +1,5 @@ const httpStatusCode = require('@generics/http-status') -const common = require('@constants/common') const notificationTemplateQueries = require('@database/queries/notificationTemplate') -const utils = require('@generics/utils') const organizationQueries = require('@database/queries/organization') const responses = require('@helpers/responses') diff --git a/src/services/org-admin.js b/src/services/org-admin.js index 3b5d136bc..8983d11a7 100644 --- a/src/services/org-admin.js +++ b/src/services/org-admin.js @@ -9,7 +9,10 @@ const common = require('@constants/common') const httpStatusCode = require('@generics/http-status') -const utils = require('@generics/utils') +const cacheUtils = require('@utils/cache') +const emailUtils = require('@utils/email') +const cloudUtils = require('@utils/cloud') +const fileUtils = require('@utils/file') const _ = require('lodash') const userQueries = require('@database/queries/users') const kafkaCommunication = require('@generics/kafka-communication') @@ -48,7 +51,7 @@ module.exports = class OrgAdminHelper { const organization = await organizationQueries.findOne({ id: organization_id }, { attributes: ['name'] }) const creationData = { - name: utils.extractFilename(filePath), + name: fileUtils.extractFilename(filePath), input_path: filePath, type: common.fileTypeCSV, organization_id, @@ -66,7 +69,7 @@ module.exports = class OrgAdminHelper { } //push to queue - const redisConfiguration = utils.generateRedisConfigForQueue() + const redisConfiguration = cacheUtils.generateRedisConfigForQueue() const invitesQueue = new Queue(process.env.DEFAULT_QUEUE, redisConfiguration) await invitesQueue.add( 'upload_invites', @@ -123,9 +126,9 @@ module.exports = class OrgAdminHelper { await Promise.all( listFileUpload.data.map(async (upload) => { /* Assigned upload url from the stored location */ - upload.input_path = await utils.getDownloadableUrl(upload.input_path) + upload.input_path = await cloudUtils.getDownloadableUrl(upload.input_path) if (upload.output_path) { - upload.output_path = await utils.getDownloadableUrl(upload.output_path) + upload.output_path = await cloudUtils.getDownloadableUrl(upload.output_path) } return upload }) @@ -464,7 +467,7 @@ function updateRoleForApprovedRequest(requestDetails, user) { //delete from cache const redisUserKey = common.redisUserPrefix + requestDetails.requester_id.toString() - await utils.redisDel(redisUserKey) + await cacheUtils.redisDel(redisUserKey) return resolve({ success: true, @@ -502,7 +505,7 @@ async function sendRoleRequestStatusEmail(userDetails, status) { email: { to: emailEncryption.decrypt(userDetails.email), subject: templateData.subject, - body: utils.composeEmailBody(templateData.body, { + body: emailUtils.composeEmailBody(templateData.body, { name: userDetails.name, appName: process.env.APP_NAME, orgName: organization.name, diff --git a/src/services/organization.js b/src/services/organization.js index 76f61f18a..34d010f26 100644 --- a/src/services/organization.js +++ b/src/services/organization.js @@ -1,7 +1,6 @@ const httpStatusCode = require('@generics/http-status') const common = require('@constants/common') const organizationQueries = require('@database/queries/organization') -const utils = require('@generics/utils') const roleQueries = require('@database/queries/user-role') const orgRoleReqQueries = require('@database/queries/orgRoleRequest') const orgDomainQueries = require('@database/queries/orgDomain') @@ -16,6 +15,8 @@ const UserCredentialQueries = require('@database/queries/userCredential') const emailEncryption = require('@utils/emailEncryption') const { eventBodyDTO } = require('@dtos/eventBody') const responses = require('@helpers/responses') +const emailUtils = require('@utils/email') +const cacheUtils = require('@utils/cache') module.exports = class OrganizationsHelper { /** @@ -108,7 +109,7 @@ module.exports = class OrganizationsHelper { email: { to: plaintextEmailId, subject: templateData.subject, - body: utils.composeEmailBody(templateData.body, { + body: emailUtils.composeEmailBody(templateData.body, { name: inviteeData.name, role: common.ORG_ADMIN_ROLE, orgName: bodyData.name, @@ -124,7 +125,7 @@ module.exports = class OrganizationsHelper { } const cacheKey = common.redisOrgPrefix + createdOrganization.id.toString() - await utils.internalDel(cacheKey) + await cacheUtils.internalDel(cacheKey) const eventBody = eventBodyDTO({ entity: 'organization', @@ -224,7 +225,7 @@ module.exports = class OrganizationsHelper { } const cacheKey = common.redisOrgPrefix + id.toString() - await utils.internalDel(cacheKey) + await cacheUtils.internalDel(cacheKey) // await KafkaProducer.clearInternalCache(cacheKey) return responses.successResponse({ statusCode: httpStatusCode.accepted, @@ -251,7 +252,7 @@ module.exports = class OrganizationsHelper { const orgDetailsFoundInRedis = [] for (let i = 0; i < organizationIds.length; i++) { let orgDetails = - (await utils.redisGet(common.redisOrgPrefix + organizationIds[i].toString())) || false + (await cacheUtils.redisGet(common.redisOrgPrefix + organizationIds[i].toString())) || false if (!orgDetails) { orgIdsNotFoundInRedis.push(organizationIds[i]) diff --git a/src/services/user.js b/src/services/user.js index 194561be2..7e168f122 100644 --- a/src/services/user.js +++ b/src/services/user.js @@ -9,17 +9,20 @@ const httpStatusCode = require('@generics/http-status') const common = require('@constants/common') const userQueries = require('@database/queries/users') -const utils = require('@generics/utils') const roleQueries = require('@database/queries/user-role') const entitiesQueries = require('@database/queries/entities') const entityTypeQueries = require('@database/queries/entityType') const organizationQueries = require('@database/queries/organization') -const { removeDefaultOrgEntityTypes } = require('@generics/utils') const _ = require('lodash') const { Op } = require('sequelize') const { eventBroadcaster } = require('@helpers/eventBroadcaster') const emailEncryption = require('@utils/emailEncryption') const responses = require('@helpers/responses') +const cacheUtils = require('@utils/cache') +const cloudUtils = require('@utils/cloud') +const genericUtils = require('@utils/generic') +const entityHelper = require('@helpers/entity') +const roleUtils = require('@utils/role') const rolePermissionMappingQueries = require('@database/queries/role-permission-mapping') module.exports = class UserHelper { @@ -70,11 +73,11 @@ module.exports = class UserHelper { model_names: { [Op.contains]: [await userQueries.getModelName()] }, } let validationData = await entityTypeQueries.findUserEntityTypesAndEntities(filter) - const prunedEntities = removeDefaultOrgEntityTypes(validationData) + const prunedEntities = entityHelper.removeDefaultOrgEntityTypes(validationData) //validationData = utils.removeParentEntityTypes(JSON.parse(JSON.stringify(validationData))) - let res = utils.validateInput(bodyData, prunedEntities, await userQueries.getModelName()) + let res = entityHelper.validateInput(bodyData, prunedEntities, await userQueries.getModelName()) if (!res.success) { return responses.failureResponse({ message: 'SESSION_CREATION_FAILED', @@ -85,7 +88,7 @@ module.exports = class UserHelper { } let userModel = await userQueries.getColumns() - bodyData = utils.restructureBody(bodyData, validationData, userModel) + bodyData = entityHelper.restructureBody(bodyData, validationData, userModel) const [affectedRows, updatedData] = await userQueries.updateUser( { id: id, organization_id: orgId }, @@ -106,10 +109,10 @@ module.exports = class UserHelper { }) } const redisUserKey = common.redisUserPrefix + id.toString() - if (await utils.redisGet(redisUserKey)) { - await utils.redisDel(redisUserKey) + if (await cacheUtils.redisGet(redisUserKey)) { + await cacheUtils.redisDel(redisUserKey) } - const processDbResponse = utils.processDbResponse( + const processDbResponse = entityHelper.processDbResponse( JSON.parse(JSON.stringify(updatedData[0])), validationData ) @@ -168,14 +171,14 @@ module.exports = class UserHelper { try { let filter = {} - if (utils.isNumeric(id)) { + if (genericUtils.isNumeric(id)) { filter = { id: id } } else { filter = { share_link: id } } const redisUserKey = common.redisUserPrefix + id.toString() - const userDetails = (await utils.redisGet(redisUserKey)) || false + const userDetails = (await cacheUtils.redisGet(redisUserKey)) || false if (!userDetails) { let options = { attributes: { @@ -195,7 +198,7 @@ module.exports = class UserHelper { } if (user && user.image) { - user.image = await utils.getDownloadableUrl(user.image) + user.image = await cloudUtils.getDownloadableUrl(user.image) } let roles = await roleQueries.findAll( @@ -230,16 +233,16 @@ module.exports = class UserHelper { }, model_names: { [Op.contains]: [await userQueries.getModelName()] }, }) - const prunedEntities = removeDefaultOrgEntityTypes(validationData, user.organization_id) + const prunedEntities = entityHelper.removeDefaultOrgEntityTypes(validationData, user.organization_id) const permissionsByModule = await this.getPermissions(user.user_roles) user.permissions = permissionsByModule - const processDbResponse = utils.processDbResponse(user, prunedEntities) + const processDbResponse = entityHelper.processDbResponse(user, prunedEntities) processDbResponse.email = emailEncryption.decrypt(processDbResponse.email) - if (utils.validateRoleAccess(roles, common.MENTOR_ROLE)) { - await utils.redisSet(redisUserKey, processDbResponse) + if (roleUtils.validateRoleAccess(roles, common.MENTOR_ROLE)) { + await cacheUtils.redisSet(redisUserKey, processDbResponse) } return responses.successResponse({ @@ -282,7 +285,7 @@ module.exports = class UserHelper { let shareLink = user.share_link if (!shareLink) { - shareLink = utils.md5Hash(userId) + shareLink = genericUtils.md5Hash(userId) await userQueries.updateUser({ id: userId }, { share_link: shareLink }) } return responses.successResponse({ diff --git a/src/services/userInvite.js b/src/services/userInvite.js index bd6cb052d..86922ce03 100644 --- a/src/services/userInvite.js +++ b/src/services/userInvite.js @@ -7,7 +7,6 @@ // Dependencies const _ = require('lodash') -const utils = require('@generics/utils') const fs = require('fs') const path = require('path') const csv = require('csvtojson') @@ -30,6 +29,13 @@ const UserCredentialQueries = require('@database/queries/userCredential') const { Op } = require('sequelize') const emailEncryption = require('@utils/emailEncryption') +const cacheUtils = require('@utils/cache') +const cloudUtils = require('@utils/cloud') +const genericUtils = require('@utils/generic') +const roleUtils = require('@utils/role') +const fileUtils = require('@utils/file') +const emailUtils = require('@utils/email') + module.exports = class UserInviteHelper { static async uploadInvites(data) { return new Promise(async (resolve, reject) => { @@ -77,14 +83,14 @@ module.exports = class UserInviteHelper { ) if (templateData) { - const inviteeUploadURL = await utils.getDownloadableUrl(output_path) + const inviteeUploadURL = await cloudUtils.getDownloadableUrl(output_path) await this.sendInviteeEmail(templateData, data.user, inviteeUploadURL) //Rename this to function to generic name since this function is used for both Invitee & Org-admin. } } // delete the downloaded file and output file. - utils.clearFile(response.result.downloadPath) - utils.clearFile(createResponse.result.outputFilePath) + fileUtils.clearFile(response.result.downloadPath) + fileUtils.clearFile(createResponse.result.outputFilePath) return resolve({ success: true, @@ -102,7 +108,7 @@ module.exports = class UserInviteHelper { static async downloadCSV(filePath) { try { - const downloadableUrl = await utils.getDownloadableUrl(filePath) + const downloadableUrl = await cloudUtils.getDownloadableUrl(filePath) const fileName = path.basename(downloadableUrl) const downloadPath = path.join(inviteeFileDir, fileName) @@ -170,7 +176,7 @@ module.exports = class UserInviteHelper { static async createUserInvites(csvData, user, fileUploadId) { try { - const outputFileName = utils.generateFileName(common.inviteeOutputFile, common.csvExtension) + const outputFileName = fileUtils.generateFileName(common.inviteeOutputFile, common.csvExtension) let menteeRoleId, mentorRoleId //get all the roles and map title and id @@ -242,11 +248,10 @@ module.exports = class UserInviteHelper { //find the invalid fields and generate error message let invalidFields = [] - if (!utils.isValidName(invitee.name)) { + if (!genericUtils.isValidName(invitee.name)) { invalidFields.push('name') } - - if (!utils.isValidEmail(invitee.email)) { + if (!genericUtils.isValidEmail(invitee.email)) { invalidFields.push('email') } @@ -345,9 +350,9 @@ module.exports = class UserInviteHelper { { organization_id: user.organization_id } ) - const currentRoles = utils.getRoleTitlesFromId(existingUser.roles, roleList) + const currentRoles = roleUtils.getRoleTitlesFromId(existingUser.roles, roleList) let newRoles = [] - newRoles = utils.getRoleTitlesFromId( + newRoles = roleUtils.getRoleTitlesFromId( _.difference(userUpdateData.roles, existingUser.roles), roleList ) @@ -380,8 +385,8 @@ module.exports = class UserInviteHelper { //remove user data from redis const redisUserKey = common.redisUserPrefix + existingUser.id.toString() - await utils.redisDel(redisUserKey) - invitee.statusOrUserId = 'Success' + await cacheUtils.redisDel(redisUserKey) + invitee.statusOrUserId = 'success' } else { invitee.statusOrUserId = 'No updates needed. User details are already up to date' } @@ -416,7 +421,7 @@ module.exports = class UserInviteHelper { if (newUserCred?.id) { const { name, email } = invitee - const roles = utils.getRoleTitlesFromId(newInvitee.roles, roleList) + const roles = roleUtils.getRoleTitlesFromId(newInvitee.roles, roleList) const roleToString = roles.length > 0 ? roles @@ -455,7 +460,7 @@ module.exports = class UserInviteHelper { } //generate output csv - const csvContent = utils.generateCSVContent(input) + const csvContent = fileUtils.generateCSVContent(input) const outputFilePath = path.join(inviteeFileDir, outputFileName) fs.writeFileSync(outputFilePath, csvContent) @@ -529,9 +534,9 @@ module.exports = class UserInviteHelper { to: userData.email, subject: subjectComposeData && Object.keys(subjectComposeData).length > 0 - ? utils.composeEmailBody(templateData.subject, subjectComposeData) + ? emailUtils.composeEmailBody(templateData.subject, subjectComposeData) : templateData.subject, - body: utils.composeEmailBody(templateData.body, { + body: emailUtils.composeEmailBody(templateData.body, { name: userData.name, role: userData.role || '', orgName: userData.org_name || '', diff --git a/src/utils/auth.js b/src/utils/auth.js new file mode 100644 index 000000000..24158977d --- /dev/null +++ b/src/utils/auth.js @@ -0,0 +1,21 @@ +const { genSaltSync, hashSync, compare } = require('bcryptjs') +const jwt = require('jsonwebtoken') + +const generateToken = (tokenData, secretKey, expiresIn) => { + return jwt.sign(tokenData, secretKey, { expiresIn }) +} + +const hashPassword = (password) => { + const saltRounds = 10 + const salt = genSaltSync(saltRounds) + const hashedPassword = hashSync(password, salt) + return hashedPassword +} + +const comparePassword = async (password, hashedPassword) => { + return await compare(password, hashedPassword) +} + +const auth = { generateToken, hashPassword, comparePassword } + +module.exports = auth diff --git a/src/utils/cache.js b/src/utils/cache.js new file mode 100644 index 000000000..4e9c1ec2e --- /dev/null +++ b/src/utils/cache.js @@ -0,0 +1,68 @@ +const { RedisCache, InternalCache } = require('elevate-node-cache') + +const generateRedisConfigForQueue = () => { + const parseURL = new URL(process.env.REDIS_HOST) + return { + connection: { + host: parseURL.hostname, + port: parseURL.port, + }, + } +} + +/** + * Set a key-value pair in the internal cache. + * @param {string} key - The key to set. + * @param {any} value - The value to set. + * @returns {boolean} Returns true if the key-value pair is successfully set, otherwise false. + */ +const internalSet = (key, value) => InternalCache.setKey(key, value) + +/** + * Delete a key from the internal cache. + * @param {string} key - The key to delete. + * @returns {boolean} Returns true if the key is successfully deleted, otherwise false. + */ +const internalDel = (key) => InternalCache.delKey(key) + +/** + * Get the value associated with a key from the internal cache. + * @param {string} key - The key to get the value for. + * @returns {any} The value associated with the key, or undefined if the key does not exist. + */ +const internalGet = (key) => InternalCache.getKey(key) + +/** + * Set a key-value pair in the Redis cache. + * @param {string} key - The key to set. + * @param {any} value - The value to set. + * @param {number} exp - The expiration time for the key-value pair (in seconds). + * @returns {boolean} Returns true if the key-value pair is successfully set, otherwise false. + */ +const redisSet = (key, value, exp) => RedisCache.setKey(key, value, exp) + +/** + * Delete a key from the Redis cache. + * @param {string} key - The key to delete. + * @returns {boolean} Returns true if the key is successfully deleted, otherwise false. + */ +const redisDel = (key) => RedisCache.deleteKey(key) + +/** + * Get the value associated with a key from the Redis cache. + * @param {string} key - The key to get the value for. + * @returns {any} The value associated with the key, or undefined if the key does not exist. + */ +const redisGet = (key) => RedisCache.getKey(key) + +const cache = { + generateRedisConfigForQueue, + internalSet, + internalGet, + internalDel, + redisSet, + redisGet, + redisDel, +} + +module.exports = cache diff --git a/src/utils/cloud.js b/src/utils/cloud.js new file mode 100644 index 000000000..15ad56d4d --- /dev/null +++ b/src/utils/cloud.js @@ -0,0 +1,43 @@ +const { AwsFileHelper, GcpFileHelper, AzureFileHelper, OciFileHelper } = require('elevate-cloud-storage') +const path = require('path') + +const getDownloadableUrl = async (imgPath) => { + if (process.env.CLOUD_STORAGE === 'GCP') { + const options = { + destFilePath: imgPath, + bucketName: process.env.DEFAULT_GCP_BUCKET_NAME, + gcpProjectId: process.env.GCP_PROJECT_ID, + gcpJsonFilePath: path.join(__dirname, '../', process.env.GCP_PATH), + } + imgPath = await GcpFileHelper.getDownloadableUrl(options) + } else if (process.env.CLOUD_STORAGE === 'AWS') { + const options = { + destFilePath: imgPath, + bucketName: process.env.DEFAULT_AWS_BUCKET_NAME, + bucketRegion: process.env.AWS_BUCKET_REGION, + } + imgPath = await AwsFileHelper.getDownloadableUrl(options.destFilePath, options.bucketName, options.bucketRegion) + } else if (process.env.CLOUD_STORAGE === 'AZURE') { + const options = { + destFilePath: imgPath, + containerName: process.env.DEFAULT_AZURE_CONTAINER_NAME, + expiry: 30, + actionType: 'rw', + accountName: process.env.AZURE_ACCOUNT_NAME, + accountKey: process.env.AZURE_ACCOUNT_KEY, + } + imgPath = await AzureFileHelper.getDownloadableUrl(options) + } else if (process.env.CLOUD_STORAGE === 'OCI') { + const options = { + destFilePath: imgPath, + bucketName: process.env.DEFAULT_OCI_BUCKET_NAME, + endpoint: process.env.OCI_BUCKET_ENDPOINT, + } + imgPath = await OciFileHelper.getDownloadableUrl(options) + } + return imgPath +} + +const cloud = { getDownloadableUrl } + +module.exports = cloud diff --git a/src/utils/email.js b/src/utils/email.js new file mode 100644 index 000000000..a36b4a64b --- /dev/null +++ b/src/utils/email.js @@ -0,0 +1,13 @@ +const composeEmailBody = (body, params) => { + return body.replace(/{([^{}]*)}/g, (a, b) => { + var r = params[b] + return typeof r === 'string' || typeof r === 'number' ? r : a + }) +} +const extractDomainFromEmail = (email) => { + return email.substring(email.lastIndexOf('@') + 1) +} + +const email = { composeEmailBody, extractDomainFromEmail } + +module.exports = email diff --git a/src/utils/file.js b/src/utils/file.js new file mode 100644 index 000000000..1c7f36aff --- /dev/null +++ b/src/utils/file.js @@ -0,0 +1,37 @@ +const moment = require('moment-timezone') +const fs = require('fs') +const { elevateLog } = require('elevate-logger') +const logger = elevateLog.init() + +const clearFile = (filePath) => { + fs.unlink(filePath, (err) => { + if (err) logger.error(err) + }) +} + +const generateFileName = (name, extension) => { + const currentDate = new Date() + const fileExtensionWithTime = moment(currentDate).tz('Asia/Kolkata').format('YYYY_MM_DD_HH_mm') + extension + return name + fileExtensionWithTime +} + +const extractFilename = (fileString) => { + const match = fileString.match(/([^/]+)(?=\.\w+$)/) + return match ? match[0] : null +} + +const generateCSVContent = (data) => { + // If data is empty + if (data.length === 0) { + return 'No Data Found' + } + const headers = Object.keys(data[0]) + return [ + headers.join(','), + ...data.map((row) => headers.map((fieldName) => JSON.stringify(row[fieldName])).join(',')), + ].join('\n') +} + +const file = { clearFile, generateFileName, extractFilename, generateCSVContent } + +module.exports = file diff --git a/src/utils/generic.js b/src/utils/generic.js index a3313cf7b..58bccaa15 100644 --- a/src/utils/generic.js +++ b/src/utils/generic.js @@ -1,5 +1,43 @@ 'use strict' +const md5 = require('md5') + +/** + * Check if an object is empty. + * @param {Object} obj - The object to check. + * @returns {boolean} Returns true if the object is empty, false otherwise. + */ exports.isEmpty = (obj) => { for (let i in obj) return false return true } + +/** + * Generate an MD5 hash for the given value. + * @param {string} value - The value to hash. + * @returns {string} Returns the MD5 hash. + */ +exports.md5Hash = (value) => md5(value) + +/** + * Check if a value is numeric. + * @param {string} value - The value to check. + * @returns {boolean} Returns true if the value is numeric, false otherwise. + */ +exports.isNumeric = (value) => /^\d+$/.test(value) + +/** + * Check if a name is valid. + * @param {string} name - The name to check. + * @returns {boolean} Returns true if the name is valid, false otherwise. + */ +exports.isValidName = (name) => /^[A-Za-z\s'-]+$/.test(name) + +/** + * Check if an email address is valid. + * @param {string} email - The email address to check. + * @returns {boolean} Returns true if the email address is valid, false otherwise. + */ +exports.isValidEmail = (email) => { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ + return emailRegex.test(email) +} diff --git a/src/utils/role.js b/src/utils/role.js new file mode 100644 index 000000000..ea78cabdb --- /dev/null +++ b/src/utils/role.js @@ -0,0 +1,23 @@ +const validateRoleAccess = (roles, requiredRoles) => { + if (!roles || roles.length === 0) return false + + if (!Array.isArray(requiredRoles)) { + requiredRoles = [requiredRoles] + } + + return roles.some((role) => requiredRoles.includes(role.title)) +} + +const getRoleTitlesFromId = (roleIds = [], roleList = []) => { + return roleIds.map((roleId) => { + const role = roleList.find((r) => r.id === roleId) + return role ? role.title : null + }) +} + +const role = { + validateRoleAccess, + getRoleTitlesFromId, +} + +module.exports = role