From 26d2da4a641ab2dd1a8ad09dcec5e4b3996e9a3d Mon Sep 17 00:00:00 2001 From: prajwal Date: Wed, 23 Apr 2025 17:39:43 +0530 Subject: [PATCH 01/91] implemented saas-policy changes --- src/controllers/v1/entities.js | 25 ++- src/controllers/v1/entityTypes.js | 29 +++- src/controllers/v1/userRoleExtension.js | 2 +- src/generics/constants/api-responses.js | 2 + src/generics/constants/common.js | 1 + src/generics/middleware/authenticator.js | 32 ++++ src/models/entities.js | 12 ++ src/models/entityTypes.js | 14 +- src/models/userRoleExtension.js | 7 + src/module/entities/helper.js | 157 ++++++++++++++----- src/module/entities/validator/v1.js | 22 +++ src/module/entityTypes/helper.js | 73 ++++++--- src/module/userRoleExtension/helper.js | 2 +- src/module/userRoleExtension/validator/v1.js | 22 +++ 14 files changed, 330 insertions(+), 70 deletions(-) diff --git a/src/controllers/v1/entities.js b/src/controllers/v1/entities.js index 2f33685..e26be9f 100644 --- a/src/controllers/v1/entities.js +++ b/src/controllers/v1/entities.js @@ -195,7 +195,8 @@ module.exports = class Entities extends Abstract { req.pageNo, req.pageSize, req?.query?.paginate?.toLowerCase() == 'true' ? true : false, - req.query.language ? req.query.language : '' + req.query.language ? req.query.language : '', + req.userDetails ) return resolve(entityData) } catch (error) { @@ -386,7 +387,9 @@ module.exports = class Entities extends Abstract { req.pageNo, req.pageSize, req?.query?.paginate?.toLowerCase() == 'true' ? true : false, - req.query.entityType ? req.query.entityType : '' + req.query.entityType ? req.query.entityType : '', + req.query.language ? req.query.language : '', + req.userDetails.userInformation.tenantId ) // Resolves the promise with the retrieved entity data return resolve(userRoleDetails) @@ -452,7 +455,8 @@ module.exports = class Entities extends Abstract { let result = await entitiesHelper.details( req.params._id ? req.params._id : '', req.body ? req.body : {}, - req.query.language ? req.query.language : '' + req.query.language ? req.query.language : '', + req.userDetails ) return resolve(result) @@ -509,7 +513,7 @@ module.exports = class Entities extends Abstract { return new Promise(async (resolve, reject) => { try { // Call 'entitiesHelper.update' to perform the entity update operation - let result = await entitiesHelper.update(req.params._id, req.body) + let result = await entitiesHelper.update(req.params._id, req.body, req.userDetails) return resolve(result) } catch (error) { @@ -620,7 +624,7 @@ module.exports = class Entities extends Abstract { return new Promise(async (resolve, reject) => { try { // Call 'entitiesHelper.listByLocationIds' to retrieve entities based on location IDs - let entitiesData = await entitiesHelper.listByLocationIds(req.body.locationIds) + let entitiesData = await entitiesHelper.listByLocationIds(req.body.locationIds, req.userDetails) entitiesData.result = entitiesData.data @@ -676,7 +680,10 @@ module.exports = class Entities extends Abstract { return new Promise(async (resolve, reject) => { try { // Call 'entitiesHelper.subEntityListBasedOnRoleAndLocation' to retrieve sub-entity list - const entityTypeMappingData = await entitiesHelper.subEntityListBasedOnRoleAndLocation(req.params._id) + const entityTypeMappingData = await entitiesHelper.subEntityListBasedOnRoleAndLocation( + req.params._id, + req.userDetails + ) return resolve(entityTypeMappingData) } catch (error) { return reject({ @@ -885,7 +892,11 @@ module.exports = class Entities extends Abstract { return new Promise(async (resolve, reject) => { try { // Call 'entitiesHelper.listByEntityIds' to retrieve entities based on provided entity IDs and fields - const entities = await entitiesHelper.listByEntityIds(req.body.entities, req.body.fields) + const entities = await entitiesHelper.listByEntityIds( + req.body.entities, + req.body.fields, + req.userDetails + ) return resolve(entities) } catch (error) { return reject({ diff --git a/src/controllers/v1/entityTypes.js b/src/controllers/v1/entityTypes.js index 21bea33..55a2a31 100644 --- a/src/controllers/v1/entityTypes.js +++ b/src/controllers/v1/entityTypes.js @@ -57,12 +57,31 @@ module.exports = class EntityTypes extends Abstract { } ] */ - async list() { + async list(req) { return new Promise(async (resolve, reject) => { try { - // Call 'entityTypesHelper.list' to retrieve a list of entity types - // 'all' parameter retrieves all entity types, and { name: 1 } specifies projection to include only 'name' field - let result = await entityTypesHelper.list('all', { name: 1 }) + let tenantId + let organizationId + let query = {} + let userRoles = req.userDetails.userInformation.roles ? req.userDetails.userInformation.roles : [] + + // create query to fetch assets as a SUPER_ADMIN + if (userRoles.includes(CONSTANTS.common.ADMIN_ROLE)) { + tenantId = req.userDetails.tenantAndOrgInfo.tenantId + query['orgId'] = { $in: req.userDetails.tenantAndOrgInfo.orgId } + } + // create query to fetch assets as a normal user + else { + tenantId = req.userDetails.userInformation.tenantId + } + query['tenantId'] = tenantId + + // handle currentOrgOnly filter + if (req.query['currentOrgOnly'] && req.query['currentOrgOnly'] == 'true') { + organizationId = req.userDetails.userInformation.organizationId + query['orgId'] = { $in: [organizationId] } + } + let result = await entityTypesHelper.list(query, { name: 1 }) return resolve(result) } catch (error) { @@ -199,7 +218,7 @@ module.exports = class EntityTypes extends Abstract { return new Promise(async (resolve, reject) => { try { // Call 'entityTypesHelper.update' to update an existing entity type - let result = await entityTypesHelper.update(req.params._id, req.body, req.userDetails.userInformation) + let result = await entityTypesHelper.update(req.params._id, req.body, req.userDetails) return resolve(result) } catch (error) { diff --git a/src/controllers/v1/userRoleExtension.js b/src/controllers/v1/userRoleExtension.js index c153d81..376f0ef 100644 --- a/src/controllers/v1/userRoleExtension.js +++ b/src/controllers/v1/userRoleExtension.js @@ -76,7 +76,7 @@ module.exports = class userRoleExtension extends Abstract { return new Promise(async (resolve, reject) => { try { // Call the helper function to create a new user role extension document - let result = await userRoleExtensionHelper.create(req.body, req.userDetails) + let result = await userRoleExtensionHelper.create(req.body) // Resolve the promise with the result of the creation operation return resolve(result) diff --git a/src/generics/constants/api-responses.js b/src/generics/constants/api-responses.js index 3bfb2f7..8f997cb 100644 --- a/src/generics/constants/api-responses.js +++ b/src/generics/constants/api-responses.js @@ -60,4 +60,6 @@ module.exports = { FIELD_MISSING: 'Fields are missing', ENTITY_TYPE_CREATION_FAILED: 'ENTITY TYPE CREATION FAILED', MAPPING_CSV_GENERATED: 'MAPPING_CSV_GENERATED', + INVALID_TENANT_AND_ORG_CODE: 'ERR_TENANT_AND_ORG_INVALID', + INVALID_TENANT_AND_ORG_MESSAGE: 'Invalid tenant and org info', } diff --git a/src/generics/constants/common.js b/src/generics/constants/common.js index 2424f7d..7937e7a 100644 --- a/src/generics/constants/common.js +++ b/src/generics/constants/common.js @@ -36,4 +36,5 @@ module.exports = { KEYCLOAK_PUBLIC_KEY: 'keycloak_public_key', }, ENGLISH_LANGUGE_CODE: 'en', + ADMIN_ROLE: 'admin', } diff --git a/src/generics/middleware/authenticator.js b/src/generics/middleware/authenticator.js index 166d310..d1d7d58 100644 --- a/src/generics/middleware/authenticator.js +++ b/src/generics/middleware/authenticator.js @@ -46,6 +46,11 @@ module.exports = async function (req, res, next, token = '') { let internalAccessApiPaths = CONSTANTS.common.INTERNAL_ACCESS_URLS let performInternalAccessTokenCheck = false + let adminHeader = false + if (process.env.ADMIN_ACCESS_TOKEN) { + adminHeader = req.headers[process.env.ADMIN_TOKEN_HEADER_NAME] + } + await Promise.all( internalAccessApiPaths.map(async function (path) { if (req.path.includes(path)) { @@ -160,6 +165,28 @@ module.exports = async function (req, res, next, token = '') { decodedToken = decodedToken || {} decodedToken['data'] = data } + if (adminHeader) { + if (adminHeader != process.env.ADMIN_ACCESS_TOKEN) { + return res.status(HTTP_STATUS_CODE['unauthorized'].status).send(respUtil(rspObj)) + } + if ( + !req.headers['tenantid'] || + !req.headers['orgid'] || + !req.headers['tenantid'].length || + !req.headers['orgid'].length + ) { + rspObj.errCode = CONSTANTS.apiResponses.INVALID_TENANT_AND_ORG_CODE + rspObj.errMsg = CONSTANTS.apiResponses.INVALID_TENANT_AND_ORG_MESSAGE + rspObj.responseCode = HTTP_STATUS_CODE['unauthorized'].status + return res.status(HTTP_STATUS_CODE['unauthorized'].status).send(respUtil(rspObj)) + } + decodedToken.data['tenantAndOrgInfo'] = {} + + decodedToken.data.tenantAndOrgInfo['tenantId'] = req.get('tenantid').toString() + + decodedToken.data.tenantAndOrgInfo['orgId'] = req.get('orgid').split(',') + decodedToken.data.roles.push({ title: CONSTANTS.common.ADMIN_ROLE }) + } } catch (err) { rspObj.errCode = CONSTANTS.apiResponses.TOKEN_MISSING_CODE rspObj.errMsg = CONSTANTS.apiResponses.TOKEN_MISSING_MESSAGE @@ -180,7 +207,12 @@ module.exports = async function (req, res, next, token = '') { // email : decodedToken.data.email, //email is removed from token firstName: decodedToken.data.name, roles: decodedToken.data.roles.map((role) => role.title), + organizationId: decodedToken.data.organization_id ? decodedToken.data.organization_id.toString() : null, + tenantId: decodedToken.data.tenant_id.toString(), }, } + if (decodedToken.data.tenantAndOrgInfo) { + req.userDetails.tenantAndOrgInfo = decodedToken.data.tenantAndOrgInfo + } next() } diff --git a/src/models/entities.js b/src/models/entities.js index 1fdeab8..a55b377 100644 --- a/src/models/entities.js +++ b/src/models/entities.js @@ -37,5 +37,17 @@ module.exports = { type: String, default: 'SYSTEM', }, + tenantId: { + type: String, + }, + orgId: { + type: Array, + }, }, + compoundIndex: [ + { + name: { 'metaInformation.name': 1, 'metaInformation.externalId': 1, tenantId: 1, entityTypeId: 1 }, + indexType: { unique: true }, + }, + ], } diff --git a/src/models/entityTypes.js b/src/models/entityTypes.js index c5641c5..7dfee0d 100644 --- a/src/models/entityTypes.js +++ b/src/models/entityTypes.js @@ -10,8 +10,6 @@ module.exports = { schema: { name: { type: String, - index: true, - unique: true, }, profileForm: Array, profileFields: Array, @@ -34,5 +32,17 @@ module.exports = { type: String, default: 'SYSTEM', }, + tenantId: { + type: String, + }, + orgId: { + type: Array, + }, }, + compoundIndex: [ + { + name: { name: 1, tenantId: 1 }, + indexType: { unique: true }, + }, + ], } diff --git a/src/models/userRoleExtension.js b/src/models/userRoleExtension.js index 03dfa95..1b87a2d 100644 --- a/src/models/userRoleExtension.js +++ b/src/models/userRoleExtension.js @@ -22,6 +22,7 @@ module.exports = { }, entityTypes: [ { + _id: false, entityType: { type: String }, entityTypeId: { type: String, index: true }, }, @@ -38,5 +39,11 @@ module.exports = { type: String, index: true, }, + tenantId: { + type: String, + }, + orgId: { + type: Array, + }, }, } diff --git a/src/module/entities/helper.js b/src/module/entities/helper.js index 2857f86..894980d 100644 --- a/src/module/entities/helper.js +++ b/src/module/entities/helper.js @@ -181,13 +181,20 @@ module.exports = class UserProjectsHelper { * @returns {Array} List of Entities. */ - static listByEntityIds(entityIds = [], fields = []) { + static listByEntityIds(entityIds = [], fields = [], userDetails) { return new Promise(async (resolve, reject) => { try { // Call 'entitiesQueries.entityDocuments' to retrieve entities based on provided entity IDs and fields + let tenantId + if (userDetails.userInformation.roles.includes(CONSTANTS.common.ADMIN_ROLE)) { + tenantId = userDetails.tenantAndOrgInfo.tenantId + } else { + tenantId = userDetails.userInformation.tenantId + } const entities = await entitiesQueries.entityDocuments( { _id: { $in: entityIds }, + tenantId: tenantId, }, fields ? fields : [] ) @@ -315,9 +322,10 @@ module.exports = class UserProjectsHelper { * @param {params} pageSize - page pageSize. * @param {params} pageNo - page no. * @param {String} type - Entity type + * @param {String} tenantId - user's tenantId * @returns {Promise} A promise that resolves to the response containing the fetched roles or an error object. */ - static targetedRoles(entityId, pageNo = '', pageSize = '', paginate, type = '') { + static targetedRoles(entityId, pageNo = '', pageSize = '', paginate, type = '', language, tenantId) { return new Promise(async (resolve, reject) => { try { // Construct the filter to retrieve entities based on provided entity IDs @@ -361,6 +369,7 @@ module.exports = class UserProjectsHelper { name: { $in: filteredHierarchyPaths, }, + tenantId: tenantId, isDeleted: false, } const entityTypeProjection = ['_id'] @@ -930,7 +939,7 @@ module.exports = class UserProjectsHelper { * @returns {Array} List of sub entity type. */ - static subEntityListBasedOnRoleAndLocation(stateLocationId) { + static subEntityListBasedOnRoleAndLocation(stateLocationId, userDetails) { return new Promise(async (resolve, reject) => { try { // let rolesDocument = await userRolesHelper.roleDocuments({ @@ -943,9 +952,15 @@ module.exports = class UserProjectsHelper { // message: CONSTANTS.apiResponses.USER_ROLES_NOT_FOUND // } // } - + let tenantId + if (userDetails.userInformation.roles.includes(CONSTANTS.common.ADMIN_ROLE)) { + tenantId = userDetails.tenantAndOrgInfo.tenantId + } else { + tenantId = userDetails.userInformation.tenantId + } let filterQuery = { 'registryDetails.code': stateLocationId, + tenantId: tenantId, } // Check if stateLocationId is a valid UUID and update the filterQuery accordingly @@ -1011,9 +1026,15 @@ module.exports = class UserProjectsHelper { * @returns {Object} entity Document */ - static listByLocationIds(locationIds) { + static listByLocationIds(locationIds, userDetails) { return new Promise(async (resolve, reject) => { try { + let tenantId + if (userDetails.userInformation.roles.includes(CONSTANTS.common.ADMIN_ROLE)) { + tenantId = userDetails.tenantAndOrgInfo.tenantId + } else { + tenantId = userDetails.userInformation.tenantId + } // Constructing the filter query to find entities based on locationIds let filterQuery = { $or: [ @@ -1024,6 +1045,7 @@ module.exports = class UserProjectsHelper { 'registryDetails.locationId': { $in: locationIds }, }, ], + tenantId: tenantId, } // Retrieving entities that match the filter query @@ -1092,19 +1114,32 @@ module.exports = class UserProjectsHelper { * @param {string} pageNo - pageNo for pagination * @param {string} language - language Code * @param {string} pageSize - pageSize for pagination + * @param {Boolean} currentOrgOnly - boolean value to fetch current org assets + * @param {Object} userDetails - user decoded token details * @returns {Promise} Promise that resolves with fetched documents or rejects with an error. */ - static entityListBasedOnEntityType(type, pageNo, pageSize, paginate, language) { + static entityListBasedOnEntityType(type, pageNo, pageSize, paginate, language, userDetails) { return new Promise(async (resolve, reject) => { try { + let tenantId + let organizationId + let query = {} + let userRoles = userDetails.userInformation.roles ? userDetails.userInformation.roles : [] + + // create query to fetch assets as a SUPER_ADMIN + if (userRoles.includes(CONSTANTS.common.ADMIN_ROLE)) { + tenantId = userDetails.tenantAndOrgInfo.tenantId + } + // create query to fetch assets as a normal user + else { + tenantId = userDetails.userInformation.tenantId + } + + query['tenantId'] = tenantId + query['name'] = type // Fetch the list of entity types available - const entityList = await entityTypeQueries.entityTypesDocument( - { - name: type, - }, - ['name'] - ) + const entityList = await entityTypeQueries.entityTypesDocument(query, ['name']) // Check if entity list is empty if (!entityList.length > 0) { throw { @@ -1113,21 +1148,21 @@ module.exports = class UserProjectsHelper { } } const projection = ['_id', 'metaInformation.name', 'metaInformation.externalId', 'translations'] + delete query.name + query['entityType'] = type // Fetch documents for the matching entity type let fetchList = await entitiesQueries.entityDocuments( - { - entityType: type, - }, + query, projection, pageSize, pageSize * (pageNo - 1), '', paginate ) - const count = await entitiesQueries.countEntityDocuments({ entityType: type }) + // const count = await entitiesQueries.countEntityDocuments({ entityType: type }) // Check if fetchList list is empty - if (count <= 0) { + if (!(fetchList.length > 0)) { throw { status: HTTP_STATUS_CODE.not_found.status, message: CONSTANTS.apiResponses.ENTITY_NOT_FOUND, @@ -1162,8 +1197,8 @@ module.exports = class UserProjectsHelper { return resolve({ success: true, message: CONSTANTS.apiResponses.ASSETS_FETCHED_SUCCESSFULLY, - result: result, - count, + result, + count: result.length, }) } catch (error) { return reject(error) @@ -1178,7 +1213,6 @@ module.exports = class UserProjectsHelper { * @param {Object} queryParams - requested query data. * @param {Object} data - requested entity data. * @param {Object} userDetails - Logged in user information. - * @param {String} userDetails.id - Logged in user id. * @returns {JSON} - Created entity information. */ @@ -1186,16 +1220,28 @@ module.exports = class UserProjectsHelper { return new Promise(async (resolve, reject) => { try { // Find the entities document based on the entityType in queryParams - let entityTypeDocument = await entityTypeQueries.findOne({ name: queryParams.type }, { _id: 1 }) + + let tenantId + if (userDetails.userInformation.roles.includes(CONSTANTS.common.ADMIN_ROLE)) { + tenantId = userDetails.tenantAndOrgInfo.tenantId + } else { + tenantId = userDetails.userInformation.tenantId + } + let entityTypeDocument = await entityTypeQueries.findOne( + { name: queryParams.type, tenantId: tenantId }, + { _id: 1 } + ) if (!entityTypeDocument) { - throw CONSTANTS.apiResponses.ENTITY_NOT_FOUND + throw { + status: HTTP_STATUS_CODE.bad_request.status, + message: CONSTANTS.apiResponses.ENTITY_NOT_FOUND, + } } let entityDocuments = [] let dataArray = Array.isArray(data) ? data : [data] for (let pointer = 0; pointer < dataArray.length; pointer++) { let singleEntity = dataArray[pointer] - if (singleEntity.createdByProgramId) { singleEntity.createdByProgramId = ObjectId(singleEntity.createdByProgramId) } @@ -1219,6 +1265,7 @@ module.exports = class UserProjectsHelper { name: { $in: singleEntity.childHierarchyPath, }, + tenantId: tenantId, }, // Specify to return only the 'name' field of matching documents @@ -1241,9 +1288,11 @@ module.exports = class UserProjectsHelper { registryDetails: registryDetails, groups: {}, metaInformation: _.omit(singleEntity, ['locationId', 'code']), - updatedBy: userDetails.userId, - createdBy: userDetails.userId, - userId: userDetails.userId, + updatedBy: userDetails.userInformation.userId, + createdBy: userDetails.userInformation.userId, + userId: userDetails.userInformation.userId, + tenantId: tenantId, + orgId: singleEntity.orgId, } entityDocuments.push(entityDoc) @@ -1286,10 +1335,11 @@ module.exports = class UserProjectsHelper { * @param {ObjectId} entityId - entity Id. * @param {Object} requestData - requested data. * @param {String} language - language code. + * @param {Object} userDetails - user decoded token details * @returns {JSON} - provide the details. */ - static details(entityId, requestData = {}, language) { + static details(entityId, requestData = {}, language, userDetails) { return new Promise(async (resolve, reject) => { try { // // let entityIdNum = parseInt(entityId) @@ -1333,6 +1383,12 @@ module.exports = class UserProjectsHelper { }, }) } + // add tenantId to the query + if (userDetails.userInformation.roles.includes(CONSTANTS.common.ADMIN_ROLE)) { + query['tenantId'] = userDetails.tenantAndOrgInfo.tenantId + } else { + query['tenantId'] = userDetails.userInformation.tenantId + } // Fetch entity documents based on constructed query let entityDocument = await entitiesQueries.entityDocuments(query, 'all', 10) @@ -1428,22 +1484,35 @@ module.exports = class UserProjectsHelper { // } // Find the entity type document based on the provided entityType + let tenantId + if (userDetails.userInformation.roles.includes(CONSTANTS.common.ADMIN_ROLE)) { + tenantId = userDetails.tenantAndOrgInfo.tenantId + } else { + tenantId = userDetails.userInformation.tenantId + } + let entityTypeDocument = await entityTypeQueries.findOne( { name: entityType, + tenantId: tenantId, }, - { _id: 1 } + { _id: 1, tenantId: 1 } ) if (!entityTypeDocument) { - throw CONSTANTS.apiResponses.INVALID_ENTITY_TYPE + throw { + status: HTTP_STATUS_CODE.bad_request.status, + message: CONSTANTS.apiResponses.INVALID_ENTITY_TYPE, + } } - // Process each entity in the entityCSVData array to create new entities const entityUploadedData = await Promise.all( entityCSVData.map(async (singleEntity) => { singleEntity = UTILS.valueParser(singleEntity) addTagsInEntities(singleEntity) - const userId = userDetails && userDetails.id ? userDetails.id : CONSTANTS.common.SYSTEM + const userId = + userDetails && userDetails.userInformation.userId + ? userDetails.userInformation.userId + : CONSTANTS.common.SYSTEM let entityCreation = { entityTypeId: entityTypeDocument._id, entityType: entityType, @@ -1451,6 +1520,8 @@ module.exports = class UserProjectsHelper { groups: {}, updatedBy: userId, createdBy: userId, + tenantId: userDetails.userInformation.tenantId, + orgId: singleEntity.orgId, } // if (singleEntity.allowedRoles && singleEntity.allowedRoles.length > 0) { // entityCreation['allowedRoles'] = await allowedRoles(singleEntity.allowedRoles) @@ -1650,12 +1721,22 @@ module.exports = class UserProjectsHelper { * @returns {JSON} - Updated entity information. */ - static update(entityId, bodyData) { + static update(entityId, bodyData, userDetails) { return new Promise(async (resolve, reject) => { try { + let tenantId + if (userDetails.userInformation.roles.includes(CONSTANTS.common.ADMIN_ROLE)) { + tenantId = userDetails.tenantAndOrgInfo.tenantId + } else { + tenantId = userDetails.userInformation.tenantId + } + if (bodyData.translations) { // Fetch existing entity document - let entityDocuments = await entitiesQueries.entityDocuments({ _id: ObjectId(entityId) }, 'all') + let entityDocuments = await entitiesQueries.entityDocuments( + { _id: ObjectId(entityId), tenantId: tenantId }, + 'all' + ) if (entityDocuments && entityDocuments.length > 0) { const existingTranslations = entityDocuments[0].translations || {} @@ -1669,9 +1750,13 @@ module.exports = class UserProjectsHelper { } // Update the entity using findOneAndUpdate - let entityInformation = await entitiesQueries.findOneAndUpdate({ _id: ObjectId(entityId) }, bodyData, { - new: true, - }) + let entityInformation = await entitiesQueries.findOneAndUpdate( + { _id: ObjectId(entityId), tenantId: tenantId }, + bodyData, + { + new: true, + } + ) // Check if entityInformation is null (not found) if (!entityInformation) { diff --git a/src/module/entities/validator/v1.js b/src/module/entities/validator/v1.js index 4f048fc..98cb169 100644 --- a/src/module/entities/validator/v1.js +++ b/src/module/entities/validator/v1.js @@ -23,6 +23,28 @@ module.exports = (req) => { .trim() // Removes leading and trailing spaces .notEmpty() .withMessage('The name field cannot be empty.') + req.checkBody('tenantId') + .exists() + .withMessage('TenantId is required') + .trim() + .notEmpty() + .withMessage('The value cannot be a empty string') + req.checkBody('orgId') + .exists() + .withMessage('orgId is required.') + .custom((value) => { + if (!Array.isArray(value)) { + throw new Error('orgId must be an array.') + } + + const invalidItems = value.filter((item) => typeof item !== 'string' || item.trim() === '') + + if (invalidItems.length > 0) { + throw new Error('orgId array cannot contain empty or non-string values.') + } + + return true + }) }, update: function () { req.checkParams('_id').exists().withMessage('required _id') diff --git a/src/module/entityTypes/helper.js b/src/module/entityTypes/helper.js index 09eba26..6de0e3e 100644 --- a/src/module/entityTypes/helper.js +++ b/src/module/entityTypes/helper.js @@ -32,6 +32,29 @@ module.exports = class UserProjectsHelper { entityTypesCSVData.map(async (entityType) => { try { entityType = UTILS.valueParser(entityType) + if (userDetails.userInformation.roles.includes(CONSTANTS.common.ADMIN_ROLE)) { + if (!entityType.tenantId || !(entityType.tenantId.length > 0)) { + entityType['tenantId'] = userDetails.tenantAndOrgInfo.tenantId + } + if ( + !entityType.orgId || + entityType.orgId.length == 0 || + entityType.orgId.includes('') + ) { + entityType['orgId'] = userDetails.tenantAndOrgInfo.orgId + } + } else { + if (!entityType.tenantId || !(entityType.tenantId.length > 0)) { + entityType['tenantId'] = userDetails.userInformation.tenantId + } + if ( + !entityType.orgId || + entityType.orgId.length == 0 || + entityType.orgId.includes('') + ) { + entityType['orgId'] = [userDetails.userInformation.organizationId] + } + } entityType.registryDetails = {} let removedKeys = [] @@ -83,8 +106,8 @@ module.exports = class UserProjectsHelper { // Set userId based on userDetails const userId = - userDetails && userDetails.userInformation.id - ? userDetails && userDetails.userInformation.id + userDetails && userDetails.userInformation.userId + ? userDetails && userDetails.userInformation.userId : CONSTANTS.common.SYSTEM if (!entityType.name) { @@ -131,7 +154,7 @@ module.exports = class UserProjectsHelper { * create single entity. * @method * @name create - * @param {Object} data - requested entity data. + * @param {Object} body - requested entity data. * @param {Object} userDetails - Logged in user information. * @returns {JSON} - create single entity. */ @@ -139,6 +162,13 @@ module.exports = class UserProjectsHelper { return new Promise(async (resolve, reject) => { try { let entityType = body + if (userDetails.userInformation.roles.includes(CONSTANTS.common.ADMIN_ROLE)) { + entityType['tenantId'] = userDetails.tenantAndOrgInfo.tenantId + entityType['orgId'] = userDetails.tenantAndOrgInfo.orgId + } else { + entityType['tenantId'] = userDetails.userInformation.tenantId + entityType['orgId'] = [userDetails.userInformation.organizationId] + } if (entityType.profileFields) { entityType.profileFields = entityType.profileFields.split(',') || [] @@ -170,9 +200,10 @@ module.exports = class UserProjectsHelper { // Determine userId based on userDetails or default to SYSTEM const userId = - userDetails && userDetails.userInformation.id - ? userDetails.userInformation.id + userDetails && userDetails.userInformation.userId + ? userDetails.userInformation.userId : CONSTANTS.common.SYSTEM + let newEntityType = await entityTypeQueries.create( _.merge( { @@ -187,6 +218,7 @@ module.exports = class UserProjectsHelper { if (newEntityType._id) { entityType.status = CONSTANTS.common.SUCCESS + entityType._id = newEntityType._id } else { entityType.status = CONSTANTS.common.FAILURE } @@ -209,12 +241,23 @@ module.exports = class UserProjectsHelper { * */ - static update(entityTypeId, bodyData) { + static update(entityTypeId, bodyData, userDetails) { return new Promise(async (resolve, reject) => { try { + // avoid adding manupulative data + delete bodyData.tenantId + delete bodyData.orgId + + let tenantId + if (userDetails.userInformation.roles.includes(CONSTANTS.common.ADMIN_ROLE)) { + tenantId = userDetails.tenantAndOrgInfo.tenantId + } else { + tenantId = userDetails.userInformation.tenantId + } + // Find and update the entity type by ID with the provided bodyData let entityInformation = await entityTypeQueries.findOneAndUpdate( - { _id: ObjectId(entityTypeId) }, + { _id: ObjectId(entityTypeId), tenantId: tenantId }, bodyData, { new: true } ) @@ -353,22 +396,16 @@ module.exports = class UserProjectsHelper { * List enitity Type. * @method * @name list - * @param {String} entityType - entity type. - * @param {String} entityId - requested entity id. - * @param {String} [queryParameter = ""] - queryParameter value if required. + * @param {Object} [query = {}] - query value if required. + * @param {Object} [projection = {}] - mongodb query project object * @returns {JSON} - Details of entity. */ - static list(queryParameter = 'all', projection = {}) { + static list(query = {}, projection = {}) { return new Promise(async (resolve, reject) => { try { - // Convert 'all' to an empty object for querying all entity types - if (queryParameter === 'all') { - queryParameter = {} - } - - // Retrieve entity type data based on the provided queryParameter and projection - let entityTypeData = await entityTypeQueries.entityTypesDocument(queryParameter, projection) + // Retrieve entity type data based on the provided query and projection + let entityTypeData = await entityTypeQueries.entityTypesDocument(query, projection) return resolve({ message: CONSTANTS.apiResponses.ENTITY_TYPES_FETCHED, result: entityTypeData, diff --git a/src/module/userRoleExtension/helper.js b/src/module/userRoleExtension/helper.js index fc44413..e957970 100644 --- a/src/module/userRoleExtension/helper.js +++ b/src/module/userRoleExtension/helper.js @@ -5,7 +5,7 @@ * Description : user role helper functionality. */ -const { result } = require('lodash') +const { result, includes } = require('lodash') // Dependencies const userRoleExtensionQueries = require(DB_QUERY_BASE_PATH + '/userRoleExtension') diff --git a/src/module/userRoleExtension/validator/v1.js b/src/module/userRoleExtension/validator/v1.js index 2c65c58..6bdea06 100644 --- a/src/module/userRoleExtension/validator/v1.js +++ b/src/module/userRoleExtension/validator/v1.js @@ -23,6 +23,28 @@ module.exports = (req) => { .notEmpty() .withMessage('The userRoleId field cannot be empty.') req.checkBody('code').exists().withMessage('code is required') + req.checkBody('tenantId') + .exists() + .withMessage('TenantId is required') + .trim() + .notEmpty() + .withMessage('The value cannot be a empty string') + req.checkBody('orgId') + .exists() + .withMessage('orgId is required.') + .custom((value) => { + if (!Array.isArray(value)) { + throw new Error('orgId must be an array.') + } + + const invalidItems = value.filter((item) => typeof item !== 'string' || item.trim() === '') + + if (invalidItems.length > 0) { + throw new Error('orgId array cannot contain empty or non-string values.') + } + + return true + }) }, update: function () { req.checkBody('title').exists().withMessage('required title') From 23e6c75be80a937ff40d458fa7dc628cf23cfc92 Mon Sep 17 00:00:00 2001 From: prajwal Date: Wed, 23 Apr 2025 19:38:03 +0530 Subject: [PATCH 02/91] envVaibales file updated --- src/.env.sample | 4 +++- src/envVariables.js | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/.env.sample b/src/.env.sample index 160f342..25e49f4 100644 --- a/src/.env.sample +++ b/src/.env.sample @@ -18,4 +18,6 @@ API_DOC_URL = "/entity-management/api-doc" IS_AUTH_TOKEN_BEARER=false AUTH_METHOD = native #or keycloak_public_key -KEYCLOAK_PUBLIC_KEY_PATH = path to the pem/secret file \ No newline at end of file +KEYCLOAK_PUBLIC_KEY_PATH = path to the pem/secret file +ADMIN_TOKEN_HEADER_NAME = admin-access-token // admin access token header name +ADMIN_ACCESS_TOKEN = ivopeiovcie-----------lvkkdvkdm // admin access token \ No newline at end of file diff --git a/src/envVariables.js b/src/envVariables.js index 7ed3868..3cfa319 100644 --- a/src/envVariables.js +++ b/src/envVariables.js @@ -47,6 +47,15 @@ let enviromentVariables = { optional: true, default: '../keycloakPublicKeys', }, + ADMIN_TOKEN_HEADER_NAME: { + message: 'Required admin access token header name', + optional: true, + default: 'admin-auth-token', + }, + ADMIN_ACCESS_TOKEN: { + message: 'Required admin access token', + optional: false, + }, } let success = true From 2417d923d5f1de12d26b74168c0aeffaf704e747 Mon Sep 17 00:00:00 2001 From: prajwal Date: Fri, 25 Apr 2025 16:00:42 +0530 Subject: [PATCH 03/91] different admins login handled --- src/controllers/v1/entities.js | 4 +- src/controllers/v1/entityTypes.js | 17 ++-- src/controllers/v1/userRoleExtension.js | 6 +- src/generics/constants/api-responses.js | 2 + src/generics/constants/common.js | 6 ++ src/generics/constants/endpoints.js | 1 + src/generics/middleware/authenticator.js | 77 +++++++++++++++--- src/generics/services/users.js | 62 ++++++++++++++ src/module/entities/helper.js | 85 ++++++-------------- src/module/entities/validator/v1.js | 22 ----- src/module/entityTypes/helper.js | 41 ++-------- src/module/userRoleExtension/helper.js | 20 ++++- src/module/userRoleExtension/validator/v1.js | 22 ----- 13 files changed, 198 insertions(+), 167 deletions(-) create mode 100644 src/generics/services/users.js diff --git a/src/controllers/v1/entities.js b/src/controllers/v1/entities.js index e26be9f..24689f2 100644 --- a/src/controllers/v1/entities.js +++ b/src/controllers/v1/entities.js @@ -389,7 +389,9 @@ module.exports = class Entities extends Abstract { req?.query?.paginate?.toLowerCase() == 'true' ? true : false, req.query.entityType ? req.query.entityType : '', req.query.language ? req.query.language : '', - req.userDetails.userInformation.tenantId + req.userDetails.tenantAndOrgInfo + ? req.userDetails.tenantAndOrgInfo.tenantId + : req.userDetails.userInformation.tenantId ) // Resolves the promise with the retrieved entity data return resolve(userRoleDetails) diff --git a/src/controllers/v1/entityTypes.js b/src/controllers/v1/entityTypes.js index 55a2a31..4224058 100644 --- a/src/controllers/v1/entityTypes.js +++ b/src/controllers/v1/entityTypes.js @@ -65,16 +65,13 @@ module.exports = class EntityTypes extends Abstract { let query = {} let userRoles = req.userDetails.userInformation.roles ? req.userDetails.userInformation.roles : [] - // create query to fetch assets as a SUPER_ADMIN - if (userRoles.includes(CONSTANTS.common.ADMIN_ROLE)) { - tenantId = req.userDetails.tenantAndOrgInfo.tenantId - query['orgId'] = { $in: req.userDetails.tenantAndOrgInfo.orgId } - } - // create query to fetch assets as a normal user - else { - tenantId = req.userDetails.userInformation.tenantId - } - query['tenantId'] = tenantId + // create query to fetch assets + query['tenantId'] = req.userDetails.tenantAndOrgInfo + ? req.userDetails.tenantAndOrgInfo.tenantId + : req.userDetails.userInformation.tenantId + query['orgId'] = req.userDetails.tenantAndOrgInfo + ? { $in: req.userDetails.tenantAndOrgInfo.orgId } + : { $in: [req.userDetails.userInformation.organizationId] } // handle currentOrgOnly filter if (req.query['currentOrgOnly'] && req.query['currentOrgOnly'] == 'true') { diff --git a/src/controllers/v1/userRoleExtension.js b/src/controllers/v1/userRoleExtension.js index 376f0ef..77e7e76 100644 --- a/src/controllers/v1/userRoleExtension.js +++ b/src/controllers/v1/userRoleExtension.js @@ -76,7 +76,7 @@ module.exports = class userRoleExtension extends Abstract { return new Promise(async (resolve, reject) => { try { // Call the helper function to create a new user role extension document - let result = await userRoleExtensionHelper.create(req.body) + let result = await userRoleExtensionHelper.create(req.body, req.userDetails) // Resolve the promise with the result of the creation operation return resolve(result) @@ -135,7 +135,7 @@ module.exports = class userRoleExtension extends Abstract { return new Promise(async (resolve, reject) => { try { // Call the helper function to update the user role extension document - let result = await userRoleExtensionHelper.update(req.params._id, req.body) + let result = await userRoleExtensionHelper.update(req.params._id, req.body, req.userDetails) // Resolve the promise with the result of the update operation return resolve(result) @@ -241,7 +241,7 @@ module.exports = class userRoleExtension extends Abstract { return new Promise(async (resolve, reject) => { try { // Call the helper function to delete the user role extension by ID - let userData = await userRoleExtensionHelper.delete(req.params._id) + let userData = await userRoleExtensionHelper.delete(req.params._id, req.userDetails) // Resolve the promise with the result of the deletion return resolve(userData) } catch (error) { diff --git a/src/generics/constants/api-responses.js b/src/generics/constants/api-responses.js index 8f997cb..d475fec 100644 --- a/src/generics/constants/api-responses.js +++ b/src/generics/constants/api-responses.js @@ -62,4 +62,6 @@ module.exports = { MAPPING_CSV_GENERATED: 'MAPPING_CSV_GENERATED', INVALID_TENANT_AND_ORG_CODE: 'ERR_TENANT_AND_ORG_INVALID', INVALID_TENANT_AND_ORG_MESSAGE: 'Invalid tenant and org info', + ORG_DETAILS_FETCH_UNSUCCESSFUL_CODE: 'ERR_ORG_DETAILS_NOT_FETCHED', + ORG_DETAILS_FETCH_UNSUCCESSFUL_MESSAGE: 'Org details fetch unsuccessful', } diff --git a/src/generics/constants/common.js b/src/generics/constants/common.js index 7937e7a..e88088d 100644 --- a/src/generics/constants/common.js +++ b/src/generics/constants/common.js @@ -22,6 +22,9 @@ module.exports = { '/userRoleExtension/create', '/userRoleExtension/update', '/entities/createMappingCsv', + '/userRoleExtension/create', + '/userRoleExtension/update', + '/userRoleExtension/delete', ], SYSTEM: 'SYSTEM', SUCCESS: 'SUCCESS', @@ -37,4 +40,7 @@ module.exports = { }, ENGLISH_LANGUGE_CODE: 'en', ADMIN_ROLE: 'admin', + ORG_ADMIN: 'org_admin', + TENANT_ADMIN: 'tenant_admin', + SERVER_TIME_OUT: 5000, } diff --git a/src/generics/constants/endpoints.js b/src/generics/constants/endpoints.js index 0231da6..2a8b9c2 100644 --- a/src/generics/constants/endpoints.js +++ b/src/generics/constants/endpoints.js @@ -7,4 +7,5 @@ module.exports = { // End points to be added here + ORGANIZATION_READ: '/v1/organization/read', } diff --git a/src/generics/middleware/authenticator.js b/src/generics/middleware/authenticator.js index d1d7d58..c835843 100644 --- a/src/generics/middleware/authenticator.js +++ b/src/generics/middleware/authenticator.js @@ -10,6 +10,7 @@ const jwt = require('jsonwebtoken') const isBearerRequired = process.env.IS_AUTH_TOKEN_BEARER === 'true' const path = require('path') const fs = require('fs') +const userService = require('../services/users') var respUtil = function (resp) { return { status: resp.errCode, @@ -78,7 +79,6 @@ module.exports = async function (req, res, next, token = '') { rspObj.responseCode = HTTP_STATUS_CODE['unauthorized'].status return res.status(HTTP_STATUS_CODE['unauthorized'].status).send(respUtil(rspObj)) } - // Check if a Bearer token is required for authentication if (isBearerRequired) { const [authType, extractedToken] = token.split(' ') @@ -165,10 +165,10 @@ module.exports = async function (req, res, next, token = '') { decodedToken = decodedToken || {} decodedToken['data'] = data } - if (adminHeader) { - if (adminHeader != process.env.ADMIN_ACCESS_TOKEN) { - return res.status(HTTP_STATUS_CODE['unauthorized'].status).send(respUtil(rspObj)) - } + + let userRoles = decodedToken.data.roles.map((role) => role.title) + // check if tenantId and orgId is present in the header for SUPER_ADMIN & TENANT_ADMIN roles + if (userRoles.includes(CONSTANTS.common.ADMIN) || userRoles.includes(CONSTANTS.common.TENANT_ADMIN)) { if ( !req.headers['tenantid'] || !req.headers['orgid'] || @@ -177,15 +177,71 @@ module.exports = async function (req, res, next, token = '') { ) { rspObj.errCode = CONSTANTS.apiResponses.INVALID_TENANT_AND_ORG_CODE rspObj.errMsg = CONSTANTS.apiResponses.INVALID_TENANT_AND_ORG_MESSAGE - rspObj.responseCode = HTTP_STATUS_CODE['unauthorized'].status - return res.status(HTTP_STATUS_CODE['unauthorized'].status).send(respUtil(rspObj)) + rspObj.responseCode = HTTP_STATUS_CODE['bad_request'].status + return res.status(HTTP_STATUS_CODE['bad_request'].status).send(respUtil(rspObj)) + } + } + decodedToken.data['tenantAndOrgInfo'] = {} + + let relatedOrgDetails = false + let validOrgIds = [] + if (!userRoles.includes(CONSTANTS.common.ORG_ADMIN) && req.headers['tenantid'] !== '') { + // fetch the related org details using organization/read api + relatedOrgDetails = await userService.fetchOrgDetails(req.headers['tenantid']) + // convert the types of items to string + + if ( + !relatedOrgDetails || + !relatedOrgDetails.success || + !relatedOrgDetails.data || + !(Object.keys(relatedOrgDetails.data).length > 0) || + !(relatedOrgDetails.data.related_orgs > 0) + ) { + rspObj.errCode = CONSTANTS.apiResponses.ORG_DETAILS_FETCH_UNSUCCESSFUL_CODE + rspObj.errMsg = CONSTANTS.apiResponses.ORG_DETAILS_FETCH_UNSUCCESSFUL_MESSAGE + rspObj.responseCode = HTTP_STATUS_CODE['bad_request'].status + return res.status(HTTP_STATUS_CODE['bad_request'].status).send(respUtil(rspObj)) } - decodedToken.data['tenantAndOrgInfo'] = {} - decodedToken.data.tenantAndOrgInfo['tenantId'] = req.get('tenantid').toString() + relatedOrgDetails.data.related_orgs = relatedOrgDetails.data.related_orgs.map(String) + // aggregate valid orgids + let headerOrgIds = req.headers['orgid']?.split(',') || [] + let relatedOrgIds = relatedOrgDetails.data.related_orgs + validOrgIds = headerOrgIds.filter((id) => relatedOrgIds.includes(id)) - decodedToken.data.tenantAndOrgInfo['orgId'] = req.get('orgid').split(',') + // if the valid orgids array is empty throw error + if (!(validOrgIds.length > 0)) { + rspObj.errCode = CONSTANTS.apiResponses.INVALID_TENANT_AND_ORG_CODE + rspObj.errMsg = CONSTANTS.apiResponses.INVALID_TENANT_AND_ORG_MESSAGE + rspObj.responseCode = HTTP_STATUS_CODE['bad_request'].status + return res.status(HTTP_STATUS_CODE['bad_request'].status).send(respUtil(rspObj)) + } + } + + if (adminHeader) { + if (adminHeader != process.env.ADMIN_ACCESS_TOKEN) { + return res.status(HTTP_STATUS_CODE['unauthorized'].status).send(respUtil(rspObj)) + } + decodedToken.data.tenantAndOrgInfo['tenantId'] = req.headers['tenantid'].toString() + decodedToken.data.tenantAndOrgInfo['orgId'] = validOrgIds decodedToken.data.roles.push({ title: CONSTANTS.common.ADMIN_ROLE }) + } else if (userRoles.includes(CONSTANTS.common.TENANT_ADMIN)) { + // throw error if decodedToken tenant_id & header tenantId does not match for TENANT_ADMIN role + if (req.headers['tenantid'] !== decodedToken.data.tenant_id.toString()) { + rspObj.errCode = CONSTANTS.apiResponses.INVALID_TENANT_AND_ORG_CODE + rspObj.errMsg = CONSTANTS.apiResponses.INVALID_TENANT_AND_ORG_MESSAGE + rspObj.responseCode = HTTP_STATUS_CODE['bad_request'].status + return res.status(HTTP_STATUS_CODE['bad_request'].status).send(respUtil(rspObj)) + } + decodedToken.data.tenantAndOrgInfo['tenantId'] = req.headers['tenantid'].toString() + decodedToken.data.tenantAndOrgInfo['orgId'] = validOrgIds + } + // set the tenant & org details for ORG_ADMIN + else if (userRoles.includes(CONSTANTS.common.ORG_ADMIN)) { + decodedToken.data.tenantAndOrgInfo = { + orgId: [decodedToken.data.organization_id.toString()], + tenantId: decodedToken.data.tenant_id.toString(), + } } } catch (err) { rspObj.errCode = CONSTANTS.apiResponses.TOKEN_MISSING_CODE @@ -211,6 +267,7 @@ module.exports = async function (req, res, next, token = '') { tenantId: decodedToken.data.tenant_id.toString(), }, } + // add tenantAndOrgInfo to req object only for admin(s) if (decodedToken.data.tenantAndOrgInfo) { req.userDetails.tenantAndOrgInfo = decodedToken.data.tenantAndOrgInfo } diff --git a/src/generics/services/users.js b/src/generics/services/users.js new file mode 100644 index 0000000..d30a70a --- /dev/null +++ b/src/generics/services/users.js @@ -0,0 +1,62 @@ +const request = require('request') + +const interfaceServiceUrl = process.env.INTERFACE_SERVICE_URL + +const fetchOrgDetails = function (organisationIdentifier, userToken) { + return new Promise(async (resolve, reject) => { + try { + let url + if (!isNaN(organisationIdentifier)) { + url = + interfaceServiceUrl + + process.env.USER_SERVICE_BASE_URL + + CONSTANTS.endpoints.ORGANIZATION_READ + + '?organisation_id=' + + organisationIdentifier + } else { + url = + interfaceServiceUrl + + process.env.USER_SERVICE_BASE_URL + + CONSTANTS.endpoints.ORGANIZATION_READ + + '?organisation_code=' + + organisationIdentifier + } + const options = { + headers: { + internal_access_token: process.env.INTERNAL_ACCESS_TOKEN, + }, + } + request.get(url, options, userReadCallback) + let result = { + success: true, + } + function userReadCallback(err, data) { + if (err) { + result.success = false + } else { + let response = JSON.parse(data.body) + if (response.responseCode === HTTP_STATUS_CODE['ok'].code) { + result['data'] = response.result + result.success = true + } else { + result.success = false + } + } + return resolve(result) + } + setTimeout(function () { + return resolve( + (result = { + success: false, + }) + ) + }, CONSTANTS.common.SERVER_TIME_OUT) + } catch (error) { + return reject(error) + } + }) +} + +module.exports = { + fetchOrgDetails: fetchOrgDetails, +} diff --git a/src/module/entities/helper.js b/src/module/entities/helper.js index 894980d..aa87f00 100644 --- a/src/module/entities/helper.js +++ b/src/module/entities/helper.js @@ -185,12 +185,10 @@ module.exports = class UserProjectsHelper { return new Promise(async (resolve, reject) => { try { // Call 'entitiesQueries.entityDocuments' to retrieve entities based on provided entity IDs and fields - let tenantId - if (userDetails.userInformation.roles.includes(CONSTANTS.common.ADMIN_ROLE)) { - tenantId = userDetails.tenantAndOrgInfo.tenantId - } else { - tenantId = userDetails.userInformation.tenantId - } + let tenantId = userDetails.tenantAndOrgInfo + ? userDetails.tenantAndOrgInfo.tenantId + : userDetails.userInformation.tenantId + const entities = await entitiesQueries.entityDocuments( { _id: { $in: entityIds }, @@ -952,12 +950,10 @@ module.exports = class UserProjectsHelper { // message: CONSTANTS.apiResponses.USER_ROLES_NOT_FOUND // } // } - let tenantId - if (userDetails.userInformation.roles.includes(CONSTANTS.common.ADMIN_ROLE)) { - tenantId = userDetails.tenantAndOrgInfo.tenantId - } else { - tenantId = userDetails.userInformation.tenantId - } + let tenantId = userDetails.tenantAndOrgInfo + ? userDetails.tenantAndOrgInfo.tenantId + : userDetails.userInformation.tenantId + let filterQuery = { 'registryDetails.code': stateLocationId, tenantId: tenantId, @@ -1029,12 +1025,10 @@ module.exports = class UserProjectsHelper { static listByLocationIds(locationIds, userDetails) { return new Promise(async (resolve, reject) => { try { - let tenantId - if (userDetails.userInformation.roles.includes(CONSTANTS.common.ADMIN_ROLE)) { - tenantId = userDetails.tenantAndOrgInfo.tenantId - } else { - tenantId = userDetails.userInformation.tenantId - } + let tenantId = userDetails.tenantAndOrgInfo + ? userDetails.tenantAndOrgInfo.tenantId + : userDetails.userInformation.tenantId + // Constructing the filter query to find entities based on locationIds let filterQuery = { $or: [ @@ -1122,21 +1116,10 @@ module.exports = class UserProjectsHelper { static entityListBasedOnEntityType(type, pageNo, pageSize, paginate, language, userDetails) { return new Promise(async (resolve, reject) => { try { - let tenantId - let organizationId let query = {} - let userRoles = userDetails.userInformation.roles ? userDetails.userInformation.roles : [] - - // create query to fetch assets as a SUPER_ADMIN - if (userRoles.includes(CONSTANTS.common.ADMIN_ROLE)) { - tenantId = userDetails.tenantAndOrgInfo.tenantId - } - // create query to fetch assets as a normal user - else { - tenantId = userDetails.userInformation.tenantId - } - - query['tenantId'] = tenantId + query['tenantId'] = userDetails.tenantAndOrgInfo + ? userDetails.tenantAndOrgInfo.tenantId + : userDetails.userInformation.tenantId query['name'] = type // Fetch the list of entity types available const entityList = await entityTypeQueries.entityTypesDocument(query, ['name']) @@ -1221,12 +1204,8 @@ module.exports = class UserProjectsHelper { try { // Find the entities document based on the entityType in queryParams - let tenantId - if (userDetails.userInformation.roles.includes(CONSTANTS.common.ADMIN_ROLE)) { - tenantId = userDetails.tenantAndOrgInfo.tenantId - } else { - tenantId = userDetails.userInformation.tenantId - } + let tenantId = userDetails.tenantAndOrgInfo.tenantId + let orgId = userDetails.tenantAndOrgInfo.orgId let entityTypeDocument = await entityTypeQueries.findOne( { name: queryParams.type, tenantId: tenantId }, { _id: 1 } @@ -1292,7 +1271,7 @@ module.exports = class UserProjectsHelper { createdBy: userDetails.userInformation.userId, userId: userDetails.userInformation.userId, tenantId: tenantId, - orgId: singleEntity.orgId, + orgId: orgId, } entityDocuments.push(entityDoc) @@ -1384,11 +1363,9 @@ module.exports = class UserProjectsHelper { }) } // add tenantId to the query - if (userDetails.userInformation.roles.includes(CONSTANTS.common.ADMIN_ROLE)) { - query['tenantId'] = userDetails.tenantAndOrgInfo.tenantId - } else { - query['tenantId'] = userDetails.userInformation.tenantId - } + query['tenantId'] = userDetails.tenantAndOrgInfo + ? userDetails.tenantAndOrgInfo.tenantId + : userDetails.userInformation.tenantId // Fetch entity documents based on constructed query let entityDocument = await entitiesQueries.entityDocuments(query, 'all', 10) @@ -1484,13 +1461,8 @@ module.exports = class UserProjectsHelper { // } // Find the entity type document based on the provided entityType - let tenantId - if (userDetails.userInformation.roles.includes(CONSTANTS.common.ADMIN_ROLE)) { - tenantId = userDetails.tenantAndOrgInfo.tenantId - } else { - tenantId = userDetails.userInformation.tenantId - } - + let tenantId = userDetails.tenantAndOrgInfo.tenantId + let orgId = userDetails.tenantAndOrgInfo.orgId let entityTypeDocument = await entityTypeQueries.findOne( { name: entityType, @@ -1520,8 +1492,8 @@ module.exports = class UserProjectsHelper { groups: {}, updatedBy: userId, createdBy: userId, - tenantId: userDetails.userInformation.tenantId, - orgId: singleEntity.orgId, + tenantId: tenantId, + orgId: orgId, } // if (singleEntity.allowedRoles && singleEntity.allowedRoles.length > 0) { // entityCreation['allowedRoles'] = await allowedRoles(singleEntity.allowedRoles) @@ -1724,12 +1696,7 @@ module.exports = class UserProjectsHelper { static update(entityId, bodyData, userDetails) { return new Promise(async (resolve, reject) => { try { - let tenantId - if (userDetails.userInformation.roles.includes(CONSTANTS.common.ADMIN_ROLE)) { - tenantId = userDetails.tenantAndOrgInfo.tenantId - } else { - tenantId = userDetails.userInformation.tenantId - } + let tenantId = userDetails.tenantAndOrgInfo.tenantId if (bodyData.translations) { // Fetch existing entity document diff --git a/src/module/entities/validator/v1.js b/src/module/entities/validator/v1.js index 98cb169..4f048fc 100644 --- a/src/module/entities/validator/v1.js +++ b/src/module/entities/validator/v1.js @@ -23,28 +23,6 @@ module.exports = (req) => { .trim() // Removes leading and trailing spaces .notEmpty() .withMessage('The name field cannot be empty.') - req.checkBody('tenantId') - .exists() - .withMessage('TenantId is required') - .trim() - .notEmpty() - .withMessage('The value cannot be a empty string') - req.checkBody('orgId') - .exists() - .withMessage('orgId is required.') - .custom((value) => { - if (!Array.isArray(value)) { - throw new Error('orgId must be an array.') - } - - const invalidItems = value.filter((item) => typeof item !== 'string' || item.trim() === '') - - if (invalidItems.length > 0) { - throw new Error('orgId array cannot contain empty or non-string values.') - } - - return true - }) }, update: function () { req.checkParams('_id').exists().withMessage('required _id') diff --git a/src/module/entityTypes/helper.js b/src/module/entityTypes/helper.js index 6de0e3e..34ee4b3 100644 --- a/src/module/entityTypes/helper.js +++ b/src/module/entityTypes/helper.js @@ -32,29 +32,8 @@ module.exports = class UserProjectsHelper { entityTypesCSVData.map(async (entityType) => { try { entityType = UTILS.valueParser(entityType) - if (userDetails.userInformation.roles.includes(CONSTANTS.common.ADMIN_ROLE)) { - if (!entityType.tenantId || !(entityType.tenantId.length > 0)) { - entityType['tenantId'] = userDetails.tenantAndOrgInfo.tenantId - } - if ( - !entityType.orgId || - entityType.orgId.length == 0 || - entityType.orgId.includes('') - ) { - entityType['orgId'] = userDetails.tenantAndOrgInfo.orgId - } - } else { - if (!entityType.tenantId || !(entityType.tenantId.length > 0)) { - entityType['tenantId'] = userDetails.userInformation.tenantId - } - if ( - !entityType.orgId || - entityType.orgId.length == 0 || - entityType.orgId.includes('') - ) { - entityType['orgId'] = [userDetails.userInformation.organizationId] - } - } + entityType['tenantId'] = userDetails.tenantAndOrgInfo.tenantId + entityType['orgId'] = userDetails.tenantAndOrgInfo.orgId entityType.registryDetails = {} let removedKeys = [] @@ -162,13 +141,8 @@ module.exports = class UserProjectsHelper { return new Promise(async (resolve, reject) => { try { let entityType = body - if (userDetails.userInformation.roles.includes(CONSTANTS.common.ADMIN_ROLE)) { - entityType['tenantId'] = userDetails.tenantAndOrgInfo.tenantId - entityType['orgId'] = userDetails.tenantAndOrgInfo.orgId - } else { - entityType['tenantId'] = userDetails.userInformation.tenantId - entityType['orgId'] = [userDetails.userInformation.organizationId] - } + entityType['tenantId'] = userDetails.tenantAndOrgInfo.tenantId + entityType['orgId'] = userDetails.tenantAndOrgInfo.orgId if (entityType.profileFields) { entityType.profileFields = entityType.profileFields.split(',') || [] @@ -248,12 +222,7 @@ module.exports = class UserProjectsHelper { delete bodyData.tenantId delete bodyData.orgId - let tenantId - if (userDetails.userInformation.roles.includes(CONSTANTS.common.ADMIN_ROLE)) { - tenantId = userDetails.tenantAndOrgInfo.tenantId - } else { - tenantId = userDetails.userInformation.tenantId - } + let tenantId = userDetails.tenantAndOrgInfo.tenantId // Find and update the entity type by ID with the provided bodyData let entityInformation = await entityTypeQueries.findOneAndUpdate( diff --git a/src/module/userRoleExtension/helper.js b/src/module/userRoleExtension/helper.js index e957970..82f2de6 100644 --- a/src/module/userRoleExtension/helper.js +++ b/src/module/userRoleExtension/helper.js @@ -17,7 +17,7 @@ module.exports = class userRoleExtensionHelper { * @param {Object} body - The data to create the new user role extension. * @returns {Promise} - A promise that resolves with the new user role extension data or rejects with an error. */ - static create(body) { + static create(body, userDetails) { return new Promise(async (resolve, reject) => { try { // Using map to handle validation @@ -38,6 +38,8 @@ module.exports = class userRoleExtensionHelper { } }) ) + body['tenantId'] = userDetails.tenantAndOrgInfo.tenantId + body['orgId'] = userDetails.tenantAndOrgInfo.orgId // Call the queries function to create a new user role extension with the provided body data let newUserRole = await userRoleExtensionQueries.create(body) @@ -57,9 +59,11 @@ module.exports = class userRoleExtensionHelper { * @param {Object} bodyData - The data to update the user role extension. * @returns {Promise} - A promise that resolves with the updated user role extension data or rejects with an error. */ - static update(userRoleId, bodyData) { + static update(userRoleId, bodyData, userDetails) { return new Promise(async (resolve, reject) => { try { + let tenantId = userDetails.tenantAndOrgInfo.tenantId + let orgId = userDetails.tenantAndOrgInfo.orgId if (bodyData.entityTypes) { await Promise.all( bodyData.entityTypes.map(async (entityTypeData) => { @@ -67,6 +71,7 @@ module.exports = class userRoleExtensionHelper { let existingEntityType = await entityTypeQueries.findOne({ name: entityTypeData.entityType, _id: ObjectId(entityTypeData.entityTypeId), + tenantId: tenantId, }) if (!existingEntityType) { @@ -80,6 +85,9 @@ module.exports = class userRoleExtensionHelper { ) } + delete bodyData.tenantId + delete bodyData.orgId + // Find and update the user role extension based on the provided userRoleId and bodyData let userInformation = await userRoleExtensionQueries.findOneAndUpdate( { _id: ObjectId(userRoleId) }, @@ -148,11 +156,15 @@ module.exports = class userRoleExtensionHelper { * @param {String} userRoleId - The ID of the user role extension to delete. * @returns {Promise} - A promise that resolves with a success message or rejects with an error. */ - static delete(userRoleId) { + static delete(userRoleId, userDetails) { return new Promise(async (resolve, reject) => { try { + let tenantId = userDetails.tenantAndOrgInfo.tenantId // Find and delete the user role extension based on the provided user role ID - let userInformation = await userRoleExtensionQueries.findOneAndDelete({ _id: ObjectId(userRoleId) }) + let userInformation = await userRoleExtensionQueries.findOneAndDelete({ + _id: ObjectId(userRoleId), + tenantId: tenantId, + }) // If no user role extension is found, reject the promise with a 404 status and an error message if (!userInformation) { diff --git a/src/module/userRoleExtension/validator/v1.js b/src/module/userRoleExtension/validator/v1.js index 6bdea06..2c65c58 100644 --- a/src/module/userRoleExtension/validator/v1.js +++ b/src/module/userRoleExtension/validator/v1.js @@ -23,28 +23,6 @@ module.exports = (req) => { .notEmpty() .withMessage('The userRoleId field cannot be empty.') req.checkBody('code').exists().withMessage('code is required') - req.checkBody('tenantId') - .exists() - .withMessage('TenantId is required') - .trim() - .notEmpty() - .withMessage('The value cannot be a empty string') - req.checkBody('orgId') - .exists() - .withMessage('orgId is required.') - .custom((value) => { - if (!Array.isArray(value)) { - throw new Error('orgId must be an array.') - } - - const invalidItems = value.filter((item) => typeof item !== 'string' || item.trim() === '') - - if (invalidItems.length > 0) { - throw new Error('orgId array cannot contain empty or non-string values.') - } - - return true - }) }, update: function () { req.checkBody('title').exists().withMessage('required title') From 64b8ee0802004c35408dfdd624bff4673fd0b687 Mon Sep 17 00:00:00 2001 From: prajwal Date: Sat, 26 Apr 2025 18:26:12 +0530 Subject: [PATCH 04/91] Code enhancement & addressed PR comments --- src/controllers/v1/entities.js | 23 +-- src/controllers/v1/entityTypes.js | 8 +- src/generics/constants/api-responses.js | 2 + src/generics/middleware/authenticator.js | 172 ++++++++++++++--------- src/models/entities.js | 7 +- src/models/entityTypes.js | 5 +- src/models/userRoleExtension.js | 12 +- src/module/entities/helper.js | 115 ++++++++------- src/module/entities/validator/v1.js | 2 +- src/module/entityTypes/helper.js | 13 +- src/module/entityTypes/validator/v1.js | 3 +- src/module/userRoleExtension/helper.js | 34 +++-- 12 files changed, 236 insertions(+), 160 deletions(-) diff --git a/src/controllers/v1/entities.js b/src/controllers/v1/entities.js index 24689f2..5810e51 100644 --- a/src/controllers/v1/entities.js +++ b/src/controllers/v1/entities.js @@ -128,7 +128,11 @@ module.exports = class Entities extends Abstract { 'entityTypeId', 'entityType', ] - let entityDocument = await entitiesQueries.entityDocuments({ _id: req.params._id }, projection) + let tenantId = req.userDetails.userInformation.tenantId + let entityDocument = await entitiesQueries.entityDocuments( + { _id: req.params._id, tenantId: tenantId }, + projection + ) if (entityDocument.length < 1) { throw { @@ -141,7 +145,8 @@ module.exports = class Entities extends Abstract { entityDocument[0]._id, entityDocument[0].entityTypeId, entityDocument[0].entityType, - projection + projection, + tenantId ) _.merge(result, entityDocument[0]) result['relatedEntities'] = relatedEntities.length > 0 ? relatedEntities : [] @@ -389,9 +394,7 @@ module.exports = class Entities extends Abstract { req?.query?.paginate?.toLowerCase() == 'true' ? true : false, req.query.entityType ? req.query.entityType : '', req.query.language ? req.query.language : '', - req.userDetails.tenantAndOrgInfo - ? req.userDetails.tenantAndOrgInfo.tenantId - : req.userDetails.userInformation.tenantId + req.userDetails.userInformation.tenantId ) // Resolves the promise with the retrieved entity data return resolve(userRoleDetails) @@ -568,8 +571,6 @@ module.exports = class Entities extends Abstract { // Prepare query parameters for adding the entity let queryParams = { type: req.query.type, - // programId: req.query.programId, - // solutionId: req.query.solutionId, parentEntityId: req.query.parentEntityId, } // Call 'entitiesHelper.add' to perform the entity addition operation @@ -780,7 +781,8 @@ module.exports = class Entities extends Abstract { req.pageSize, req.pageSize * (req.pageNo - 1), req.schoolTypes, - req.administrationTypes + req.administrationTypes, + req.userDetails ) return resolve(result) @@ -850,7 +852,8 @@ module.exports = class Entities extends Abstract { req.searchText, req.pageSize, req.pageNo, - req.query.language ? req.query.language : '' + req.query.language ? req.query.language : '', + req.userDetails ) return resolve(entityDocuments) } catch (error) { @@ -1022,7 +1025,7 @@ module.exports = class Entities extends Abstract { translationFile = JSON.parse(req.files.translationFile.data.toString()) } // Call 'entitiesHelper.bulkUpdate' to update entities based on CSV data and user details - let newEntityData = await entitiesHelper.bulkUpdate(entityCSVData, translationFile) + let newEntityData = await entitiesHelper.bulkUpdate(entityCSVData, translationFile, userDetails) // Check if entities were updated successfully if (newEntityData.length > 0) { diff --git a/src/controllers/v1/entityTypes.js b/src/controllers/v1/entityTypes.js index 4224058..1ed62a7 100644 --- a/src/controllers/v1/entityTypes.js +++ b/src/controllers/v1/entityTypes.js @@ -60,23 +60,21 @@ module.exports = class EntityTypes extends Abstract { async list(req) { return new Promise(async (resolve, reject) => { try { - let tenantId let organizationId let query = {} - let userRoles = req.userDetails.userInformation.roles ? req.userDetails.userInformation.roles : [] // create query to fetch assets query['tenantId'] = req.userDetails.tenantAndOrgInfo ? req.userDetails.tenantAndOrgInfo.tenantId : req.userDetails.userInformation.tenantId - query['orgId'] = req.userDetails.tenantAndOrgInfo - ? { $in: req.userDetails.tenantAndOrgInfo.orgId } + query['orgIds'] = req.userDetails.tenantAndOrgInfo + ? { $in: req.userDetails.tenantAndOrgInfo.orgIds } : { $in: [req.userDetails.userInformation.organizationId] } // handle currentOrgOnly filter if (req.query['currentOrgOnly'] && req.query['currentOrgOnly'] == 'true') { organizationId = req.userDetails.userInformation.organizationId - query['orgId'] = { $in: [organizationId] } + query['orgIds'] = { $in: [organizationId] } } let result = await entityTypesHelper.list(query, { name: 1 }) diff --git a/src/generics/constants/api-responses.js b/src/generics/constants/api-responses.js index d475fec..0e4e996 100644 --- a/src/generics/constants/api-responses.js +++ b/src/generics/constants/api-responses.js @@ -64,4 +64,6 @@ module.exports = { INVALID_TENANT_AND_ORG_MESSAGE: 'Invalid tenant and org info', ORG_DETAILS_FETCH_UNSUCCESSFUL_CODE: 'ERR_ORG_DETAILS_NOT_FETCHED', ORG_DETAILS_FETCH_UNSUCCESSFUL_MESSAGE: 'Org details fetch unsuccessful', + TENANTID_AND_ORGID_REQUIRED_IN_TOKEN_MESSAGE: 'TenantId and OrgnizationId required in the token', + TENANTID_AND_ORGID_REQUIRED_IN_TOKEN_CODE: 'ERR_TENANTID_AND_ORGID_REQUIRED', } diff --git a/src/generics/middleware/authenticator.js b/src/generics/middleware/authenticator.js index c835843..499e859 100644 --- a/src/generics/middleware/authenticator.js +++ b/src/generics/middleware/authenticator.js @@ -166,81 +166,119 @@ module.exports = async function (req, res, next, token = '') { decodedToken['data'] = data } - let userRoles = decodedToken.data.roles.map((role) => role.title) - // check if tenantId and orgId is present in the header for SUPER_ADMIN & TENANT_ADMIN roles - if (userRoles.includes(CONSTANTS.common.ADMIN) || userRoles.includes(CONSTANTS.common.TENANT_ADMIN)) { - if ( - !req.headers['tenantid'] || - !req.headers['orgid'] || - !req.headers['tenantid'].length || - !req.headers['orgid'].length - ) { - rspObj.errCode = CONSTANTS.apiResponses.INVALID_TENANT_AND_ORG_CODE - rspObj.errMsg = CONSTANTS.apiResponses.INVALID_TENANT_AND_ORG_MESSAGE - rspObj.responseCode = HTTP_STATUS_CODE['bad_request'].status - return res.status(HTTP_STATUS_CODE['bad_request'].status).send(respUtil(rspObj)) - } + if (!decodedToken) { + rspObj.errCode = CONSTANTS.apiResponses.TOKEN_MISSING_CODE + rspObj.errMsg = CONSTANTS.apiResponses.TOKEN_MISSING_MESSAGE + rspObj.responseCode = HTTP_STATUS_CODE['unauthorized'].status + return res.status(HTTP_STATUS_CODE['unauthorized'].status).send(respUtil(rspObj)) } - decodedToken.data['tenantAndOrgInfo'] = {} - let relatedOrgDetails = false - let validOrgIds = [] - if (!userRoles.includes(CONSTANTS.common.ORG_ADMIN) && req.headers['tenantid'] !== '') { - // fetch the related org details using organization/read api - relatedOrgDetails = await userService.fetchOrgDetails(req.headers['tenantid']) - // convert the types of items to string + // throw error if tenant_id or organization_id is not present in the decoded token + if ( + !decodedToken.data.tenant_id || + !(decodedToken.data.tenant_id.toString().length > 0) || + !decodedToken.data.organization_id || + !(decodedToken.data.organization_id.toString().length > 0) + ) { + rspObj.errCode = CONSTANTS.apiResponses.TENANTID_AND_ORGID_REQUIRED_IN_TOKEN_CODE + rspObj.errMsg = CONSTANTS.apiResponses.TENANTID_AND_ORGID_REQUIRED_IN_TOKEN_MESSAGE + rspObj.responseCode = HTTP_STATUS_CODE['bad_request'].status + return res.status(HTTP_STATUS_CODE['bad_request'].status).send(respUtil(rspObj)) + } + if (performInternalAccessTokenCheck) { + let userRoles = decodedToken.data.roles.map((role) => role.title) + // throw error if normal user tries to fetch internal apis if ( - !relatedOrgDetails || - !relatedOrgDetails.success || - !relatedOrgDetails.data || - !(Object.keys(relatedOrgDetails.data).length > 0) || - !(relatedOrgDetails.data.related_orgs > 0) + !adminHeader && + !userRoles.includes(CONSTANTS.common.TENANT_ADMIN) && + !userRoles.includes(CONSTANTS.common.ORG_ADMIN) ) { - rspObj.errCode = CONSTANTS.apiResponses.ORG_DETAILS_FETCH_UNSUCCESSFUL_CODE - rspObj.errMsg = CONSTANTS.apiResponses.ORG_DETAILS_FETCH_UNSUCCESSFUL_MESSAGE - rspObj.responseCode = HTTP_STATUS_CODE['bad_request'].status - return res.status(HTTP_STATUS_CODE['bad_request'].status).send(respUtil(rspObj)) + rspObj.errCode = CONSTANTS.apiResponses.TOKEN_INVALID_CODE + rspObj.errMsg = CONSTANTS.apiResponses.TOKEN_INVALID_MESSAGE + rspObj.responseCode = HTTP_STATUS_CODE['unauthorized'].status + return res.status(HTTP_STATUS_CODE['unauthorized'].status).send(respUtil(rspObj)) } - relatedOrgDetails.data.related_orgs = relatedOrgDetails.data.related_orgs.map(String) - // aggregate valid orgids - let headerOrgIds = req.headers['orgid']?.split(',') || [] - let relatedOrgIds = relatedOrgDetails.data.related_orgs - validOrgIds = headerOrgIds.filter((id) => relatedOrgIds.includes(id)) - - // if the valid orgids array is empty throw error - if (!(validOrgIds.length > 0)) { - rspObj.errCode = CONSTANTS.apiResponses.INVALID_TENANT_AND_ORG_CODE - rspObj.errMsg = CONSTANTS.apiResponses.INVALID_TENANT_AND_ORG_MESSAGE - rspObj.responseCode = HTTP_STATUS_CODE['bad_request'].status - return res.status(HTTP_STATUS_CODE['bad_request'].status).send(respUtil(rspObj)) + // validate SUPER_ADMIN + if (adminHeader) { + if (adminHeader != process.env.ADMIN_ACCESS_TOKEN) { + return res.status(HTTP_STATUS_CODE['unauthorized'].status).send(respUtil(rspObj)) + } + decodedToken.data.roles.push({ title: CONSTANTS.common.ADMIN_ROLE }) + userRoles.push(CONSTANTS.common.ADMIN_ROLE) } - } - if (adminHeader) { - if (adminHeader != process.env.ADMIN_ACCESS_TOKEN) { - return res.status(HTTP_STATUS_CODE['unauthorized'].status).send(respUtil(rspObj)) + // check if tenantId and orgId is present in the header for SUPER_ADMIN & TENANT_ADMIN roles + if (userRoles.includes(CONSTANTS.common.ADMIN_ROLE) || userRoles.includes(CONSTANTS.common.TENANT_ADMIN)) { + if ( + !req.headers['tenantid'] || + !req.headers['orgid'] || + !req.headers['tenantid'].length || + !req.headers['orgid'].length + ) { + rspObj.errCode = CONSTANTS.apiResponses.INVALID_TENANT_AND_ORG_CODE + rspObj.errMsg = CONSTANTS.apiResponses.INVALID_TENANT_AND_ORG_MESSAGE + rspObj.responseCode = HTTP_STATUS_CODE['bad_request'].status + return res.status(HTTP_STATUS_CODE['bad_request'].status).send(respUtil(rspObj)) + } } - decodedToken.data.tenantAndOrgInfo['tenantId'] = req.headers['tenantid'].toString() - decodedToken.data.tenantAndOrgInfo['orgId'] = validOrgIds - decodedToken.data.roles.push({ title: CONSTANTS.common.ADMIN_ROLE }) - } else if (userRoles.includes(CONSTANTS.common.TENANT_ADMIN)) { - // throw error if decodedToken tenant_id & header tenantId does not match for TENANT_ADMIN role - if (req.headers['tenantid'] !== decodedToken.data.tenant_id.toString()) { - rspObj.errCode = CONSTANTS.apiResponses.INVALID_TENANT_AND_ORG_CODE - rspObj.errMsg = CONSTANTS.apiResponses.INVALID_TENANT_AND_ORG_MESSAGE - rspObj.responseCode = HTTP_STATUS_CODE['bad_request'].status - return res.status(HTTP_STATUS_CODE['bad_request'].status).send(respUtil(rspObj)) + decodedToken.data['tenantAndOrgInfo'] = {} + + let relatedOrgDetails = false + let validOrgIds = [] + if (!userRoles.includes(CONSTANTS.common.ORG_ADMIN) && req.headers['tenantid'] !== '') { + // fetch the related org details using organization/read api + relatedOrgDetails = await userService.fetchOrgDetails(req.headers['tenantid']) + if ( + !relatedOrgDetails || + !relatedOrgDetails.success || + !relatedOrgDetails.data || + !(Object.keys(relatedOrgDetails.data).length > 0) || + !(relatedOrgDetails.data.related_orgs > 0) + ) { + rspObj.errCode = CONSTANTS.apiResponses.ORG_DETAILS_FETCH_UNSUCCESSFUL_CODE + rspObj.errMsg = CONSTANTS.apiResponses.ORG_DETAILS_FETCH_UNSUCCESSFUL_MESSAGE + rspObj.responseCode = HTTP_STATUS_CODE['bad_request'].status + return res.status(HTTP_STATUS_CODE['bad_request'].status).send(respUtil(rspObj)) + } + + // convert the types of items to string + relatedOrgDetails.data.related_orgs = relatedOrgDetails.data.related_orgs.map(String) + // aggregate valid orgids + let headerOrgIds = req.headers['orgid']?.split(',') || [] + let relatedOrgIds = relatedOrgDetails.data.related_orgs + validOrgIds = headerOrgIds.filter((id) => { + return relatedOrgIds.includes(id) || id == 'all' + }) + // if the valid orgids array is empty throw error + if (!(validOrgIds.length > 0)) { + rspObj.errCode = CONSTANTS.apiResponses.INVALID_TENANT_AND_ORG_CODE + rspObj.errMsg = CONSTANTS.apiResponses.INVALID_TENANT_AND_ORG_MESSAGE + rspObj.responseCode = HTTP_STATUS_CODE['bad_request'].status + return res.status(HTTP_STATUS_CODE['bad_request'].status).send(respUtil(rspObj)) + } } - decodedToken.data.tenantAndOrgInfo['tenantId'] = req.headers['tenantid'].toString() - decodedToken.data.tenantAndOrgInfo['orgId'] = validOrgIds - } - // set the tenant & org details for ORG_ADMIN - else if (userRoles.includes(CONSTANTS.common.ORG_ADMIN)) { - decodedToken.data.tenantAndOrgInfo = { - orgId: [decodedToken.data.organization_id.toString()], - tenantId: decodedToken.data.tenant_id.toString(), + + // set the tenant & org details for ORG_ADMIN + if (userRoles.includes(CONSTANTS.common.ORG_ADMIN)) { + decodedToken.data.tenantAndOrgInfo = { + orgIds: [decodedToken.data.organization_id.toString()], + tenantId: decodedToken.data.tenant_id.toString(), + } + } else if (userRoles.includes(CONSTANTS.common.TENANT_ADMIN)) { + // throw error if decodedToken tenant_id & header tenantId does not match for TENANT_ADMIN role + if (req.headers['tenantid'] !== decodedToken.data.tenant_id.toString()) { + rspObj.errCode = CONSTANTS.apiResponses.INVALID_TENANT_AND_ORG_CODE + rspObj.errMsg = CONSTANTS.apiResponses.INVALID_TENANT_AND_ORG_MESSAGE + rspObj.responseCode = HTTP_STATUS_CODE['bad_request'].status + return res.status(HTTP_STATUS_CODE['bad_request'].status).send(respUtil(rspObj)) + } + decodedToken.data.tenantAndOrgInfo['tenantId'] = req.headers['tenantid'].toString() + decodedToken.data.tenantAndOrgInfo['orgIds'] = validOrgIds + } else if (adminHeader) { + decodedToken.data.tenantAndOrgInfo['tenantId'] = req.headers['tenantid'].toString() + decodedToken.data.tenantAndOrgInfo['orgIds'] = validOrgIds } } } catch (err) { @@ -249,12 +287,6 @@ module.exports = async function (req, res, next, token = '') { rspObj.responseCode = HTTP_STATUS_CODE['unauthorized'].status return res.status(HTTP_STATUS_CODE['unauthorized'].status).send(respUtil(rspObj)) } - if (!decodedToken) { - rspObj.errCode = CONSTANTS.apiResponses.TOKEN_MISSING_CODE - rspObj.errMsg = CONSTANTS.apiResponses.TOKEN_MISSING_MESSAGE - rspObj.responseCode = HTTP_STATUS_CODE['unauthorized'].status - return res.status(HTTP_STATUS_CODE['unauthorized'].status).send(respUtil(rspObj)) - } req.userDetails = { userToken: token, userInformation: { diff --git a/src/models/entities.js b/src/models/entities.js index a55b377..0b361f1 100644 --- a/src/models/entities.js +++ b/src/models/entities.js @@ -15,7 +15,7 @@ module.exports = { }, groups: Object, metaInformation: { - externalId: { type: String, index: true, unique: true }, + externalId: { type: String, index: true }, name: { type: String, index: true }, }, childHierarchyPath: Array, @@ -39,9 +39,12 @@ module.exports = { }, tenantId: { type: String, + require: true, }, - orgId: { + orgIds: { type: Array, + require: true, + index: true, }, }, compoundIndex: [ diff --git a/src/models/entityTypes.js b/src/models/entityTypes.js index 7dfee0d..61ae640 100644 --- a/src/models/entityTypes.js +++ b/src/models/entityTypes.js @@ -34,9 +34,12 @@ module.exports = { }, tenantId: { type: String, + require: true, }, - orgId: { + orgIds: { type: Array, + require: true, + index: true, }, }, compoundIndex: [ diff --git a/src/models/userRoleExtension.js b/src/models/userRoleExtension.js index 1b87a2d..b36fba0 100644 --- a/src/models/userRoleExtension.js +++ b/src/models/userRoleExtension.js @@ -10,7 +10,6 @@ module.exports = { schema: { userRoleId: { type: String, - unique: true, }, title: { type: String, @@ -41,9 +40,18 @@ module.exports = { }, tenantId: { type: String, + require: true, }, - orgId: { + orgIds: { type: Array, + require: true, + index: true, }, }, + compoundIndex: [ + { + name: { userRoleId: 1, tenantId: 1 }, + indexType: { unique: true }, + }, + ], } diff --git a/src/module/entities/helper.js b/src/module/entities/helper.js index aa87f00..638329a 100644 --- a/src/module/entities/helper.js +++ b/src/module/entities/helper.js @@ -177,7 +177,9 @@ module.exports = class UserProjectsHelper { * List of Entities * @method * @name listByEntityIds - * @param bodyData - Body data. + * @param {Array} entityIds + * @param {Array} fields + * @param {Object} userDetails - user's loggedin info * @returns {Array} List of Entities. */ @@ -185,9 +187,7 @@ module.exports = class UserProjectsHelper { return new Promise(async (resolve, reject) => { try { // Call 'entitiesQueries.entityDocuments' to retrieve entities based on provided entity IDs and fields - let tenantId = userDetails.tenantAndOrgInfo - ? userDetails.tenantAndOrgInfo.tenantId - : userDetails.userInformation.tenantId + let tenantId = userDetails.userInformation.tenantId const entities = await entitiesQueries.entityDocuments( { @@ -218,12 +218,14 @@ module.exports = class UserProjectsHelper { * @param {params} limit - page limit. * @param {params} pageNo - page no. * @param {params} language - language Code + * @param {Object} userDetails - loggedin user's details * @returns {Array} - List of all sub list entities. */ - static subEntityList(entities, entityId, type, search, limit, pageNo, language) { + static subEntityList(entities, entityId, type, search, limit, pageNo, language, userDetails) { return new Promise(async (resolve, reject) => { try { + let tenantId = userDetails.userInformation.tenantId let result = [] let obj = { entityId: entityId, @@ -234,13 +236,13 @@ module.exports = class UserProjectsHelper { } // Retrieve sub-entities using 'this.subEntities' for a single entity if (entityId !== '') { - result = await this.subEntities(obj, language) + result = await this.subEntities(obj, language, tenantId) } else { // Retrieve sub-entities using 'this.subEntities' for multiple entities await Promise.all( entities.map(async (entity) => { obj['entityId'] = entity - let entitiesDocument = await this.subEntities(obj, language) + let entitiesDocument = await this.subEntities(obj, language, tenantId) if (Array.isArray(entitiesDocument.data) && entitiesDocument.data.length > 0) { result = entitiesDocument @@ -261,6 +263,8 @@ module.exports = class UserProjectsHelper { $in: entityIds, } + entityFilter['tenantId'] = tenantId + // Retrieve all the entity documents with the entity ids in their gropu const entityDocuments = await entitiesQueries.entityDocuments(entityFilter, [ 'entityType', @@ -331,6 +335,7 @@ module.exports = class UserProjectsHelper { _id: { $in: entityId, }, + tenantId: tenantId, } const projectionFields = ['childHierarchyPath', 'entityType'] // Retrieve entityDetails based on provided entity IDs @@ -392,6 +397,7 @@ module.exports = class UserProjectsHelper { $in: userRoleFilter, }, status: CONSTANTS.common.ACTIVE_STATUS, + tenantId: tenantId, } // Specify the fields to include in the result set @@ -439,10 +445,11 @@ module.exports = class UserProjectsHelper { * @method * @name subEntities * @param {body} entitiesData + * @param {String} tenantId * @returns {Array} - List of all immediate entities or traversal data. */ - static subEntities(entitiesData, language) { + static subEntities(entitiesData, language, tenantId) { return new Promise(async (resolve, reject) => { try { let entitiesDocument @@ -455,7 +462,8 @@ module.exports = class UserProjectsHelper { entitiesData.search, entitiesData.limit, entitiesData.pageNo, - language + language, + tenantId ) } else { // Retrieve immediate entities @@ -464,7 +472,8 @@ module.exports = class UserProjectsHelper { entitiesData.search, entitiesData.limit, entitiesData.pageNo, - language + language, + tenantId ) } @@ -480,10 +489,14 @@ module.exports = class UserProjectsHelper { * @method * @name immediateEntities * @param {Object} entityId + * @param {String} searchText + * @param {String} pageSize + * @param {String} pageNo + * @param {String} tenantId - user's tenant id * @returns {Array} - List of all immediateEntities based on entityId. */ - static immediateEntities(entityId, searchText = '', pageSize = '', pageNo = '') { + static immediateEntities(entityId, searchText = '', pageSize = '', pageNo = '', tenantId) { return new Promise(async (resolve, reject) => { try { // Define projection fields for entity retrieval @@ -492,6 +505,7 @@ module.exports = class UserProjectsHelper { let entitiesDocument = await entitiesQueries.entityDocuments( { _id: entityId, + tenantId: tenantId, }, projection ) @@ -505,6 +519,7 @@ module.exports = class UserProjectsHelper { let getImmediateEntityTypes = await entityTypesHelper.entityTypesDocument( { name: entitiesDocument[0].entityType, + tenantId: tenantId, }, ['immediateChildrenEntityType'] ) @@ -544,10 +559,11 @@ module.exports = class UserProjectsHelper { * @param {Number} pageSize - total page size. * @param {Number} pageNo - Page no. * @param {String} searchText - Search Text. + * @param {String} tenantId - user's tenant id * @returns {Array} - List of all immediateEntities based on entityId. */ - static entityTraversal(entityId, entityTraversalType = '', searchText = '', pageSize, pageNo, language) { + static entityTraversal(entityId, entityTraversalType = '', searchText = '', pageSize, pageNo, language, tenantId) { return new Promise(async (resolve, reject) => { try { let entityTraversal = `groups.${entityTraversalType}` @@ -557,6 +573,7 @@ module.exports = class UserProjectsHelper { _id: entityId, groups: { $exists: true }, [entityTraversal]: { $exists: true }, + tenantId: tenantId, }, [entityTraversal] ) @@ -572,7 +589,8 @@ module.exports = class UserProjectsHelper { pageSize, pageNo, entitiesDocument[0].groups[entityTraversalType], - language + language, + tenantId ) result = entityTraversalData[0] @@ -592,15 +610,18 @@ module.exports = class UserProjectsHelper { * @param {String} language - language Code. * @param {Number} pageSize - total page size. * @param {Number} pageNo - Page no. + * @param {String} tenantId - user's tenantId * @param {Array} [entityIds = false] - Array of entity ids. */ - static search(searchText, pageSize, pageNo, entityIds = false, language) { + static search(searchText, pageSize, pageNo, entityIds = false, language, tenantId) { return new Promise(async (resolve, reject) => { try { let queryObject = {} // Configure match criteria based on search text and entity IDs (if provided) - queryObject['$match'] = {} + queryObject['$match'] = { + tenantId: tenantId, + } if (entityIds && entityIds.length > 0) { queryObject['$match']['_id'] = {} @@ -884,10 +905,11 @@ module.exports = class UserProjectsHelper { * @param {String} entityTypeId - entity type id. * @param {String} entityType - entity type. * @param {Array} [projection = "all"] - total fields to be projected. + * @param {String} tenantId - user's tenant id * @returns {Array} - returns an array of related entities data. */ - static relatedEntities(entityId, entityTypeId, entityType, projection = 'all') { + static relatedEntities(entityId, entityTypeId, entityType, projection = 'all', tenantId) { return new Promise(async (resolve, reject) => { try { // if ( @@ -899,7 +921,9 @@ module.exports = class UserProjectsHelper { // return resolve(this.entityMapProcessData.relatedEntities[entityId.toString()]) // } - let relatedEntitiesQuery = {} + let relatedEntitiesQuery = { + tenantId, + } if (entityTypeId && entityId && entityType) { relatedEntitiesQuery[`groups.${entityType}`] = entityId @@ -932,7 +956,7 @@ module.exports = class UserProjectsHelper { * Sub entity type list. * @method * @name subEntityListBasedOnRoleAndLocation - * @param role - role code + * @param userDetails - loggedin user's details * @param stateLocationId - state location id. * @returns {Array} List of sub entity type. */ @@ -950,9 +974,7 @@ module.exports = class UserProjectsHelper { // message: CONSTANTS.apiResponses.USER_ROLES_NOT_FOUND // } // } - let tenantId = userDetails.tenantAndOrgInfo - ? userDetails.tenantAndOrgInfo.tenantId - : userDetails.userInformation.tenantId + let tenantId = userDetails.userInformation.tenantId let filterQuery = { 'registryDetails.code': stateLocationId, @@ -1019,15 +1041,14 @@ module.exports = class UserProjectsHelper { * @method * @name listByLocationIds * @param {Object} locationIds - locationIds + * @param {Object} userDetails - loggedin user's details * @returns {Object} entity Document */ static listByLocationIds(locationIds, userDetails) { return new Promise(async (resolve, reject) => { try { - let tenantId = userDetails.tenantAndOrgInfo - ? userDetails.tenantAndOrgInfo.tenantId - : userDetails.userInformation.tenantId + let tenantId = userDetails.userInformation.tenantId // Constructing the filter query to find entities based on locationIds let filterQuery = { @@ -1108,7 +1129,6 @@ module.exports = class UserProjectsHelper { * @param {string} pageNo - pageNo for pagination * @param {string} language - language Code * @param {string} pageSize - pageSize for pagination - * @param {Boolean} currentOrgOnly - boolean value to fetch current org assets * @param {Object} userDetails - user decoded token details * @returns {Promise} Promise that resolves with fetched documents or rejects with an error. */ @@ -1117,9 +1137,7 @@ module.exports = class UserProjectsHelper { return new Promise(async (resolve, reject) => { try { let query = {} - query['tenantId'] = userDetails.tenantAndOrgInfo - ? userDetails.tenantAndOrgInfo.tenantId - : userDetails.userInformation.tenantId + query['tenantId'] = userDetails.userInformation.tenantId query['name'] = type // Fetch the list of entity types available const entityList = await entityTypeQueries.entityTypesDocument(query, ['name']) @@ -1142,7 +1160,6 @@ module.exports = class UserProjectsHelper { '', paginate ) - // const count = await entitiesQueries.countEntityDocuments({ entityType: type }) // Check if fetchList list is empty if (!(fetchList.length > 0)) { @@ -1205,7 +1222,7 @@ module.exports = class UserProjectsHelper { // Find the entities document based on the entityType in queryParams let tenantId = userDetails.tenantAndOrgInfo.tenantId - let orgId = userDetails.tenantAndOrgInfo.orgId + let orgId = userDetails.tenantAndOrgInfo.orgIds let entityTypeDocument = await entityTypeQueries.findOne( { name: queryParams.type, tenantId: tenantId }, { _id: 1 } @@ -1271,7 +1288,7 @@ module.exports = class UserProjectsHelper { createdBy: userDetails.userInformation.userId, userId: userDetails.userInformation.userId, tenantId: tenantId, - orgId: orgId, + orgIds: orgId, } entityDocuments.push(entityDoc) @@ -1321,8 +1338,6 @@ module.exports = class UserProjectsHelper { static details(entityId, requestData = {}, language, userDetails) { return new Promise(async (resolve, reject) => { try { - // // let entityIdNum = parseInt(entityId) - // let entityIdNum = entityId.replace(/"/, ''); let entityIds = [] let query = {} query['$or'] = [] @@ -1363,9 +1378,7 @@ module.exports = class UserProjectsHelper { }) } // add tenantId to the query - query['tenantId'] = userDetails.tenantAndOrgInfo - ? userDetails.tenantAndOrgInfo.tenantId - : userDetails.userInformation.tenantId + query['tenantId'] = userDetails.userInformation.tenantId // Fetch entity documents based on constructed query let entityDocument = await entitiesQueries.entityDocuments(query, 'all', 10) @@ -1462,7 +1475,7 @@ module.exports = class UserProjectsHelper { // Find the entity type document based on the provided entityType let tenantId = userDetails.tenantAndOrgInfo.tenantId - let orgId = userDetails.tenantAndOrgInfo.orgId + let orgId = userDetails.tenantAndOrgInfo.orgIds let entityTypeDocument = await entityTypeQueries.findOne( { name: entityType, @@ -1493,7 +1506,7 @@ module.exports = class UserProjectsHelper { updatedBy: userId, createdBy: userId, tenantId: tenantId, - orgId: orgId, + orgIds: orgId, } // if (singleEntity.allowedRoles && singleEntity.allowedRoles.length > 0) { // entityCreation['allowedRoles'] = await allowedRoles(singleEntity.allowedRoles) @@ -1591,9 +1604,10 @@ module.exports = class UserProjectsHelper { * @returns {Array} - Array of updated entity data. */ - static bulkUpdate(entityCSVData, translationFile) { + static bulkUpdate(entityCSVData, translationFile, userDetails) { return new Promise(async (resolve, reject) => { try { + let tenantId = userDetails.tenantAndOrgInfo.tenantId const entityUploadedData = await Promise.all( entityCSVData.map(async (singleEntity) => { singleEntity = UTILS.valueParser(singleEntity) @@ -1653,7 +1667,7 @@ module.exports = class UserProjectsHelper { if (Object.keys(updateData).length > 0) { let updateEntity = await entitiesQueries.findOneAndUpdate( - { _id: singleEntity['_SYSTEM_ID'] }, + { _id: singleEntity['_SYSTEM_ID'], tenantId: tenantId }, { $set: updateData }, { _id: 1 } ) @@ -1690,6 +1704,7 @@ module.exports = class UserProjectsHelper { * @name update * @param {String} entityId - entity id. * @param {Object} data - entity information that need to be updated. + * @param {Object} userDetails - loggedin user's info * @returns {JSON} - Updated entity information. */ @@ -1773,7 +1788,7 @@ module.exports = class UserProjectsHelper { try { // Retrieve the schema meta information key let schemaMetaInformation = this.entitiesSchemaData().SCHEMA_METAINFORMATION - + let tenantId = req.userDetails.userInformation.tenantId // Define projection for entity document fields to retrieve let projection = [ schemaMetaInformation + '.externalId', @@ -1788,6 +1803,7 @@ module.exports = class UserProjectsHelper { let entityDocuments = await entitiesQueries.entityDocuments( { entityTypeId: ObjectId(req.params._id), + tenantId: tenantId, }, projection, req.pageSize, @@ -1839,21 +1855,24 @@ module.exports = class UserProjectsHelper { * @param {String} entityId - requested entity id. * @param {String} [limitingValue = ""] - Limiting value if required. * @param {String} [skippingValue = ""] - Skipping value if required. + * @param {Object} userDetails - loggedin user's details * @returns {JSON} - Details of entity. */ static list( entityType, - entityTypeId, + entityId, limitingValue = '', skippingValue = '', schoolTypes = '', - administrationTypes = '' + administrationTypes = '', + userDetails ) { return new Promise(async (resolve, reject) => { try { // Query for the specified entity type within the given entity type ID document - let queryObject = { _id: ObjectId(entityTypeId) } + let tenantId = userDetails.userInformation.tenantId + let queryObject = { _id: ObjectId(entityId), tenantId: tenantId } let projectObject = { [`groups.${entityType}`]: 1 } let result = await entitiesQueries.findOne(queryObject, projectObject) if (!result) { @@ -1862,8 +1881,7 @@ module.exports = class UserProjectsHelper { message: CONSTANTS.apiResponses.ENTITY_NOT_FOUND, }) } - - // Check if the specified entity group within the document is not found + // Check if the specified entity group within the document is present or not if (!result.groups || !result.groups[entityType]) { return resolve({ status: HTTP_STATUS_CODE.bad_request.status, @@ -1875,13 +1893,12 @@ module.exports = class UserProjectsHelper { let entityIds = result.groups[entityType] const entityTypesArray = await entityTypesHelper.list( - {}, + { tenantId: tenantId }, { name: 1, immediateChildrenEntityType: 1, } ) - let enityTypeToImmediateChildrenEntityMap = {} // Build a map of entity types to their immediate child entity types @@ -1895,7 +1912,7 @@ module.exports = class UserProjectsHelper { } let filteredQuery = { - $match: { _id: { $in: entityIds } }, + $match: { _id: { $in: entityIds }, tenantId: tenantId }, } let schoolOrAdministrationTypes = [] diff --git a/src/module/entities/validator/v1.js b/src/module/entities/validator/v1.js index 4f048fc..6528b2f 100644 --- a/src/module/entities/validator/v1.js +++ b/src/module/entities/validator/v1.js @@ -61,7 +61,7 @@ module.exports = (req) => { }, find: function () { req.checkBody('query').exists().withMessage('required query') - // req.checkBody('projection').exists().withMessage('required projection') + req.checkBody('query.tenantId').exists().withMessage('required tenant id') }, listByEntityType: function () { req.checkParams('_id').exists().withMessage('required Entity type') diff --git a/src/module/entityTypes/helper.js b/src/module/entityTypes/helper.js index 34ee4b3..077d4aa 100644 --- a/src/module/entityTypes/helper.js +++ b/src/module/entityTypes/helper.js @@ -33,7 +33,7 @@ module.exports = class UserProjectsHelper { try { entityType = UTILS.valueParser(entityType) entityType['tenantId'] = userDetails.tenantAndOrgInfo.tenantId - entityType['orgId'] = userDetails.tenantAndOrgInfo.orgId + entityType['orgIds'] = userDetails.tenantAndOrgInfo.orgIds entityType.registryDetails = {} let removedKeys = [] @@ -142,7 +142,7 @@ module.exports = class UserProjectsHelper { try { let entityType = body entityType['tenantId'] = userDetails.tenantAndOrgInfo.tenantId - entityType['orgId'] = userDetails.tenantAndOrgInfo.orgId + entityType['orgIds'] = userDetails.tenantAndOrgInfo.orgIds if (entityType.profileFields) { entityType.profileFields = entityType.profileFields.split(',') || [] @@ -206,10 +206,11 @@ module.exports = class UserProjectsHelper { }) } /** - * update single entity. + * update single entityType. * @method * @name update - * @param {Object} data - requested entity data. + * @param {Object} entityTypeId - entity type id. + * @param {Object} bodyData - requested entity data. * @param {Object} userDetails - Logged in user information. * @returns {JSON} - update single entity. * @@ -220,7 +221,7 @@ module.exports = class UserProjectsHelper { try { // avoid adding manupulative data delete bodyData.tenantId - delete bodyData.orgId + delete bodyData.orgIds let tenantId = userDetails.tenantAndOrgInfo.tenantId @@ -258,6 +259,7 @@ module.exports = class UserProjectsHelper { static bulkUpdate(entityTypesCSVData, userDetails) { return new Promise(async (resolve, reject) => { try { + let tenantId = userDetails.tenantAndOrgInfo.tenantId // Process each entity type in the provided array asynchronously const entityTypesUploadedData = await Promise.all( entityTypesCSVData.map(async (entityType) => { @@ -325,6 +327,7 @@ module.exports = class UserProjectsHelper { let updateEntityType = await entityTypeQueries.findOneAndUpdate( { _id: ObjectId(entityType._SYSTEM_ID), + tenantId: tenantId, }, _.merge( diff --git a/src/module/entityTypes/validator/v1.js b/src/module/entityTypes/validator/v1.js index c142615..266203e 100644 --- a/src/module/entityTypes/validator/v1.js +++ b/src/module/entityTypes/validator/v1.js @@ -38,7 +38,8 @@ module.exports = (req, res) => { }, find: function () { - req.checkBody('query').exists().withMessage('required name') + req.checkBody('query').exists().withMessage('required query') + req.checkBody('query.tenantId').exists().withMessage('required tenantId') }, } diff --git a/src/module/userRoleExtension/helper.js b/src/module/userRoleExtension/helper.js index 82f2de6..daf0fba 100644 --- a/src/module/userRoleExtension/helper.js +++ b/src/module/userRoleExtension/helper.js @@ -15,31 +15,35 @@ module.exports = class userRoleExtensionHelper { /** * Create a new user role extension with the provided body data. * @param {Object} body - The data to create the new user role extension. + * @param {Object} userDetails - loggedin user details * @returns {Promise} - A promise that resolves with the new user role extension data or rejects with an error. */ static create(body, userDetails) { return new Promise(async (resolve, reject) => { try { + let tenantId = userDetails.tenantAndOrgInfo.tenantId // Using map to handle validation await Promise.all( body.entityTypes.map(async (entityTypeData) => { // Validate that both entityType and entityTypeId exist in the entityType DB - let existingEntityType = await entityTypeQueries.findOne({ + let filterQuery = { name: entityTypeData.entityType, _id: ObjectId(entityTypeData.entityTypeId), - }) + tenantId: tenantId, + } + let existingEntityType = await entityTypeQueries.findOne(filterQuery) if (!existingEntityType) { // If any entityType is invalid, reject the request throw { status: HTTP_STATUS_CODE.bad_request.status, - message: `EntityType '${entityTypeData.entityType}' with ID '${entityTypeData.entityTypeId}' does not exist.`, + message: `EntityType '${entityTypeData.entityType}' with ID '${entityTypeData.entityTypeId}' & tenantId ${tenantId} does not exist.`, } } }) ) body['tenantId'] = userDetails.tenantAndOrgInfo.tenantId - body['orgId'] = userDetails.tenantAndOrgInfo.orgId + body['orgIds'] = userDetails.tenantAndOrgInfo.orgIds // Call the queries function to create a new user role extension with the provided body data let newUserRole = await userRoleExtensionQueries.create(body) @@ -57,28 +61,29 @@ module.exports = class userRoleExtensionHelper { * Update a user role extension with the provided userRoleId and body data. * @param {ObjectId} userRoleId - The ID of the user role extension to be updated. * @param {Object} bodyData - The data to update the user role extension. + * @param {Object} userDetails - loggedin user details * @returns {Promise} - A promise that resolves with the updated user role extension data or rejects with an error. */ - static update(userRoleId, bodyData, userDetails) { + static update(documentId, bodyData, userDetails) { return new Promise(async (resolve, reject) => { try { let tenantId = userDetails.tenantAndOrgInfo.tenantId - let orgId = userDetails.tenantAndOrgInfo.orgId if (bodyData.entityTypes) { await Promise.all( bodyData.entityTypes.map(async (entityTypeData) => { // Validate that both entityType and entityTypeId exist in the entityType DB - let existingEntityType = await entityTypeQueries.findOne({ + let entityTypeFilterQuery = { name: entityTypeData.entityType, _id: ObjectId(entityTypeData.entityTypeId), tenantId: tenantId, - }) + } + let existingEntityType = await entityTypeQueries.findOne(entityTypeFilterQuery) if (!existingEntityType) { // If any entityType is invalid, reject the request throw { status: HTTP_STATUS_CODE.bad_request.status, - message: `EntityType '${entityTypeData.entityType}' with ID '${entityTypeData.entityTypeId}' does not exist.`, + message: `EntityType '${entityTypeData.entityType}' with ID '${entityTypeData.entityTypeId}' & tenantId ${tenantId} does not exist.`, } } }) @@ -86,11 +91,11 @@ module.exports = class userRoleExtensionHelper { } delete bodyData.tenantId - delete bodyData.orgId + delete bodyData.orgIds // Find and update the user role extension based on the provided userRoleId and bodyData let userInformation = await userRoleExtensionQueries.findOneAndUpdate( - { _id: ObjectId(userRoleId) }, + { _id: ObjectId(documentId), tenantId: tenantId }, bodyData, { new: true } ) @@ -153,16 +158,17 @@ module.exports = class userRoleExtensionHelper { /** * Delete a user role extension by its ID. - * @param {String} userRoleId - The ID of the user role extension to delete. + * @param {String} documentId - The ID of the user role extension to delete. + * @param {Object} userDetails - loggedin user details * @returns {Promise} - A promise that resolves with a success message or rejects with an error. */ - static delete(userRoleId, userDetails) { + static delete(documentId, userDetails) { return new Promise(async (resolve, reject) => { try { let tenantId = userDetails.tenantAndOrgInfo.tenantId // Find and delete the user role extension based on the provided user role ID let userInformation = await userRoleExtensionQueries.findOneAndDelete({ - _id: ObjectId(userRoleId), + _id: ObjectId(documentId), tenantId: tenantId, }) From 42847ceb423c1b84d69068b1234c5446e5ebdeb2 Mon Sep 17 00:00:00 2001 From: prajwal Date: Sat, 26 Apr 2025 18:30:33 +0530 Subject: [PATCH 05/91] resolved conflict --- src/generics/constants/api-responses.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/generics/constants/api-responses.js b/src/generics/constants/api-responses.js index 0e4e996..c3e4a9e 100644 --- a/src/generics/constants/api-responses.js +++ b/src/generics/constants/api-responses.js @@ -64,6 +64,9 @@ module.exports = { INVALID_TENANT_AND_ORG_MESSAGE: 'Invalid tenant and org info', ORG_DETAILS_FETCH_UNSUCCESSFUL_CODE: 'ERR_ORG_DETAILS_NOT_FETCHED', ORG_DETAILS_FETCH_UNSUCCESSFUL_MESSAGE: 'Org details fetch unsuccessful', + KEYS_INDEXED_SUCCESSFULL: 'KEYS_INDEXED_SUCCESSFULL', + KEYS_ALREADY_INDEXED_SUCCESSFULL: 'KEYS_ALREADY_INDEXED_SUCCESSFULL', + NOT_VALID_ID_AND_EXTERNALID: 'NOT_VALID_ID_AND_EXTERNALID', TENANTID_AND_ORGID_REQUIRED_IN_TOKEN_MESSAGE: 'TenantId and OrgnizationId required in the token', TENANTID_AND_ORGID_REQUIRED_IN_TOKEN_CODE: 'ERR_TENANTID_AND_ORGID_REQUIRED', } From 690d69a6af05150952920274a149b4551098f3cd Mon Sep 17 00:00:00 2001 From: prajwal Date: Tue, 29 Apr 2025 13:24:29 +0530 Subject: [PATCH 06/91] new authenticator changes added --- config.json | 10 + src/generics/middleware/authenticator.js | 276 ++++++++++++++++------- 2 files changed, 204 insertions(+), 82 deletions(-) create mode 100644 config.json diff --git a/config.json b/config.json new file mode 100644 index 0000000..2faeebc --- /dev/null +++ b/config.json @@ -0,0 +1,10 @@ +{ + "authTokenUserInformation": { + "userId": "data.id", + "userName": "data.name", + "firstName": "data.name", + "roles": "data.roles", + "organizationId": "data.organization_id", + "tenantId": "data.tenant_id" + } +} diff --git a/src/generics/middleware/authenticator.js b/src/generics/middleware/authenticator.js index 499e859..93f2b50 100644 --- a/src/generics/middleware/authenticator.js +++ b/src/generics/middleware/authenticator.js @@ -95,6 +95,7 @@ module.exports = async function (req, res, next, token = '') { // <---- For Elevate user service user compactibility ----> let decodedToken = null + let userInformation = {} try { if (process.env.AUTH_METHOD === CONSTANTS.common.AUTH_METHOD.NATIVE) { try { @@ -173,6 +174,66 @@ module.exports = async function (req, res, next, token = '') { return res.status(HTTP_STATUS_CODE['unauthorized'].status).send(respUtil(rspObj)) } + // Path to config.json + const configFilePath = path.resolve(__dirname, '../../../config.json') + // Initialize variables + let configData = {} + let defaultTokenExtraction = false + + // Check if config.json exists + if (fs.existsSync(configFilePath)) { + // Read and parse the config.json file + const rawData = fs.readFileSync(configFilePath) + try { + configData = JSON.parse(rawData) + if (!configData.authTokenUserInformation) { + defaultTokenExtraction = true + } + configData = configData.authTokenUserInformation + } catch (error) { + console.error('Error parsing config.json:', error) + } + } else { + // If file doesn't exist, set defaultTokenExtraction to true + defaultTokenExtraction = true + } + + // Create user details to request + req.userDetails = { + userToken: token, + } + // performing default token data extraction + if (defaultTokenExtraction) { + if (!decodedToken.data.organization_id) { + const orgId = req.get(process.env.ORG_ID_HEADER_NAME) + if (orgId && orgId != '') { + decodedToken.data.organization_id = orgId.toString() + } else decodedToken.data.organization_id = null + } + userInformation = { + userId: + typeof decodedToken.data.id == 'string' ? decodedToken.data.id : decodedToken.data.id.toString(), + userName: decodedToken.data.name, + organizationId: decodedToken.data.organization_id ? decodedToken.data.organization_id.toString() : null, + firstName: decodedToken.data.name, + roles: decodedToken.data.roles.map((role) => role.title), + tenantId: decodedToken.data.tenant_id.toString(), + } + } else { + // Iterate through each key in the config object + for (let key in configData) { + let stringTypeKeys = ['userId', 'tenantId', 'organizationId'] + if (configData.hasOwnProperty(key)) { + let keyValue = getNestedValue(decodedToken, configData[key]) + if (stringTypeKeys.includes(key)) { + keyValue = keyValue.toString() + } + // For each key in config, assign the corresponding value from decodedToken + userInformation[`${key}`] = keyValue + } + } + } + // throw error if tenant_id or organization_id is not present in the decoded token if ( !decodedToken.data.tenant_id || @@ -186,100 +247,155 @@ module.exports = async function (req, res, next, token = '') { return res.status(HTTP_STATUS_CODE['bad_request'].status).send(respUtil(rspObj)) } - if (performInternalAccessTokenCheck) { - let userRoles = decodedToken.data.roles.map((role) => role.title) - // throw error if normal user tries to fetch internal apis - if ( - !adminHeader && - !userRoles.includes(CONSTANTS.common.TENANT_ADMIN) && - !userRoles.includes(CONSTANTS.common.ORG_ADMIN) - ) { - rspObj.errCode = CONSTANTS.apiResponses.TOKEN_INVALID_CODE - rspObj.errMsg = CONSTANTS.apiResponses.TOKEN_INVALID_MESSAGE - rspObj.responseCode = HTTP_STATUS_CODE['unauthorized'].status - return res.status(HTTP_STATUS_CODE['unauthorized'].status).send(respUtil(rspObj)) - } - - // validate SUPER_ADMIN - if (adminHeader) { - if (adminHeader != process.env.ADMIN_ACCESS_TOKEN) { - return res.status(HTTP_STATUS_CODE['unauthorized'].status).send(respUtil(rspObj)) - } - decodedToken.data.roles.push({ title: CONSTANTS.common.ADMIN_ROLE }) - userRoles.push(CONSTANTS.common.ADMIN_ROLE) - } - - // check if tenantId and orgId is present in the header for SUPER_ADMIN & TENANT_ADMIN roles - if (userRoles.includes(CONSTANTS.common.ADMIN_ROLE) || userRoles.includes(CONSTANTS.common.TENANT_ADMIN)) { - if ( - !req.headers['tenantid'] || - !req.headers['orgid'] || - !req.headers['tenantid'].length || - !req.headers['orgid'].length - ) { - rspObj.errCode = CONSTANTS.apiResponses.INVALID_TENANT_AND_ORG_CODE - rspObj.errMsg = CONSTANTS.apiResponses.INVALID_TENANT_AND_ORG_MESSAGE - rspObj.responseCode = HTTP_STATUS_CODE['bad_request'].status - return res.status(HTTP_STATUS_CODE['bad_request'].status).send(respUtil(rspObj)) - } - } - decodedToken.data['tenantAndOrgInfo'] = {} + /** + * Validate if provided orgId(s) belong to the tenant by checking against related_orgs. + * + * @param {String} tenantId - ID of the tenant + * @param {String} orgId - Comma separated string of org IDs or 'ALL' + * @returns {Object} - Success with validOrgIds array or failure with error object + */ + async function validateIfOrgsBelongsToTenant(tenantId, orgId) { + let orgIdArr = orgId?.split(',') || [] + let orgDetails = await userService.fetchDefaultOrgDetails(tenantId) + let validOrgIds = null - let relatedOrgDetails = false - let validOrgIds = [] - if (!userRoles.includes(CONSTANTS.common.ORG_ADMIN) && req.headers['tenantid'] !== '') { - // fetch the related org details using organization/read api - relatedOrgDetails = await userService.fetchOrgDetails(req.headers['tenantid']) + if (orgIdArr.includes('ALL') || orgIdArr.includes('all')) { + validOrgIds = ['ALL'] + } else { if ( - !relatedOrgDetails || - !relatedOrgDetails.success || - !relatedOrgDetails.data || - !(Object.keys(relatedOrgDetails.data).length > 0) || - !(relatedOrgDetails.data.related_orgs > 0) + !orgDetails || + !orgDetails.success || + !orgDetails.data || + !(Object.keys(orgDetails.data).length > 0) || + !orgDetails.data.related_orgs || + !(orgDetails.data.related_orgs > 0) ) { - rspObj.errCode = CONSTANTS.apiResponses.ORG_DETAILS_FETCH_UNSUCCESSFUL_CODE - rspObj.errMsg = CONSTANTS.apiResponses.ORG_DETAILS_FETCH_UNSUCCESSFUL_MESSAGE - rspObj.responseCode = HTTP_STATUS_CODE['bad_request'].status - return res.status(HTTP_STATUS_CODE['bad_request'].status).send(respUtil(rspObj)) + let errorObj = {} + errorObj.errCode = CONSTANTS.apiResponses.ORG_DETAILS_FETCH_UNSUCCESSFUL_CODE + errorObj.errMsg = CONSTANTS.apiResponses.ORG_DETAILS_FETCH_UNSUCCESSFUL_MESSAGE + errorObj.responseCode = HTTP_STATUS_CODE['bad_request'].status + return { success: false, errorObj: errorObj } } // convert the types of items to string - relatedOrgDetails.data.related_orgs = relatedOrgDetails.data.related_orgs.map(String) + orgDetails.data.related_orgs = orgDetails.data.related_orgs.map(String) // aggregate valid orgids - let headerOrgIds = req.headers['orgid']?.split(',') || [] - let relatedOrgIds = relatedOrgDetails.data.related_orgs - validOrgIds = headerOrgIds.filter((id) => { - return relatedOrgIds.includes(id) || id == 'all' - }) - // if the valid orgids array is empty throw error + + let relatedOrgIds = orgDetails.data.related_orgs + + validOrgIds = orgIdArr.filter((id) => relatedOrgIds.includes(id)) + if (!(validOrgIds.length > 0)) { - rspObj.errCode = CONSTANTS.apiResponses.INVALID_TENANT_AND_ORG_CODE - rspObj.errMsg = CONSTANTS.apiResponses.INVALID_TENANT_AND_ORG_MESSAGE + rspObj.errCode = CONSTANTS.apiResponses.TENANTID_AND_ORGID_REQUIRED_IN_TOKEN_CODE + rspObj.errMsg = CONSTANTS.apiResponses.TENANTID_AND_ORGID_REQUIRED_IN_TOKEN_MESSAGE rspObj.responseCode = HTTP_STATUS_CODE['bad_request'].status return res.status(HTTP_STATUS_CODE['bad_request'].status).send(respUtil(rspObj)) } } - // set the tenant & org details for ORG_ADMIN - if (userRoles.includes(CONSTANTS.common.ORG_ADMIN)) { - decodedToken.data.tenantAndOrgInfo = { - orgIds: [decodedToken.data.organization_id.toString()], - tenantId: decodedToken.data.tenant_id.toString(), + return { success: true, validOrgIds: validOrgIds } + } + /** + * Extract tenantId and orgId from incoming request or decoded token. + * + * Priority order: body -> query -> headers -> decoded token data + * + * @param {Object} req - Express request object + * @param {Object} decodedTokenData - Decoded JWT token data + * @returns {Object} - Success with tenantId and orgId or failure object + */ + function getTenantIdAndOrgIdFromTheTheReqIntoHeaders(req, decodedTokenData) { + // Step 1: Check in the request body + if (req.body && req.body.tenantId && req.body.orgId) { + return { success: true, tenantId: req.body.tenantId, orgId: req.body.orgId } + } + + // Step 2: Check in query parameters if not found in body + if (req.query.tenantId && req.query.orgId) { + return { success: true, tenantId: req.query.tenantId, orgId: req.query.orgId } + } + + // Step 3: Check in headers if not found in query params + if (req.headers['tenantid'] && req.headers['orgid']) { + return { success: true, tenantId: req.headers['tenantid'], orgId: req.headers['orgid'] } + } + + // Step 4: Check in user token (already decoded) if still not found + if (decodedTokenData && decodedTokenData.tenantId && decodedTokenData.orgId) { + return { success: true, tenantId: decodedTokenData.tenantId, orgId: decodedTokenData.orgId } + } + + return { sucess: false } + } + + let userRoles = decodedToken.data.roles.map((role) => role.title) + + if (performInternalAccessTokenCheck) { + decodedToken.data['tenantAndOrgInfo'] = {} + // validate SUPER_ADMIN + if (adminHeader) { + if (adminHeader != process.env.ADMIN_ACCESS_TOKEN) { + return res.status(HTTP_STATUS_CODE['unauthorized'].status).send(respUtil(rspObj)) + } + decodedToken.data.roles.push({ title: CONSTANTS.common.ADMIN_ROLE }) + + let result = getTenantIdAndOrgIdFromTheTheReqIntoHeaders(req, decodedToken.data) + if (!result.success) { + rspObj.errCode = reqMsg.ADMIN_TOKEN.MISSING_CODE + rspObj.errMsg = reqMsg.ADMIN_TOKEN.MISSING_MESSAGE + rspObj.responseCode = responseCode.unauthorized.status + return res.status(responseCode.unauthorized.status).send(respUtil(rspObj)) + } + + req.headers['tenantid'] = result.tenantId + req.headers['orgid'] = result.orgId + + let validateOrgsResult = await validateIfOrgsBelongsToTenant( + req.headers['tenantid'], + req.headers['orgid'] + ) + + if (!validateOrgsResult.success) { + return res + .status(HTTP_STATUS_CODE['unauthorized'].status) + .send(respUtil(validateOrgsResult.errorObj)) } + + req.headers['orgid'] = validateOrgsResult.validOrgIds } else if (userRoles.includes(CONSTANTS.common.TENANT_ADMIN)) { - // throw error if decodedToken tenant_id & header tenantId does not match for TENANT_ADMIN role - if (req.headers['tenantid'] !== decodedToken.data.tenant_id.toString()) { + req.headers['tenantid'] = decodedToken.data.tenant_id.toString() + + let orgId = req.body.orgId || req.headers['orgid'] + + if (!orgId) { rspObj.errCode = CONSTANTS.apiResponses.INVALID_TENANT_AND_ORG_CODE rspObj.errMsg = CONSTANTS.apiResponses.INVALID_TENANT_AND_ORG_MESSAGE rspObj.responseCode = HTTP_STATUS_CODE['bad_request'].status return res.status(HTTP_STATUS_CODE['bad_request'].status).send(respUtil(rspObj)) } - decodedToken.data.tenantAndOrgInfo['tenantId'] = req.headers['tenantid'].toString() - decodedToken.data.tenantAndOrgInfo['orgIds'] = validOrgIds - } else if (adminHeader) { - decodedToken.data.tenantAndOrgInfo['tenantId'] = req.headers['tenantid'].toString() - decodedToken.data.tenantAndOrgInfo['orgIds'] = validOrgIds + + req.headers['orgid'] = orgId + + let validateOrgsResult = await validateIfOrgsBelongsToTenant( + req.headers['tenantid'], + req.headers['orgid'] + ) + if (!validateOrgsResult.success) { + return res.status(responseCode['unauthorized'].status).send(respUtil(validateOrgsResult.errorObj)) + } + req.headers['orgid'] = validateOrgsResult.validOrgIds + } else if (userRoles.includes(CONSTANTS.common.ORG_ADMIN)) { + req.headers['tenantid'] = decodedToken.data.tenant_id.toString() + req.headers['orgid'] = [decodedToken.data.organization_id.toString()] + } else { + rspObj.errCode = reqMsg.INVALID_ROLE.INVALID_CODE + rspObj.errMsg = reqMsg.INVALID_ROLE.INVALID_MESSAGE + rspObj.responseCode = responseCode.unauthorized.status + return res.status(responseCode['unauthorized'].status).send(respUtil(rspObj)) } + + decodedToken.data.tenantAndOrgInfo['tenantId'] = req.headers['tenantid'].toString() + decodedToken.data.tenantAndOrgInfo['orgIds'] = req.headers['orgid'] } } catch (err) { rspObj.errCode = CONSTANTS.apiResponses.TOKEN_MISSING_CODE @@ -289,19 +405,15 @@ module.exports = async function (req, res, next, token = '') { } req.userDetails = { userToken: token, - userInformation: { - userId: typeof decodedToken.data.id == 'string' ? decodedToken.data.id : decodedToken.data.id.toString(), - userName: decodedToken.data.name, - // email : decodedToken.data.email, //email is removed from token - firstName: decodedToken.data.name, - roles: decodedToken.data.roles.map((role) => role.title), - organizationId: decodedToken.data.organization_id ? decodedToken.data.organization_id.toString() : null, - tenantId: decodedToken.data.tenant_id.toString(), - }, + userInformation: userInformation, } // add tenantAndOrgInfo to req object only for admin(s) if (decodedToken.data.tenantAndOrgInfo) { req.userDetails.tenantAndOrgInfo = decodedToken.data.tenantAndOrgInfo } + // Helper function to access nested properties + function getNestedValue(obj, path) { + return path.split('.').reduce((acc, part) => acc && acc[part], obj) + } next() } From 0c61d8d118de1645e2707a61e5372d47ba56dddf Mon Sep 17 00:00:00 2001 From: prajwal Date: Tue, 29 Apr 2025 15:51:17 +0530 Subject: [PATCH 07/91] updated function signatures in controllers --- src/controllers/v1/entities.js | 31 +++++++------------------ src/controllers/v1/entityTypes.js | 6 ----- src/controllers/v1/userRoleExtension.js | 13 +++-------- 3 files changed, 12 insertions(+), 38 deletions(-) diff --git a/src/controllers/v1/entities.js b/src/controllers/v1/entities.js index 5810e51..d876359 100644 --- a/src/controllers/v1/entities.js +++ b/src/controllers/v1/entities.js @@ -42,6 +42,7 @@ module.exports = class Entities extends Abstract { * @api {POST} /v1/entities/find all the API based on projection * @apiVersion 1.0.0 * @apiName find + * @param {Object} req - The request object. * @apiGroup Entities * @apiSampleRequest { "query" : { @@ -87,6 +88,7 @@ module.exports = class Entities extends Abstract { * @apiVersion 1.0.0 * @apiName Get Related Entities * @apiGroup Entities + * @param {Object} req - The request object. * @apiSampleRequest v1/entities/relatedEntities/5c0bbab881bdbe330655da7f * @apiUse successBody * @apiUse errorBody @@ -171,6 +173,7 @@ module.exports = class Entities extends Abstract { * @apiVersion 1.0.0 * @apiName entityListBasedOnEntityType * @apiGroup Entities + * @param {Object} req - The request object. * @apiUse successBody * @apiUse errorBody * @apiParamExample {json} Response: @@ -221,6 +224,7 @@ module.exports = class Entities extends Abstract { * @apiName createMappingCsv * @apiGroup Entities * @apiParam {File} entityCSV Mandatory entity mapping file of type CSV. + * @param {Object} req - The request object. * @apiUse successBody * @apiUse errorBody * @param {Object} req - The request object containing the uploaded CSV file in `req.files.entityCSV`. @@ -294,6 +298,7 @@ module.exports = class Entities extends Abstract { * @apiName mappingUpload * @apiGroup Entities * @apiParam {File} entityMap Mandatory entity mapping file of type CSV. + * @param {Object} req - The request object. * @apiUse successBody * @apiUse errorBody * @param {Array} req.files.entityMap - Array of entityMap data. @@ -346,8 +351,6 @@ module.exports = class Entities extends Abstract { * @apiUse successBody * @apiUse errorBody * @param {Object} req - The request object containing parameters and user details. - * @param {Object} req.params - The request parameters. - * @param {string} req.params._id - The entity ID to filter roles. * @returns {Promise} A promise that resolves to the response containing the fetched roles or an error object. * * @returns {JSON} - Message of successfully response. * @@ -415,6 +418,7 @@ module.exports = class Entities extends Abstract { * @apiName details * @apiGroup Entities * @apiHeader {String} X-authenticated-user-token Authenticity token + * @param {Object} req - The request object. * @apiSampleRequest v1/entities/details/67dcf90f97174bab15241faa?&language=hi * @apiUse successBody * @apiUse errorBody @@ -486,9 +490,6 @@ module.exports = class Entities extends Abstract { * @apiUse successBody * @apiUse errorBody * @param {Object} req - requested entity data. - * @param {String} req.query.type - entity type. - * @param {String} req.params._id - entity id. - * @param {Object} req.body - entity information that need to be updated. * @returns {JSON} - Updated entity information. * * @@ -542,7 +543,6 @@ module.exports = class Entities extends Abstract { * @apiUse successBody * @apiUse errorBody * @param {Object} req - All requested Data. - * @param {Object} req.files - requested files. * @returns {JSON} - Added entities information. * * "result": [ @@ -601,7 +601,6 @@ module.exports = class Entities extends Abstract { * @apiUse successBody * @apiUse errorBody * @param {Object} req - requested data. - * @param {Object} req.body.locationIds - registry data. * @returns {Object} - * * "result": [ @@ -650,6 +649,7 @@ module.exports = class Entities extends Abstract { * @apiGroup Entities * @apiHeader {String} X-authenticated-user-token Authenticity token * @apiSampleRequest v1/entities/subEntityListBasedOnRoleAndLocation + * @param {Object} req - The request object. * @apiUse successBody * @apiUse errorBody * @param {String} req.params._id - entityId. @@ -707,7 +707,6 @@ module.exports = class Entities extends Abstract { * @apiUse successBody * @apiUse errorBody * @param {Object} req - requested data. - * @param {String} req.params._id - requested entity type. * @returns {JSON} - Array of entities. "result": [ @@ -752,12 +751,7 @@ module.exports = class Entities extends Abstract { * @apiSampleRequest /v1/entities/list * @apiUse successBody * @apiUse errorBody - * @param {String} req.query.type - type of entity requested. - * @param {String} req.params._id - requested entity id. - * @param {Number} req.pageSize - total size of the page. - * @param {Number} req.pageNo - page number. - * @param {string} req.query.schoolTypes - comma seperated school types. - * @param {string} req.query.administrationTypes - comma seperated administration types. + * @param {Object} req - The request object. * @apiParamExample {json} Response: * "result": [ { @@ -888,8 +882,7 @@ module.exports = class Entities extends Abstract { * List of entities. * @method * @name listByIds - * @param {Object} req - requested data. - * @param {String} req.params._id - requested entity type. + * @param {Object} req - requested data. * @returns {JSON} - Array of entities. */ @@ -924,10 +917,6 @@ module.exports = class Entities extends Abstract { * @apiUse errorBody * @apiParamExample {json} Response: * @param {Object} req - requested data. - * @param {String} req.query.type - requested entity type. - * @param {Object} req.userDetails - logged in user details. - * @param {Object} req.files.entities - entities data. - * @param {Object} req.files.translationFile - translation data. * @returns {CSV} - A CSV with name Entity-Upload is saved inside the folder * public/reports/currentDate * @@ -1001,8 +990,6 @@ module.exports = class Entities extends Abstract { * @apiUse errorBody * @apiParamExample {json} Response: * @param {Object} req - requested data. - * @param {Object} req.files.entities - entities data. - * @param {Object} req.files.translationFile - entities data. * @returns {CSV} - A CSV with name Entity-Upload is saved inside the folder * public/reports/currentDate * diff --git a/src/controllers/v1/entityTypes.js b/src/controllers/v1/entityTypes.js index 1ed62a7..8c1e9db 100644 --- a/src/controllers/v1/entityTypes.js +++ b/src/controllers/v1/entityTypes.js @@ -137,7 +137,6 @@ module.exports = class EntityTypes extends Abstract { * @apiUse successBody * @apiUse errorBody * @param {Object} req -request data. - * @param {Object} req.files.entityTypes -entityTypes data. * @returns {CSV} create single entity Types data. * * "result": { @@ -180,9 +179,6 @@ module.exports = class EntityTypes extends Abstract { * @apiUse successBody * @apiUse errorBody * @param {Object} req - requested entityType data. - * @param {String} req.query.type - entityType type. - * @param {String} req.params._id - entityType id. - * @param {Object} req.body - entityType information that need to be updated. * @returns {JSON} - Updated entityType information. * "result": { "profileForm": [], @@ -237,7 +233,6 @@ module.exports = class EntityTypes extends Abstract { * @apiUse errorBody * @apiParamExample {json} Response: * @param {Object} req -request data. - * @param {Object} req.files.entityTypes -entityTypes data. * @returns {CSV} Bulk create entity Types data. */ async bulkCreate(req) { @@ -302,7 +297,6 @@ module.exports = class EntityTypes extends Abstract { * @apiUse errorBody * @apiParamExample {json} Response: * @param {Object} req -request data. - * @param {Object} req.files.entityTypes -entityTypes data. * @returns {CSV} Bulk update entity Types data. */ async bulkUpdate(req) { diff --git a/src/controllers/v1/userRoleExtension.js b/src/controllers/v1/userRoleExtension.js index 77e7e76..6ad991a 100644 --- a/src/controllers/v1/userRoleExtension.js +++ b/src/controllers/v1/userRoleExtension.js @@ -43,9 +43,7 @@ module.exports = class userRoleExtension extends Abstract { * @apiSampleRequest /v1/userRoleExtension/create * @apiUse successBody * @apiUse errorBody - * @param {Object} req - The request object containing the request body and user details. - * @param {Object} req.body - The data for creating a new user role extension. - * @param {Object} req.userDetails - The details of the user making the request. + * @param {Object} req - The request object. * @returns {Promise} - A promise that resolves with the result of the creation or rejects with an error. * * { @@ -100,10 +98,7 @@ module.exports = class userRoleExtension extends Abstract { * @apiSampleRequest /v1/userRoleExtension/update/663364443c990eaa179e289e * @apiUse successBody * @apiUse errorBody - * @param {Object} req - The request object containing the request parameters and body. - * @param {Object} req.params - The request parameters. - * @param {string} req.params._id - The ID of the user role extension to update. - * @param {Object} req.body - The data for updating the user role extension. + * @param {Object} req - The request object. * @returns {Promise} - A promise that resolves with the result of the update or rejects with an error. * * @@ -155,6 +150,7 @@ module.exports = class userRoleExtension extends Abstract { * @apiVersion 1.0.0 * @apiName find * @apiGroup userRoleExtension + * @param {Object} req - The request object containing the request body. * @apiSampleRequest { { "query": { @@ -226,9 +222,6 @@ module.exports = class userRoleExtension extends Abstract { * @apiUse successBody * @apiUse errorBody * @param {Object} req - The request object containing the request body. - * @param {Object} req.body - The request body. - * @param {Object} req.body.query - The query object to filter user role extensions. - * @param {Array} req.body.projection - The projection array to specify which fields to include in the result. * @returns {Promise} - A promise that resolves with the user data or rejects with an error. * * From 08f0e29204e0e0794b8faa047d028f086d6d0c63 Mon Sep 17 00:00:00 2001 From: prajwal Date: Tue, 29 Apr 2025 16:20:20 +0530 Subject: [PATCH 08/91] indexed fields in model --- src/models/entities.js | 1 + src/models/entityTypes.js | 1 + src/models/userRoleExtension.js | 1 + 3 files changed, 3 insertions(+) diff --git a/src/models/entities.js b/src/models/entities.js index 0b361f1..9cbc380 100644 --- a/src/models/entities.js +++ b/src/models/entities.js @@ -39,6 +39,7 @@ module.exports = { }, tenantId: { type: String, + index: true, require: true, }, orgIds: { diff --git a/src/models/entityTypes.js b/src/models/entityTypes.js index 61ae640..932b46c 100644 --- a/src/models/entityTypes.js +++ b/src/models/entityTypes.js @@ -34,6 +34,7 @@ module.exports = { }, tenantId: { type: String, + index: true, require: true, }, orgIds: { diff --git a/src/models/userRoleExtension.js b/src/models/userRoleExtension.js index b36fba0..f3032c7 100644 --- a/src/models/userRoleExtension.js +++ b/src/models/userRoleExtension.js @@ -40,6 +40,7 @@ module.exports = { }, tenantId: { type: String, + index: true, require: true, }, orgIds: { From ce23deda692edda825bb2d3bb4e721e47d5c3b58 Mon Sep 17 00:00:00 2001 From: prajwal Date: Tue, 29 Apr 2025 16:25:08 +0530 Subject: [PATCH 09/91] functionName fixed in middleware --- src/generics/middleware/authenticator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/generics/middleware/authenticator.js b/src/generics/middleware/authenticator.js index 02b1b5e..9e12588 100644 --- a/src/generics/middleware/authenticator.js +++ b/src/generics/middleware/authenticator.js @@ -274,7 +274,7 @@ module.exports = async function (req, res, next, token = '') { */ async function validateIfOrgsBelongsToTenant(tenantId, orgId) { let orgIdArr = orgId?.split(',') || [] - let orgDetails = await userService.fetchDefaultOrgDetails(tenantId) + let orgDetails = await userService.fetchOrgDetails(tenantId) let validOrgIds = null if (orgIdArr.includes('ALL') || orgIdArr.includes('all')) { From be7aa6329716a024447d6d7ec51d207bd874af8d Mon Sep 17 00:00:00 2001 From: prajwal Date: Tue, 29 Apr 2025 16:36:46 +0530 Subject: [PATCH 10/91] middleware fixed to handle role extraction --- src/generics/middleware/authenticator.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/generics/middleware/authenticator.js b/src/generics/middleware/authenticator.js index 9e12588..5593936 100644 --- a/src/generics/middleware/authenticator.js +++ b/src/generics/middleware/authenticator.js @@ -250,6 +250,9 @@ module.exports = async function (req, res, next, token = '') { userInformation[`${key}`] = keyValue } } + if (userInformation.roles && Array.isArray(userInformation.roles) && userInformation.roles.length) { + userInformation.roles = userInformation.roles.map((role) => role.title) + } } // throw error if tenant_id or organization_id is not present in the decoded token From a99498fa2504bc8a11b90e196a4f9e66806855c8 Mon Sep 17 00:00:00 2001 From: prajwal Date: Wed, 30 Apr 2025 11:21:48 +0530 Subject: [PATCH 11/91] entities model updated --- src/models/entities.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/entities.js b/src/models/entities.js index 9cbc380..a38d52a 100644 --- a/src/models/entities.js +++ b/src/models/entities.js @@ -50,7 +50,7 @@ module.exports = { }, compoundIndex: [ { - name: { 'metaInformation.name': 1, 'metaInformation.externalId': 1, tenantId: 1, entityTypeId: 1 }, + name: { 'metaInformation.externalId': 1, tenantId: 1 }, indexType: { unique: true }, }, ], From 3d6cf441d87d092cfc905970271104c4f95413ff Mon Sep 17 00:00:00 2001 From: prajwal Date: Wed, 30 Apr 2025 18:14:15 +0530 Subject: [PATCH 12/91] gitignore file updated --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 38bd082..f0cbe09 100644 --- a/.gitignore +++ b/.gitignore @@ -78,3 +78,5 @@ gcp1.json dev-ops/report +config.json + From 7b78cf4b2362b649043ba547b77f543885b14cfe Mon Sep 17 00:00:00 2001 From: prajwal <140602407+Prajwal17Tunerlabs@users.noreply.github.com> Date: Wed, 30 Apr 2025 18:17:27 +0530 Subject: [PATCH 13/91] Delete config.json --- config.json | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 config.json diff --git a/config.json b/config.json deleted file mode 100644 index 2faeebc..0000000 --- a/config.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "authTokenUserInformation": { - "userId": "data.id", - "userName": "data.name", - "firstName": "data.name", - "roles": "data.roles", - "organizationId": "data.organization_id", - "tenantId": "data.tenant_id" - } -} From 8987c7da7eae0c683a7f14f73035242df2e384dd Mon Sep 17 00:00:00 2001 From: borkarsaish65 Date: Thu, 1 May 2025 15:07:18 +0530 Subject: [PATCH 14/91] savepoint --- src/generics/constants/api-responses.js | 2 ++ src/generics/middleware/authenticator.js | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/src/generics/constants/api-responses.js b/src/generics/constants/api-responses.js index 644822a..cf3d5ab 100644 --- a/src/generics/constants/api-responses.js +++ b/src/generics/constants/api-responses.js @@ -63,4 +63,6 @@ module.exports = { KEYS_INDEXED_SUCCESSFULL: 'KEYS_INDEXED_SUCCESSFULL', KEYS_ALREADY_INDEXED_SUCCESSFULL: 'KEYS_ALREADY_INDEXED_SUCCESSFULL', NOT_VALID_ID_AND_EXTERNALID: 'NOT_VALID_ID_AND_EXTERNALID', + TENANT_ID_MISSING_CODE: 'ERR_TENANT_ID_MISSING', + TENANT_ID_MISSING_MESSAGE: 'Require tenantId in body', } diff --git a/src/generics/middleware/authenticator.js b/src/generics/middleware/authenticator.js index 71f427b..416ce97 100644 --- a/src/generics/middleware/authenticator.js +++ b/src/generics/middleware/authenticator.js @@ -57,6 +57,13 @@ module.exports = async function (req, res, next, token = '') { }) ) + if (guestAccess == true && !req.body['tenantId']) { + rspObj.errCode = CONSTANTS.apiResponses.TENANT_ID_MISSING_CODE + rspObj.errMsg = CONSTANTS.apiResponses.TENANT_ID_MISSING_MESSAGE + rspObj.responseCode = HTTP_STATUS_CODE['unauthorized'].status + return res.status(HTTP_STATUS_CODE['unauthorized'].status).send(respUtil(rspObj)) + } + if (guestAccess == true && !token) { next() return From ac311beddce4c78519ff03cdf8f17c268fb13741 Mon Sep 17 00:00:00 2001 From: borkarsaish65 Date: Thu, 1 May 2025 15:09:04 +0530 Subject: [PATCH 15/91] savepoint --- src/generics/middleware/authenticator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/generics/middleware/authenticator.js b/src/generics/middleware/authenticator.js index 5593936..2e21c5f 100644 --- a/src/generics/middleware/authenticator.js +++ b/src/generics/middleware/authenticator.js @@ -289,7 +289,7 @@ module.exports = async function (req, res, next, token = '') { !orgDetails.data || !(Object.keys(orgDetails.data).length > 0) || !orgDetails.data.related_orgs || - !(orgDetails.data.related_orgs > 0) + !(orgDetails.data.related_orgs.length > 0) ) { let errorObj = {} errorObj.errCode = CONSTANTS.apiResponses.ORG_DETAILS_FETCH_UNSUCCESSFUL_CODE From 7a2bc607bdd6950ce1bc303197d5d5dc91717659 Mon Sep 17 00:00:00 2001 From: prajwal Date: Tue, 6 May 2025 18:49:23 +0530 Subject: [PATCH 16/91] middleware changes --- src/generics/middleware/authenticator.js | 79 +++++++++++++++++++++--- 1 file changed, 72 insertions(+), 7 deletions(-) diff --git a/src/generics/middleware/authenticator.js b/src/generics/middleware/authenticator.js index 2e21c5f..d867348 100644 --- a/src/generics/middleware/authenticator.js +++ b/src/generics/middleware/authenticator.js @@ -222,17 +222,27 @@ module.exports = async function (req, res, next, token = '') { } // performing default token data extraction if (defaultTokenExtraction) { - if (!decodedToken.data.organization_id) { - const orgId = req.get(process.env.ORG_ID_HEADER_NAME) - if (orgId && orgId != '') { - decodedToken.data.organization_id = orgId.toString() - } else decodedToken.data.organization_id = null + if (!decodedToken.data.organization_ids || !decodedToken.data.tenant_id) { + rspObj.errCode = CONSTANTS.apiResponses.TENANTID_AND_ORGID_REQUIRED_IN_TOKEN_CODE + rspObj.errMsg = CONSTANTS.apiResponses.TENANTID_AND_ORGID_REQUIRED_IN_TOKEN_MESSAGE + rspObj.responseCode = HTTP_STATUS_CODE['bad_request'].status + return res.status(HTTP_STATUS_CODE['bad_request'].status).send(respUtil(rspObj)) + } + //here assuming that req.headers['orgid'] will be a single value if multiple passed first element of the array will be taken + let fetchSingleOrgIdFunc = await fetchSingleOrgIdFromProvidedData( + decodedToken.data.tenant_id.toString(), + decodedToken.data.organization_ids, + req.headers['orgid'] + ) + + if (!fetchSingleOrgIdFunc.success) { + return res.status(HTTP_STATUS_CODE['unauthorized'].status).send(respUtil(fetchSingleOrgIdFunc.errorObj)) } userInformation = { userId: typeof decodedToken.data.id == 'string' ? decodedToken.data.id : decodedToken.data.id.toString(), userName: decodedToken.data.name, - organizationId: decodedToken.data.organization_id ? decodedToken.data.organization_id.toString() : null, + organizationId: fetchSingleOrgIdFunc.orgId, firstName: decodedToken.data.name, roles: decodedToken.data.roles.map((role) => role.title), tenantId: decodedToken.data.tenant_id.toString(), @@ -276,7 +286,7 @@ module.exports = async function (req, res, next, token = '') { * @returns {Object} - Success with validOrgIds array or failure with error object */ async function validateIfOrgsBelongsToTenant(tenantId, orgId) { - let orgIdArr = orgId?.split(',') || [] + let orgIdArr = Array.isArray(orgId) ? orgId : typeof orgId === 'string' ? orgId.split(',') : [] let orgDetails = await userService.fetchOrgDetails(tenantId) let validOrgIds = null @@ -316,6 +326,61 @@ module.exports = async function (req, res, next, token = '') { return { success: true, validOrgIds: validOrgIds } } + + /** + * Fetches a valid orgId from the provided data, checking if it's valid for the given tenant. + * + * @param {String} tenantId - ID of the tenant + * @param {String[]} orgIdArr - Array of orgIds to choose from + * @param {String} orgIdFromHeader - The orgId provided in the request headers + * @returns {Promise} - Returns a promise resolving to an object containing the success status, orgId, or error details + */ + async function fetchSingleOrgIdFromProvidedData(tenantId, orgIdArr, orgIdFromHeader) { + try { + // Check if orgIdFromHeader is provided and valid + if (orgIdFromHeader && orgIdFromHeader != '') { + if (!orgIdArr.includes(orgIdFromHeader)) { + rspObj.errCode = CONSTANTS.apiResponses.TENANTID_AND_ORGID_REQUIRED_IN_TOKEN_CODE + rspObj.errMsg = CONSTANTS.apiResponses.TENANTID_AND_ORGID_REQUIRED_IN_TOKEN_MESSAGE + rspObj.responseCode = HTTP_STATUS_CODE['bad_request'].status + return res.status(HTTP_STATUS_CODE['bad_request'].status).send(respUtil(rspObj)) + } + + let validateOrgsResult = await validateIfOrgsBelongsToTenant(tenantId, orgIdFromHeader) + + if (!validateOrgsResult.success) { + rspObj.errCode = CONSTANTS.apiResponses.TENANTID_AND_ORGID_REQUIRED_IN_TOKEN_CODE + rspObj.errMsg = CONSTANTS.apiResponses.TENANTID_AND_ORGID_REQUIRED_IN_TOKEN_MESSAGE + rspObj.responseCode = HTTP_STATUS_CODE['bad_request'].status + return res.status(HTTP_STATUS_CODE['bad_request'].status).send(respUtil(rspObj)) + } + + return { success: true, orgId: orgIdFromHeader } + } + + // If orgIdFromHeader is not provided, check orgIdArr + if (orgIdArr.length > 0) { + return { success: true, orgId: orgIdArr[0] } + } + + // If no orgId is found, throw error + rspObj.errCode = CONSTANTS.apiResponses.TENANTID_AND_ORGID_REQUIRED_IN_TOKEN_CODE + rspObj.errMsg = CONSTANTS.apiResponses.TENANTID_AND_ORGID_REQUIRED_IN_TOKEN_MESSAGE + rspObj.responseCode = HTTP_STATUS_CODE['bad_request'].status + return res.status(HTTP_STATUS_CODE['bad_request'].status).send(respUtil(rspObj)) + } catch (err) { + // Handle error when no valid orgId is found + if (orgIdArr.length > 0) { + return { success: true, orgId: orgIdArr[0] } + } + + rspObj.errCode = CONSTANTS.apiResponses.TENANTID_AND_ORGID_REQUIRED_IN_TOKEN_CODE + rspObj.errMsg = CONSTANTS.apiResponses.TENANTID_AND_ORGID_REQUIRED_IN_TOKEN_MESSAGE + rspObj.responseCode = HTTP_STATUS_CODE['bad_request'].status + return res.status(HTTP_STATUS_CODE['bad_request'].status).send(respUtil(rspObj)) + } + } + /** * Extract tenantId and orgId from incoming request or decoded token. * From 8d797dd106bf8349c2fc44c78f4f4f3ab3d8a7eb Mon Sep 17 00:00:00 2001 From: vishnu Date: Wed, 7 May 2025 14:27:30 +0530 Subject: [PATCH 17/91] scp code commented temp --- src/module/entities/helper.js | 108 +++++++++++++++++----------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/src/module/entities/helper.js b/src/module/entities/helper.js index e93a223..504c76a 100644 --- a/src/module/entities/helper.js +++ b/src/module/entities/helper.js @@ -252,60 +252,60 @@ module.exports = class UserProjectsHelper { } // Modify data properties (e.g., 'label') of retrieved entities if necessary - if (result.data && result.data.length > 0) { - // fetch the entity ids to look for parent hierarchy - const entityIds = _.map(result.data, (item) => ObjectId(item._id)) - // dynamically set the entityType to search inside the group - const key = ['groups', type] - // create filter for fetching the parent data using group - let entityFilter = {} - entityFilter[key.join('.')] = { - $in: entityIds, - } - - entityFilter['tenantId'] = tenantId - - // Retrieve all the entity documents with the entity ids in their gropu - const entityDocuments = await entitiesQueries.entityDocuments(entityFilter, [ - 'entityType', - 'metaInformation.name', - 'childHierarchyPath', - key.join('.'), - ]) - // find out the state of the passed entityId - const stateEntity = entityDocuments.find((entity) => entity.entityType == 'state') - // fetch the child hierarchy path of the state - const stateChildHierarchy = stateEntity.childHierarchyPath - let upperLevelsOfType = type != 'state' ? ['state'] : [] // add state as default if type != state - // fetch all the upper levels of the type from state hierarchy - upperLevelsOfType = [ - ...upperLevelsOfType, - ...stateChildHierarchy.slice(0, stateChildHierarchy.indexOf(type)), - ] - result.data = result.data.map((data) => { - let cloneData = { ...data } - cloneData[cloneData.entityType] = cloneData.name - // if we have upper levels to fetch - if (upperLevelsOfType.length > 0) { - // iterate through the data fetched to fetch the parent entity names - entityDocuments.forEach((eachEntity) => { - eachEntity[key[0]][key[1]].forEach((eachEntityGroup) => { - if ( - ObjectId(eachEntityGroup).equals(cloneData._id) && - upperLevelsOfType.includes(eachEntity.entityType) - ) { - if (eachEntity?.entityType !== 'state') { - cloneData[eachEntity?.entityType] = eachEntity?.metaInformation?.name - } - } - }) - }) - } - cloneData['label'] = cloneData.name - cloneData['value'] = cloneData._id - return cloneData - }) - } + // if (result.data && result.data.length > 0) { + // // fetch the entity ids to look for parent hierarchy + // const entityIds = _.map(result.data, (item) => ObjectId(item._id)) + // // dynamically set the entityType to search inside the group + // const key = ['groups', type] + // // create filter for fetching the parent data using group + // let entityFilter = {} + // entityFilter[key.join('.')] = { + // $in: entityIds, + // } + + // entityFilter['tenantId'] = tenantId + + // // Retrieve all the entity documents with the entity ids in their gropu + // const entityDocuments = await entitiesQueries.entityDocuments(entityFilter, [ + // 'entityType', + // 'metaInformation.name', + // 'childHierarchyPath', + // key.join('.'), + // ]) + // // find out the state of the passed entityId + // const stateEntity = entityDocuments.find((entity) => entity.entityType == 'state') + // // fetch the child hierarchy path of the state + // const stateChildHierarchy = stateEntity.childHierarchyPath + // let upperLevelsOfType = type != 'state' ? ['state'] : [] // add state as default if type != state + // // fetch all the upper levels of the type from state hierarchy + // upperLevelsOfType = [ + // ...upperLevelsOfType, + // ...stateChildHierarchy.slice(0, stateChildHierarchy.indexOf(type)), + // ] + // result.data = result.data.map((data) => { + // let cloneData = { ...data } + // cloneData[cloneData.entityType] = cloneData.name + // // if we have upper levels to fetch + // if (upperLevelsOfType.length > 0) { + // // iterate through the data fetched to fetch the parent entity names + // entityDocuments.forEach((eachEntity) => { + // eachEntity[key[0]][key[1]].forEach((eachEntityGroup) => { + // if ( + // ObjectId(eachEntityGroup).equals(cloneData._id) && + // upperLevelsOfType.includes(eachEntity.entityType) + // ) { + // if (eachEntity?.entityType !== 'state') { + // cloneData[eachEntity?.entityType] = eachEntity?.metaInformation?.name + // } + // } + // }) + // }) + // } + // cloneData['label'] = cloneData.name + // cloneData['value'] = cloneData._id + // return cloneData + // }) + // } resolve({ message: CONSTANTS.apiResponses.ENTITIES_FETCHED, From 127dbd483c99f683bfc0718796466d1bf510f2c4 Mon Sep 17 00:00:00 2001 From: borkarsaish65 Date: Wed, 7 May 2025 18:01:27 +0530 Subject: [PATCH 18/91] savepoint --- src/generics/middleware/authenticator.js | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/generics/middleware/authenticator.js b/src/generics/middleware/authenticator.js index b00c9fc..998e49f 100644 --- a/src/generics/middleware/authenticator.js +++ b/src/generics/middleware/authenticator.js @@ -347,19 +347,13 @@ module.exports = async function (req, res, next, token = '') { // Check if orgIdFromHeader is provided and valid if (orgIdFromHeader && orgIdFromHeader != '') { if (!orgIdArr.includes(orgIdFromHeader)) { - rspObj.errCode = CONSTANTS.apiResponses.TENANTID_AND_ORGID_REQUIRED_IN_TOKEN_CODE - rspObj.errMsg = CONSTANTS.apiResponses.TENANTID_AND_ORGID_REQUIRED_IN_TOKEN_MESSAGE - rspObj.responseCode = HTTP_STATUS_CODE['bad_request'].status - return res.status(HTTP_STATUS_CODE['bad_request'].status).send(respUtil(rspObj)) + throw CONSTANTS.apiResponses.TENANTID_AND_ORGID_REQUIRED_IN_TOKEN_CODE } let validateOrgsResult = await validateIfOrgsBelongsToTenant(tenantId, orgIdFromHeader) if (!validateOrgsResult.success) { - rspObj.errCode = CONSTANTS.apiResponses.TENANTID_AND_ORGID_REQUIRED_IN_TOKEN_CODE - rspObj.errMsg = CONSTANTS.apiResponses.TENANTID_AND_ORGID_REQUIRED_IN_TOKEN_MESSAGE - rspObj.responseCode = HTTP_STATUS_CODE['bad_request'].status - return res.status(HTTP_STATUS_CODE['bad_request'].status).send(respUtil(rspObj)) + throw CONSTANTS.apiResponses.TENANTID_AND_ORGID_REQUIRED_IN_TOKEN_CODE } return { success: true, orgId: orgIdFromHeader } @@ -371,10 +365,7 @@ module.exports = async function (req, res, next, token = '') { } // If no orgId is found, throw error - rspObj.errCode = CONSTANTS.apiResponses.TENANTID_AND_ORGID_REQUIRED_IN_TOKEN_CODE - rspObj.errMsg = CONSTANTS.apiResponses.TENANTID_AND_ORGID_REQUIRED_IN_TOKEN_MESSAGE - rspObj.responseCode = HTTP_STATUS_CODE['bad_request'].status - return res.status(HTTP_STATUS_CODE['bad_request'].status).send(respUtil(rspObj)) + throw CONSTANTS.apiResponses.TENANTID_AND_ORGID_REQUIRED_IN_TOKEN_CODE } catch (err) { // Handle error when no valid orgId is found if (orgIdArr.length > 0) { From c598c59f71927693fcdac2d611e23706c1bcc96d Mon Sep 17 00:00:00 2001 From: vishnu Date: Wed, 7 May 2025 18:03:12 +0530 Subject: [PATCH 19/91] middleware changes for guest urls --- src/generics/constants/common.js | 2 +- src/generics/middleware/authenticator.js | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/generics/constants/common.js b/src/generics/constants/common.js index cbdbd04..e9dea6a 100644 --- a/src/generics/constants/common.js +++ b/src/generics/constants/common.js @@ -44,5 +44,5 @@ module.exports = { ORG_ADMIN: 'org_admin', TENANT_ADMIN: 'tenant_admin', SERVER_TIME_OUT: 5000, - GUEST_URLS: ['/entities/details'], + GUEST_URLS: ['/entities/details', '/entities/entityListBasedOnEntityType', 'entities/subEntityList'], } diff --git a/src/generics/middleware/authenticator.js b/src/generics/middleware/authenticator.js index d867348..3fa636c 100644 --- a/src/generics/middleware/authenticator.js +++ b/src/generics/middleware/authenticator.js @@ -59,6 +59,19 @@ module.exports = async function (req, res, next, token = '') { ) if (guestAccess == true && !token) { + if (!req.headers['tenantid']) { + rspObj.errCode = CONSTANTS.apiResponses.TOKEN_MISSING_CODE + rspObj.errMsg = CONSTANTS.apiResponses.TOKEN_MISSING_MESSAGE + rspObj.responseCode = HTTP_STATUS_CODE['unauthorized'].status + return res.status(HTTP_STATUS_CODE['unauthorized'].status).send(respUtil(rspObj)) + } + req.userDetails = { + userInformation: { + tenantId: req.headers['tenantid'], + organizationId: req.headers['orgid'] || null, + }, + } + next() return } From fcfa4462d253ad4ed1fb1bb7f2d1dbee692fbae7 Mon Sep 17 00:00:00 2001 From: vishnu Date: Wed, 7 May 2025 18:37:26 +0530 Subject: [PATCH 20/91] authenticator change --- src/generics/middleware/authenticator.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/generics/middleware/authenticator.js b/src/generics/middleware/authenticator.js index 6e6bf26..3cecced 100644 --- a/src/generics/middleware/authenticator.js +++ b/src/generics/middleware/authenticator.js @@ -58,17 +58,17 @@ module.exports = async function (req, res, next, token = '') { }) ) - if (guestAccess == true && !req.body['tenantId']) { - rspObj.errCode = CONSTANTS.apiResponses.TENANT_ID_MISSING_CODE - rspObj.errMsg = CONSTANTS.apiResponses.TENANT_ID_MISSING_MESSAGE - rspObj.responseCode = HTTP_STATUS_CODE['unauthorized'].status - return res.status(HTTP_STATUS_CODE['unauthorized'].status).send(respUtil(rspObj)) - } + // if (guestAccess == true && !req.body['tenantId']) { + // rspObj.errCode = CONSTANTS.apiResponses.TENANT_ID_MISSING_CODE + // rspObj.errMsg = CONSTANTS.apiResponses.TENANT_ID_MISSING_MESSAGE + // rspObj.responseCode = HTTP_STATUS_CODE['unauthorized'].status + // return res.status(HTTP_STATUS_CODE['unauthorized'].status).send(respUtil(rspObj)) + // } if (guestAccess == true && !token) { if (!req.headers['tenantid']) { - rspObj.errCode = CONSTANTS.apiResponses.TOKEN_MISSING_CODE - rspObj.errMsg = CONSTANTS.apiResponses.TOKEN_MISSING_MESSAGE + rspObj.errCode = CONSTANTS.apiResponses.TENANT_ID_MISSING_CODE + rspObj.errMsg = CONSTANTS.apiResponses.TENANT_ID_MISSING_MESSAGE rspObj.responseCode = HTTP_STATUS_CODE['unauthorized'].status return res.status(HTTP_STATUS_CODE['unauthorized'].status).send(respUtil(rspObj)) } From b74cf4b1d8b25deca0ef16dd1017840586a0f840 Mon Sep 17 00:00:00 2001 From: vishnu Date: Wed, 7 May 2025 18:37:57 +0530 Subject: [PATCH 21/91] authenticator change --- src/generics/middleware/authenticator.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/generics/middleware/authenticator.js b/src/generics/middleware/authenticator.js index 3cecced..c0f7495 100644 --- a/src/generics/middleware/authenticator.js +++ b/src/generics/middleware/authenticator.js @@ -66,7 +66,7 @@ module.exports = async function (req, res, next, token = '') { // } if (guestAccess == true && !token) { - if (!req.headers['tenantid']) { + if (!req.headers['tenantId']) { rspObj.errCode = CONSTANTS.apiResponses.TENANT_ID_MISSING_CODE rspObj.errMsg = CONSTANTS.apiResponses.TENANT_ID_MISSING_MESSAGE rspObj.responseCode = HTTP_STATUS_CODE['unauthorized'].status @@ -74,8 +74,8 @@ module.exports = async function (req, res, next, token = '') { } req.userDetails = { userInformation: { - tenantId: req.headers['tenantid'], - organizationId: req.headers['orgid'] || null, + tenantId: req.headers['tenantId'], + organizationId: req.headers['orgId'] || null, }, } From 20f7ec2bcd01728e3b12ee12caabb7e1e52e74b1 Mon Sep 17 00:00:00 2001 From: vishnu Date: Wed, 7 May 2025 18:42:34 +0530 Subject: [PATCH 22/91] authenticator change --- src/generics/constants/api-responses.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/generics/constants/api-responses.js b/src/generics/constants/api-responses.js index 2c89731..a84fea1 100644 --- a/src/generics/constants/api-responses.js +++ b/src/generics/constants/api-responses.js @@ -73,5 +73,5 @@ module.exports = { KEYS_ALREADY_INDEXED_SUCCESSFULL: 'KEYS_ALREADY_INDEXED_SUCCESSFULL', NOT_VALID_ID_AND_EXTERNALID: 'NOT_VALID_ID_AND_EXTERNALID', TENANT_ID_MISSING_CODE: 'ERR_TENANT_ID_MISSING', - TENANT_ID_MISSING_MESSAGE: 'Require tenantId in body', + TENANT_ID_MISSING_MESSAGE: 'Require tenantId in header', } From ad50ca66cefccfeb7eb012f2539335c6bce6fb53 Mon Sep 17 00:00:00 2001 From: vishnu Date: Wed, 7 May 2025 18:44:49 +0530 Subject: [PATCH 23/91] authenticator change --- src/generics/middleware/authenticator.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/generics/middleware/authenticator.js b/src/generics/middleware/authenticator.js index c0f7495..3cecced 100644 --- a/src/generics/middleware/authenticator.js +++ b/src/generics/middleware/authenticator.js @@ -66,7 +66,7 @@ module.exports = async function (req, res, next, token = '') { // } if (guestAccess == true && !token) { - if (!req.headers['tenantId']) { + if (!req.headers['tenantid']) { rspObj.errCode = CONSTANTS.apiResponses.TENANT_ID_MISSING_CODE rspObj.errMsg = CONSTANTS.apiResponses.TENANT_ID_MISSING_MESSAGE rspObj.responseCode = HTTP_STATUS_CODE['unauthorized'].status @@ -74,8 +74,8 @@ module.exports = async function (req, res, next, token = '') { } req.userDetails = { userInformation: { - tenantId: req.headers['tenantId'], - organizationId: req.headers['orgId'] || null, + tenantId: req.headers['tenantid'], + organizationId: req.headers['orgid'] || null, }, } From e7d5330a156ac21848a0b63a90b7e16dd8762b03 Mon Sep 17 00:00:00 2001 From: prajwal Date: Tue, 13 May 2025 11:26:52 +0530 Subject: [PATCH 24/91] new user-token structure changs --- src/generics/middleware/authenticator.js | 81 ++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 7 deletions(-) diff --git a/src/generics/middleware/authenticator.js b/src/generics/middleware/authenticator.js index d867348..7152a3a 100644 --- a/src/generics/middleware/authenticator.js +++ b/src/generics/middleware/authenticator.js @@ -90,7 +90,6 @@ module.exports = async function (req, res, next, token = '') { return } } - if (!token) { rspObj.errCode = CONSTANTS.apiResponses.TOKEN_MISSING_CODE rspObj.errMsg = CONSTANTS.apiResponses.TOKEN_MISSING_MESSAGE @@ -216,10 +215,13 @@ module.exports = async function (req, res, next, token = '') { defaultTokenExtraction = true } + let organizationKey = 'organization_id' + // Create user details to request req.userDetails = { userToken: token, } + // performing default token data extraction if (defaultTokenExtraction) { if (!decodedToken.data.organization_ids || !decodedToken.data.tenant_id) { @@ -248,16 +250,34 @@ module.exports = async function (req, res, next, token = '') { tenantId: decodedToken.data.tenant_id.toString(), } } else { - // Iterate through each key in the config object for (let key in configData) { - let stringTypeKeys = ['userId', 'tenantId', 'organizationId'] if (configData.hasOwnProperty(key)) { let keyValue = getNestedValue(decodedToken, configData[key]) - if (stringTypeKeys.includes(key)) { - keyValue = keyValue.toString() + if (key === organizationKey) { + let value = getOrgId(req.headers, decodedToken, configData[key]) + userInformation[`organizationId`] = value.toString() + decodedToken.data[key] = value + continue } + if (key === 'roles') { + let orgId = getOrgId(req.headers, decodedToken, configData[organizationKey]) + // Now extract roles using fully dynamic path + const rolePathTemplate = configData['roles'] + decodedToken.data[organizationKey] = orgId + const resolvedRolePath = resolvePathTemplate(rolePathTemplate, decodedToken.data) + const roles = getNestedValue(decodedToken, resolvedRolePath) || [] + userInformation[`${key}`] = roles + decodedToken.data[key] = roles + continue + } + // For each key in config, assign the corresponding value from decodedToken - userInformation[`${key}`] = keyValue + decodedToken.data[key] = keyValue + if (key == 'tenant_id') { + userInformation[`tenantId`] = keyValue.toString() + } else { + userInformation[`${key}`] = keyValue.toString() + } } } if (userInformation.roles && Array.isArray(userInformation.roles) && userInformation.roles.length) { @@ -497,9 +517,56 @@ module.exports = async function (req, res, next, token = '') { if (decodedToken.data.tenantAndOrgInfo) { req.userDetails.tenantAndOrgInfo = decodedToken.data.tenantAndOrgInfo } + // Helper function to access nested properties + function getOrgId(headers, decodedToken, orgConfigData) { + if (headers['organization_id']) { + return (orgId = headers['organization_id'].toString()) + } else { + const orgIdPath = orgConfigData + return (orgId = getNestedValue(decodedToken, orgIdPath)?.toString()) + } + } + function getNestedValue(obj, path) { - return path.split('.').reduce((acc, part) => acc && acc[part], obj) + const parts = path.split('.') + let current = obj + + for (const part of parts) { + if (!current) return undefined + + // Conditional match: key[?field=value] + const conditionalMatch = part.match(/^(\w+)\[\?(\w+)=([^\]]+)\]$/) + if (conditionalMatch) { + const [, arrayKey, field, expected] = conditionalMatch + const array = current[arrayKey] + if (!Array.isArray(array)) return undefined + const found = array.find((item) => String(item[field]) === String(expected)) + if (!found) return undefined + current = found + continue + } + + // Index match: key[0] + const indexMatch = part.match(/^(\w+)\[(\d+)\]$/) + if (indexMatch) { + const [, key, index] = indexMatch + const array = current[key] + if (!Array.isArray(array)) return undefined + current = array[parseInt(index, 10)] + continue + } + + current = current[part] + } + return current + } + + function resolvePathTemplate(template, contextObject) { + return template.replace(/\{\{(.*?)\}\}/g, (_, path) => { + const value = getNestedValue(contextObject, path.trim()) + return value?.toString?.() ?? '' + }) } next() } From ce38b7d9c0d3fed18df6443cba23ecd8738912a0 Mon Sep 17 00:00:00 2001 From: prajwal Date: Tue, 13 May 2025 12:53:00 +0530 Subject: [PATCH 25/91] authenticator fix --- src/generics/middleware/authenticator.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/generics/middleware/authenticator.js b/src/generics/middleware/authenticator.js index 7152a3a..2571d15 100644 --- a/src/generics/middleware/authenticator.js +++ b/src/generics/middleware/authenticator.js @@ -253,6 +253,9 @@ module.exports = async function (req, res, next, token = '') { for (let key in configData) { if (configData.hasOwnProperty(key)) { let keyValue = getNestedValue(decodedToken, configData[key]) + if (key == 'userId') { + keyValue = keyValue?.toString() + } if (key === organizationKey) { let value = getOrgId(req.headers, decodedToken, configData[key]) userInformation[`organizationId`] = value.toString() @@ -276,7 +279,7 @@ module.exports = async function (req, res, next, token = '') { if (key == 'tenant_id') { userInformation[`tenantId`] = keyValue.toString() } else { - userInformation[`${key}`] = keyValue.toString() + userInformation[`${key}`] = keyValue } } } From d2080fae7040df0dcfb36edb1e4c3bda13f4d2cb Mon Sep 17 00:00:00 2001 From: prajwal Date: Tue, 13 May 2025 18:36:58 +0530 Subject: [PATCH 26/91] config.json file path updated in middleware --- src/generics/middleware/authenticator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/generics/middleware/authenticator.js b/src/generics/middleware/authenticator.js index 2571d15..bf6843d 100644 --- a/src/generics/middleware/authenticator.js +++ b/src/generics/middleware/authenticator.js @@ -192,7 +192,7 @@ module.exports = async function (req, res, next, token = '') { } // Path to config.json - const configFilePath = path.resolve(__dirname, '../../../config.json') + const configFilePath = path.resolve(__dirname, '../../config.json') // Initialize variables let configData = {} let defaultTokenExtraction = false From a2760aa0a79b22b6ac6fc2f04063e8d28a4b4f2e Mon Sep 17 00:00:00 2001 From: MallanagoudaB Date: Wed, 14 May 2025 13:26:07 +0530 Subject: [PATCH 27/91] "tenantid-changed-to-tenantId" --- src/generics/middleware/authenticator.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/generics/middleware/authenticator.js b/src/generics/middleware/authenticator.js index 3e2c7a2..8ae23df 100644 --- a/src/generics/middleware/authenticator.js +++ b/src/generics/middleware/authenticator.js @@ -74,8 +74,8 @@ module.exports = async function (req, res, next, token = '') { } req.userDetails = { userInformation: { - tenantId: req.headers['tenantid'], - organizationId: req.headers['orgid'] || null, + tenantId: req.headers['tenantId'], + organizationId: req.headers['orgId'] || null, }, } From e92453da70ce7217c53705c4ba7a57adfe9974dc Mon Sep 17 00:00:00 2001 From: MallanagoudaB Date: Wed, 14 May 2025 13:43:48 +0530 Subject: [PATCH 28/91] "added-ALL" --- src/generics/middleware/authenticator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/generics/middleware/authenticator.js b/src/generics/middleware/authenticator.js index 8ae23df..2deda06 100644 --- a/src/generics/middleware/authenticator.js +++ b/src/generics/middleware/authenticator.js @@ -75,7 +75,7 @@ module.exports = async function (req, res, next, token = '') { req.userDetails = { userInformation: { tenantId: req.headers['tenantId'], - organizationId: req.headers['orgId'] || null, + organizationId: req.headers['orgId'] || 'ALL', }, } From 2d525e9a3c74fb4730bc39e0b4ab9ccbe1063e46 Mon Sep 17 00:00:00 2001 From: MallanagoudaB Date: Wed, 14 May 2025 15:13:22 +0530 Subject: [PATCH 29/91] "tenantId-added-in-header" --- src/generics/middleware/authenticator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/generics/middleware/authenticator.js b/src/generics/middleware/authenticator.js index 2deda06..54bcc61 100644 --- a/src/generics/middleware/authenticator.js +++ b/src/generics/middleware/authenticator.js @@ -74,7 +74,7 @@ module.exports = async function (req, res, next, token = '') { } req.userDetails = { userInformation: { - tenantId: req.headers['tenantId'], + tenantId: req.headers.tenantid, organizationId: req.headers['orgId'] || 'ALL', }, } From 2df158e2ed23586a764e1f0d878ad24e4bcdf1f1 Mon Sep 17 00:00:00 2001 From: MallanagoudaB Date: Thu, 15 May 2025 15:45:31 +0530 Subject: [PATCH 30/91] "added-ALL-for-ORG" --- src/module/entities/helper.js | 4 ++-- src/module/entityTypes/helper.js | 4 ++-- src/module/userRoleExtension/helper.js | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/module/entities/helper.js b/src/module/entities/helper.js index 504c76a..f1cdfcd 100644 --- a/src/module/entities/helper.js +++ b/src/module/entities/helper.js @@ -1222,7 +1222,7 @@ module.exports = class UserProjectsHelper { // Find the entities document based on the entityType in queryParams let tenantId = userDetails.tenantAndOrgInfo.tenantId - let orgId = userDetails.tenantAndOrgInfo.orgIds + let orgId = 'ALL' let entityTypeDocument = await entityTypeQueries.findOne( { name: queryParams.type, tenantId: tenantId }, { _id: 1 } @@ -1550,7 +1550,7 @@ module.exports = class UserProjectsHelper { // Find the entity type document based on the provided entityType let tenantId = userDetails.tenantAndOrgInfo.tenantId - let orgId = userDetails.tenantAndOrgInfo.orgIds + let orgId = 'ALL' let entityTypeDocument = await entityTypeQueries.findOne( { name: entityType, diff --git a/src/module/entityTypes/helper.js b/src/module/entityTypes/helper.js index 077d4aa..c6a5b71 100644 --- a/src/module/entityTypes/helper.js +++ b/src/module/entityTypes/helper.js @@ -33,7 +33,7 @@ module.exports = class UserProjectsHelper { try { entityType = UTILS.valueParser(entityType) entityType['tenantId'] = userDetails.tenantAndOrgInfo.tenantId - entityType['orgIds'] = userDetails.tenantAndOrgInfo.orgIds + entityType['orgIds'] = 'ALL' entityType.registryDetails = {} let removedKeys = [] @@ -142,7 +142,7 @@ module.exports = class UserProjectsHelper { try { let entityType = body entityType['tenantId'] = userDetails.tenantAndOrgInfo.tenantId - entityType['orgIds'] = userDetails.tenantAndOrgInfo.orgIds + entityType['orgIds'] = 'ALL' if (entityType.profileFields) { entityType.profileFields = entityType.profileFields.split(',') || [] diff --git a/src/module/userRoleExtension/helper.js b/src/module/userRoleExtension/helper.js index daf0fba..9e47c02 100644 --- a/src/module/userRoleExtension/helper.js +++ b/src/module/userRoleExtension/helper.js @@ -43,7 +43,7 @@ module.exports = class userRoleExtensionHelper { }) ) body['tenantId'] = userDetails.tenantAndOrgInfo.tenantId - body['orgIds'] = userDetails.tenantAndOrgInfo.orgIds + body['orgIds'] = 'ALL' // Call the queries function to create a new user role extension with the provided body data let newUserRole = await userRoleExtensionQueries.create(body) From 4abc25e20425a97ca588577fd2201b0bcf218fee Mon Sep 17 00:00:00 2001 From: MallanagoudaB Date: Thu, 15 May 2025 15:51:08 +0530 Subject: [PATCH 31/91] "added-to-common" --- src/generics/constants/common.js | 1 + src/module/entities/helper.js | 4 ++-- src/module/entityTypes/helper.js | 4 ++-- src/module/userRoleExtension/helper.js | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/generics/constants/common.js b/src/generics/constants/common.js index e9dea6a..5011c80 100644 --- a/src/generics/constants/common.js +++ b/src/generics/constants/common.js @@ -45,4 +45,5 @@ module.exports = { TENANT_ADMIN: 'tenant_admin', SERVER_TIME_OUT: 5000, GUEST_URLS: ['/entities/details', '/entities/entityListBasedOnEntityType', 'entities/subEntityList'], + ALL: 'ALL', } diff --git a/src/module/entities/helper.js b/src/module/entities/helper.js index f1cdfcd..283a2e6 100644 --- a/src/module/entities/helper.js +++ b/src/module/entities/helper.js @@ -1222,7 +1222,7 @@ module.exports = class UserProjectsHelper { // Find the entities document based on the entityType in queryParams let tenantId = userDetails.tenantAndOrgInfo.tenantId - let orgId = 'ALL' + let orgId = CONSTANTS.common.ALL let entityTypeDocument = await entityTypeQueries.findOne( { name: queryParams.type, tenantId: tenantId }, { _id: 1 } @@ -1550,7 +1550,7 @@ module.exports = class UserProjectsHelper { // Find the entity type document based on the provided entityType let tenantId = userDetails.tenantAndOrgInfo.tenantId - let orgId = 'ALL' + let orgId = CONSTANTS.common.ALL let entityTypeDocument = await entityTypeQueries.findOne( { name: entityType, diff --git a/src/module/entityTypes/helper.js b/src/module/entityTypes/helper.js index c6a5b71..f4b9ef9 100644 --- a/src/module/entityTypes/helper.js +++ b/src/module/entityTypes/helper.js @@ -33,7 +33,7 @@ module.exports = class UserProjectsHelper { try { entityType = UTILS.valueParser(entityType) entityType['tenantId'] = userDetails.tenantAndOrgInfo.tenantId - entityType['orgIds'] = 'ALL' + entityType['orgIds'] = CONSTANTS.common.ALL entityType.registryDetails = {} let removedKeys = [] @@ -142,7 +142,7 @@ module.exports = class UserProjectsHelper { try { let entityType = body entityType['tenantId'] = userDetails.tenantAndOrgInfo.tenantId - entityType['orgIds'] = 'ALL' + entityType['orgIds'] = CONSTANTS.common.ALL if (entityType.profileFields) { entityType.profileFields = entityType.profileFields.split(',') || [] diff --git a/src/module/userRoleExtension/helper.js b/src/module/userRoleExtension/helper.js index 9e47c02..4aeedda 100644 --- a/src/module/userRoleExtension/helper.js +++ b/src/module/userRoleExtension/helper.js @@ -43,7 +43,7 @@ module.exports = class userRoleExtensionHelper { }) ) body['tenantId'] = userDetails.tenantAndOrgInfo.tenantId - body['orgIds'] = 'ALL' + body['orgIds'] = CONSTANTS.common.ALL // Call the queries function to create a new user role extension with the provided body data let newUserRole = await userRoleExtensionQueries.create(body) From a155d41c7ca7aa37c45ccac50295d876ef527a53 Mon Sep 17 00:00:00 2001 From: vishnu Date: Fri, 16 May 2025 07:55:12 +0530 Subject: [PATCH 32/91] create mapping csv fix --- src/controllers/v1/entities.js | 3 ++- src/module/entities/helper.js | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/controllers/v1/entities.js b/src/controllers/v1/entities.js index 170c976..5751f62 100644 --- a/src/controllers/v1/entities.js +++ b/src/controllers/v1/entities.js @@ -272,11 +272,12 @@ module.exports = class Entities extends Abstract { createMappingCsv(req) { return new Promise(async (resolve, reject) => { try { + let tenantId = req.userDetails.userInformation.tenantId // Parse CSV data from the uploaded file in the request body let entityCSVData = await csv().fromString(req.files.entityCSV.data.toString()) // Process the entity mapping upload data using 'entitiesHelper.createMappingCsv' - let mappedEntities = await entitiesHelper.createMappingCsv(entityCSVData) + let mappedEntities = await entitiesHelper.createMappingCsv(entityCSVData, tenantId) return resolve({ message: CONSTANTS.apiResponses.MAPPING_CSV_GENERATED, diff --git a/src/module/entities/helper.js b/src/module/entities/helper.js index 283a2e6..9950372 100644 --- a/src/module/entities/helper.js +++ b/src/module/entities/helper.js @@ -93,10 +93,11 @@ module.exports = class UserProjectsHelper { * @method * @name createMappingCsv * @param {Array} entityCSVData - Array of objects parsed from the input CSV file. + * @param {String} tenantId - Tenant ID for the user. * @returns {Promise} Resolves with an object containing: */ - static async createMappingCsv(entityCSVData) { + static async createMappingCsv(entityCSVData, tenantId) { return new Promise(async (resolve, reject) => { try { const parentEntityIds = [] @@ -113,6 +114,7 @@ module.exports = class UserProjectsHelper { // Filter criteria to fetch entity documents based on entity type and external ID const filter = { 'metaInformation.externalId': value, + tenantId: tenantId, } const entityDocuments = await entitiesQueries.entityDocuments(filter, ['_id']) From 446faa2d6e1616ec6c931a7237f8abd68e7a751e Mon Sep 17 00:00:00 2001 From: prajwal Date: Tue, 20 May 2025 19:13:55 +0530 Subject: [PATCH 33/91] pagination handled for entities/find api --- src/controllers/v1/entities.js | 7 ++++++- src/module/entities/helper.js | 11 +++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/controllers/v1/entities.js b/src/controllers/v1/entities.js index 5751f62..fe4bda7 100644 --- a/src/controllers/v1/entities.js +++ b/src/controllers/v1/entities.js @@ -71,7 +71,12 @@ module.exports = class Entities extends Abstract { return new Promise(async (resolve, reject) => { try { // Calls the 'find' function from 'entitiesHelper' to retrieve entity data - let entityData = await entitiesHelper.find(req.body.query, req.body.projection) + let entityData = await entitiesHelper.find( + req.body.query, + req.body.projection, + req.pageNo, + req.pageSize + ) return resolve(entityData) } catch (error) { return reject({ diff --git a/src/module/entities/helper.js b/src/module/entities/helper.js index 9950372..99a4fd2 100644 --- a/src/module/entities/helper.js +++ b/src/module/entities/helper.js @@ -1099,13 +1099,20 @@ module.exports = class UserProjectsHelper { * @name find * @param {Object} bodyQuery - body data * @param {Object} projection - projection to filter data + * @param {Number} pageNo - page number + * @param {Number} pageSize - page limit */ - static find(bodyQuery, projection) { + static find(bodyQuery, projection, pageNo, pageSize) { return new Promise(async (resolve, reject) => { try { // Fetch entities based on the provided query and projection - const result = await entitiesQueries.entityDocuments(bodyQuery, projection) + let result = await entitiesQueries.entityDocuments(bodyQuery, projection) + if (result.length > 0) { + let startIndex = pageSize * (pageNo - 1) + let endIndex = startIndex + pageSize + result = result.slice(startIndex, endIndex) + } if (result.length < 1) { throw { status: HTTP_STATUS_CODE.not_found.status, From d2744322168b379f0a5869ae553e702f8ea24f3f Mon Sep 17 00:00:00 2001 From: prajwal Date: Tue, 20 May 2025 19:40:04 +0530 Subject: [PATCH 34/91] pagination handled via aggregate function --- src/module/entities/helper.js | 38 ++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/src/module/entities/helper.js b/src/module/entities/helper.js index 99a4fd2..9871d1e 100644 --- a/src/module/entities/helper.js +++ b/src/module/entities/helper.js @@ -1106,14 +1106,34 @@ module.exports = class UserProjectsHelper { static find(bodyQuery, projection, pageNo, pageSize) { return new Promise(async (resolve, reject) => { try { - // Fetch entities based on the provided query and projection - let result = await entitiesQueries.entityDocuments(bodyQuery, projection) - if (result.length > 0) { - let startIndex = pageSize * (pageNo - 1) - let endIndex = startIndex + pageSize - result = result.slice(startIndex, endIndex) - } - if (result.length < 1) { + // Create facet object to attain pagination + let facetQuery = {} + facetQuery['$facet'] = {} + facetQuery['$facet']['totalCount'] = [{ $count: 'count' }] + if (pageSize === '' && pageNo === '') { + facetQuery['$facet']['data'] = [{ $skip: 0 }] + } else { + facetQuery['$facet']['data'] = [{ $skip: pageSize * (pageNo - 1) }, { $limit: pageSize }] + } + bodyQuery, projection, facetQuery + + // Create projection object + let projection1 = {} + if (projection.length > 0) { + projection.forEach((projectedData) => { + projection1[projectedData] = 1 + }) + } + + const result = await entitiesQueries.getAggregate([ + { $match: bodyQuery }, + { + $sort: { updatedAt: -1 }, + }, + { $project: projection1 }, + facetQuery, + ]) + if (!(result.length > 0) || !result[0].data || !(result[0].data.length > 0)) { throw { status: HTTP_STATUS_CODE.not_found.status, message: CONSTANTS.apiResponses.ENTITY_NOT_FOUND, @@ -1122,7 +1142,7 @@ module.exports = class UserProjectsHelper { return resolve({ success: true, message: CONSTANTS.apiResponses.ASSETS_FETCHED_SUCCESSFULLY, - result: result, + result: result[0].data, }) } catch (error) { return reject(error) From c2bc4a289097db1b8597815d35955fc7c9d37843 Mon Sep 17 00:00:00 2001 From: prajwal Date: Tue, 20 May 2025 20:16:20 +0530 Subject: [PATCH 35/91] helper function added to convert mongoids to object ids --- src/module/entities/helper.js | 38 +++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/src/module/entities/helper.js b/src/module/entities/helper.js index 9871d1e..2328fd7 100644 --- a/src/module/entities/helper.js +++ b/src/module/entities/helper.js @@ -1115,7 +1115,7 @@ module.exports = class UserProjectsHelper { } else { facetQuery['$facet']['data'] = [{ $skip: pageSize * (pageNo - 1) }, { $limit: pageSize }] } - bodyQuery, projection, facetQuery + bodyQuery = convertMongoIds(bodyQuery) // Create projection object let projection1 = {} @@ -1124,7 +1124,6 @@ module.exports = class UserProjectsHelper { projection1[projectedData] = 1 }) } - const result = await entitiesQueries.getAggregate([ { $match: bodyQuery }, { @@ -2178,3 +2177,38 @@ function addTagsInEntities(entityMetaInformation) { } return entityMetaInformation } + +// Helper function to convert mongo ids to objectIds to facilitate proper query in aggregate function +function convertMongoIds(query) { + const keysToConvert = ['_id'] // Add other fields if needed + + const convertValue = (value) => { + if (Array.isArray(value)) { + return value.map((v) => (isValidObjectId(v) ? new ObjectId(v) : v)) + } else if (isValidObjectId(value)) { + return new ObjectId(value) + } + return value + } + + const isValidObjectId = (id) => { + return typeof id === 'string' && /^[a-fA-F0-9]{24}$/.test(id) + } + + const recurse = (obj) => { + for (const key in obj) { + if (keysToConvert.includes(key)) { + if (typeof obj[key] === 'object' && obj[key] !== null && '$in' in obj[key]) { + obj[key]['$in'] = convertValue(obj[key]['$in']) + } else { + obj[key] = convertValue(obj[key]) + } + } else if (typeof obj[key] === 'object' && obj[key] !== null) { + recurse(obj[key]) + } + } + } + + recurse(query) + return query +} From 72e80dd5eb8e5c6fd17bc95d7303682252916a45 Mon Sep 17 00:00:00 2001 From: prajwal Date: Tue, 20 May 2025 21:20:02 +0530 Subject: [PATCH 36/91] pagination handled using slice method --- src/module/entities/helper.js | 34 +++++++--------------------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/src/module/entities/helper.js b/src/module/entities/helper.js index 2328fd7..5e212af 100644 --- a/src/module/entities/helper.js +++ b/src/module/entities/helper.js @@ -1106,33 +1106,13 @@ module.exports = class UserProjectsHelper { static find(bodyQuery, projection, pageNo, pageSize) { return new Promise(async (resolve, reject) => { try { - // Create facet object to attain pagination - let facetQuery = {} - facetQuery['$facet'] = {} - facetQuery['$facet']['totalCount'] = [{ $count: 'count' }] - if (pageSize === '' && pageNo === '') { - facetQuery['$facet']['data'] = [{ $skip: 0 }] - } else { - facetQuery['$facet']['data'] = [{ $skip: pageSize * (pageNo - 1) }, { $limit: pageSize }] - } - bodyQuery = convertMongoIds(bodyQuery) - - // Create projection object - let projection1 = {} - if (projection.length > 0) { - projection.forEach((projectedData) => { - projection1[projectedData] = 1 - }) + let result = await entitiesQueries.entityDocuments(bodyQuery, projection) + if (result.length > 0) { + let startIndex = pageSize * (pageNo - 1) + let endIndex = startIndex + pageSize + result = result.slice(startIndex, endIndex) } - const result = await entitiesQueries.getAggregate([ - { $match: bodyQuery }, - { - $sort: { updatedAt: -1 }, - }, - { $project: projection1 }, - facetQuery, - ]) - if (!(result.length > 0) || !result[0].data || !(result[0].data.length > 0)) { + if (result.length < 1) { throw { status: HTTP_STATUS_CODE.not_found.status, message: CONSTANTS.apiResponses.ENTITY_NOT_FOUND, @@ -1141,7 +1121,7 @@ module.exports = class UserProjectsHelper { return resolve({ success: true, message: CONSTANTS.apiResponses.ASSETS_FETCHED_SUCCESSFULLY, - result: result[0].data, + result: result, }) } catch (error) { return reject(error) From a98b2e2c7bc1db4d99a5bfaca69c9be84e635975 Mon Sep 17 00:00:00 2001 From: prajwal Date: Wed, 21 May 2025 22:32:02 +0530 Subject: [PATCH 37/91] savePoint --- src/controllers/v1/entityTypes.js | 4 +- src/databaseQueries/entityTypes.js | 24 ++++++++++ src/generics/helpers/utils.js | 36 +++++++++++++++ src/module/entities/helper.js | 70 ++++++++++++------------------ src/module/entityTypes/helper.js | 34 +++++++++++++-- 5 files changed, 120 insertions(+), 48 deletions(-) diff --git a/src/controllers/v1/entityTypes.js b/src/controllers/v1/entityTypes.js index 8c1e9db..56d7062 100644 --- a/src/controllers/v1/entityTypes.js +++ b/src/controllers/v1/entityTypes.js @@ -76,7 +76,7 @@ module.exports = class EntityTypes extends Abstract { organizationId = req.userDetails.userInformation.organizationId query['orgIds'] = { $in: [organizationId] } } - let result = await entityTypesHelper.list(query, { name: 1 }) + let result = await entityTypesHelper.list(query, ['name'], req.pageNo, req.pageSize) return resolve(result) } catch (error) { @@ -114,7 +114,7 @@ module.exports = class EntityTypes extends Abstract { return new Promise(async (resolve, reject) => { try { // Call 'entityTypesHelper.list' to find entity types based on provided query, projection, and skipFields - let result = await entityTypesHelper.list(req.body.query, req.body.projection, req.body.skipFields) + let result = await entityTypesHelper.list(req.body.query, req.body.projection, req.pageNo, req.pageSize) return resolve(result) } catch (error) { diff --git a/src/databaseQueries/entityTypes.js b/src/databaseQueries/entityTypes.js index 96ffb48..53f1584 100644 --- a/src/databaseQueries/entityTypes.js +++ b/src/databaseQueries/entityTypes.js @@ -207,4 +207,28 @@ module.exports = class EntityTypes { } }) } + + /** + * Get Aggregate of entities documents. + * @method + * @name getAggregate + * @param {Object} [aggregateData] - aggregate Data. + * @returns {Array} - entities data. + */ + + static getAggregate(aggregateData) { + return new Promise(async (resolve, reject) => { + try { + // Use database model 'entities' to perform aggregation using the provided aggregateData + let entityData = await database.models.entityTypes.aggregate(aggregateData) + return resolve(entityData) + } catch (error) { + return reject({ + status: error.status || HTTP_STATUS_CODE.bad_request.status, + message: error.message || HTTP_STATUS_CODE.bad_request.message, + errorObject: error, + }) + } + }) + } } diff --git a/src/generics/helpers/utils.js b/src/generics/helpers/utils.js index 884b827..8df55e7 100644 --- a/src/generics/helpers/utils.js +++ b/src/generics/helpers/utils.js @@ -238,6 +238,41 @@ function generateUniqueId() { return uuidV4() } +// Helper function to convert mongo ids to objectIds to facilitate proper query in aggregate function +function convertMongoIds(query) { + const keysToConvert = ['_id'] // Add other fields if needed + + const convertValue = (value) => { + if (Array.isArray(value)) { + return value.map((v) => (isValidObjectId(v) ? new ObjectId(v) : v)) + } else if (isValidObjectId(value)) { + return new ObjectId(value) + } + return value + } + + const isValidObjectId = (id) => { + return typeof id === 'string' && /^[a-fA-F0-9]{24}$/.test(id) + } + + const recurse = (obj) => { + for (const key in obj) { + if (keysToConvert.includes(key)) { + if (typeof obj[key] === 'object' && obj[key] !== null && '$in' in obj[key]) { + obj[key]['$in'] = convertValue(obj[key]['$in']) + } else { + obj[key] = convertValue(obj[key]) + } + } else if (typeof obj[key] === 'object' && obj[key] !== null) { + recurse(obj[key]) + } + } + } + + recurse(query) + return query +} + module.exports = { camelCaseToTitleCase: camelCaseToTitleCase, lowerCase: lowerCase, @@ -251,4 +286,5 @@ module.exports = { noOfElementsInArray: noOfElementsInArray, operatorValidation: operatorValidation, generateUniqueId: generateUniqueId, + convertMongoIds: convertMongoIds, } diff --git a/src/module/entities/helper.js b/src/module/entities/helper.js index 5e212af..3e0d0ec 100644 --- a/src/module/entities/helper.js +++ b/src/module/entities/helper.js @@ -1106,13 +1106,34 @@ module.exports = class UserProjectsHelper { static find(bodyQuery, projection, pageNo, pageSize) { return new Promise(async (resolve, reject) => { try { - let result = await entitiesQueries.entityDocuments(bodyQuery, projection) - if (result.length > 0) { - let startIndex = pageSize * (pageNo - 1) - let endIndex = startIndex + pageSize - result = result.slice(startIndex, endIndex) + // Create facet object to attain pagination + let facetQuery = {} + facetQuery['$facet'] = {} + facetQuery['$facet']['totalCount'] = [{ $count: 'count' }] + if (pageSize === '' && pageNo === '') { + facetQuery['$facet']['data'] = [{ $skip: 0 }] + } else { + facetQuery['$facet']['data'] = [{ $skip: pageSize * (pageNo - 1) }, { $limit: pageSize }] + } + + bodyQuery = UTILS.convertMongoIds(bodyQuery) + // Create projection object + let projection1 = {} + if (projection.length > 0) { + projection.forEach((projectedData) => { + projection1[projectedData] = 1 + }) } - if (result.length < 1) { + + const result = await entitiesQueries.getAggregate([ + { $match: bodyQuery }, + { + $sort: { updatedAt: -1 }, + }, + { $project: projection1 }, + facetQuery, + ]) + if (!(result.length > 0) || !result[0].data || !(result[0].data.length > 0)) { throw { status: HTTP_STATUS_CODE.not_found.status, message: CONSTANTS.apiResponses.ENTITY_NOT_FOUND, @@ -1121,7 +1142,7 @@ module.exports = class UserProjectsHelper { return resolve({ success: true, message: CONSTANTS.apiResponses.ASSETS_FETCHED_SUCCESSFULLY, - result: result, + result: result[0].data, }) } catch (error) { return reject(error) @@ -2157,38 +2178,3 @@ function addTagsInEntities(entityMetaInformation) { } return entityMetaInformation } - -// Helper function to convert mongo ids to objectIds to facilitate proper query in aggregate function -function convertMongoIds(query) { - const keysToConvert = ['_id'] // Add other fields if needed - - const convertValue = (value) => { - if (Array.isArray(value)) { - return value.map((v) => (isValidObjectId(v) ? new ObjectId(v) : v)) - } else if (isValidObjectId(value)) { - return new ObjectId(value) - } - return value - } - - const isValidObjectId = (id) => { - return typeof id === 'string' && /^[a-fA-F0-9]{24}$/.test(id) - } - - const recurse = (obj) => { - for (const key in obj) { - if (keysToConvert.includes(key)) { - if (typeof obj[key] === 'object' && obj[key] !== null && '$in' in obj[key]) { - obj[key]['$in'] = convertValue(obj[key]['$in']) - } else { - obj[key] = convertValue(obj[key]) - } - } else if (typeof obj[key] === 'object' && obj[key] !== null) { - recurse(obj[key]) - } - } - } - - recurse(query) - return query -} diff --git a/src/module/entityTypes/helper.js b/src/module/entityTypes/helper.js index f4b9ef9..93d2355 100644 --- a/src/module/entityTypes/helper.js +++ b/src/module/entityTypes/helper.js @@ -368,19 +368,45 @@ module.exports = class UserProjectsHelper { * List enitity Type. * @method * @name list - * @param {Object} [query = {}] - query value if required. + * @param {Object} [bodyQuery = {}] - query value if required. * @param {Object} [projection = {}] - mongodb query project object * @returns {JSON} - Details of entity. */ - static list(query = {}, projection = {}) { + static list(bodyQuery = {}, projection = [], pageNo, pageSize) { return new Promise(async (resolve, reject) => { try { + // Create facet object to attain pagination + let facetQuery = {} + facetQuery['$facet'] = {} + facetQuery['$facet']['totalCount'] = [{ $count: 'count' }] + if (pageSize === '' && pageNo === '') { + facetQuery['$facet']['data'] = [{ $skip: 0 }] + } else { + facetQuery['$facet']['data'] = [{ $skip: pageSize * (pageNo - 1) }, { $limit: pageSize }] + } + + bodyQuery = UTILS.convertMongoIds(bodyQuery) + // Create projection object + let projection1 = {} + if (projection.length > 0) { + projection.forEach((projectedData) => { + projection1[projectedData] = 1 + }) + } + // Retrieve entity type data based on the provided query and projection - let entityTypeData = await entityTypeQueries.entityTypesDocument(query, projection) + const result = await entityTypeQueries.getAggregate([ + { $match: bodyQuery }, + { + $sort: { updatedAt: -1 }, + }, + { $project: projection1 }, + facetQuery, + ]) return resolve({ message: CONSTANTS.apiResponses.ENTITY_TYPES_FETCHED, - result: entityTypeData, + result: result[0].data, }) } catch (error) { return reject(error) From db789d321e20349f474a8f8b481b61f430e2b0e9 Mon Sep 17 00:00:00 2001 From: prajwal Date: Thu, 22 May 2025 15:54:18 +0530 Subject: [PATCH 38/91] handled projection --- src/module/entities/helper.js | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/module/entities/helper.js b/src/module/entities/helper.js index 3e0d0ec..32e294b 100644 --- a/src/module/entities/helper.js +++ b/src/module/entities/helper.js @@ -1118,21 +1118,32 @@ module.exports = class UserProjectsHelper { bodyQuery = UTILS.convertMongoIds(bodyQuery) // Create projection object - let projection1 = {} - if (projection.length > 0) { + let projection1 + let aggregateData + if (Array.isArray(projection) && projection.length > 0) { + projection1 = {} projection.forEach((projectedData) => { projection1[projectedData] = 1 }) + aggregateData = [ + { $match: bodyQuery }, + { + $sort: { updatedAt: -1 }, + }, + { $project: projection1 }, + facetQuery, + ] + } else { + aggregateData = [ + { $match: bodyQuery }, + { + $sort: { updatedAt: -1 }, + }, + facetQuery, + ] } - const result = await entitiesQueries.getAggregate([ - { $match: bodyQuery }, - { - $sort: { updatedAt: -1 }, - }, - { $project: projection1 }, - facetQuery, - ]) + const result = await entitiesQueries.getAggregate(aggregateData) if (!(result.length > 0) || !result[0].data || !(result[0].data.length > 0)) { throw { status: HTTP_STATUS_CODE.not_found.status, From 847226f407b8438ee9e2363a9d6da1f57aef69bd Mon Sep 17 00:00:00 2001 From: prajwal Date: Thu, 22 May 2025 16:35:16 +0530 Subject: [PATCH 39/91] pagination handled for internal api calls --- src/controllers/v1/userRoleExtension.js | 8 ++++++- src/module/entityTypes/helper.js | 29 +++++++++++++++++-------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/controllers/v1/userRoleExtension.js b/src/controllers/v1/userRoleExtension.js index 6ad991a..cfce4ed 100644 --- a/src/controllers/v1/userRoleExtension.js +++ b/src/controllers/v1/userRoleExtension.js @@ -198,7 +198,13 @@ module.exports = class userRoleExtension extends Abstract { return new Promise(async (resolve, reject) => { try { // Call the helper function to find the user role extensions - let userData = await userRoleExtensionHelper.find(req.body.query, req.body.projection) + let userData = await userRoleExtensionHelper.find( + req.body.query, + req.body.projection, + req.pageSize, + req.pageSize * (req.pageNo - 1), + true + ) // Resolve the promise with the found user role extensions return resolve(userData) } catch (error) { diff --git a/src/module/entityTypes/helper.js b/src/module/entityTypes/helper.js index 93d2355..762f24d 100644 --- a/src/module/entityTypes/helper.js +++ b/src/module/entityTypes/helper.js @@ -389,21 +389,32 @@ module.exports = class UserProjectsHelper { bodyQuery = UTILS.convertMongoIds(bodyQuery) // Create projection object let projection1 = {} - if (projection.length > 0) { + let aggregateData + if (Array.isArray(projection) && projection.length > 0) { + projection1 = {} projection.forEach((projectedData) => { projection1[projectedData] = 1 }) + aggregateData = [ + { $match: bodyQuery }, + { + $sort: { updatedAt: -1 }, + }, + { $project: projection1 }, + facetQuery, + ] + } else { + aggregateData = [ + { $match: bodyQuery }, + { + $sort: { updatedAt: -1 }, + }, + facetQuery, + ] } // Retrieve entity type data based on the provided query and projection - const result = await entityTypeQueries.getAggregate([ - { $match: bodyQuery }, - { - $sort: { updatedAt: -1 }, - }, - { $project: projection1 }, - facetQuery, - ]) + const result = await entityTypeQueries.getAggregate(aggregateData) return resolve({ message: CONSTANTS.apiResponses.ENTITY_TYPES_FETCHED, result: result[0].data, From e181a126998b728d86c7fc308eafe68ced2d1269 Mon Sep 17 00:00:00 2001 From: prajwal Date: Fri, 23 May 2025 18:12:32 +0530 Subject: [PATCH 40/91] search filter added to endpoints --- src/controllers/v1/entities.js | 3 ++- src/controllers/v1/entityTypes.js | 10 ++++++++-- src/module/entities/helper.js | 14 +++++++++++++- src/module/entityTypes/helper.js | 16 +++++++++++++++- 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/controllers/v1/entities.js b/src/controllers/v1/entities.js index fe4bda7..7d9c582 100644 --- a/src/controllers/v1/entities.js +++ b/src/controllers/v1/entities.js @@ -75,7 +75,8 @@ module.exports = class Entities extends Abstract { req.body.query, req.body.projection, req.pageNo, - req.pageSize + req.pageSize, + req.searchText ) return resolve(entityData) } catch (error) { diff --git a/src/controllers/v1/entityTypes.js b/src/controllers/v1/entityTypes.js index 56d7062..2c11a14 100644 --- a/src/controllers/v1/entityTypes.js +++ b/src/controllers/v1/entityTypes.js @@ -76,7 +76,7 @@ module.exports = class EntityTypes extends Abstract { organizationId = req.userDetails.userInformation.organizationId query['orgIds'] = { $in: [organizationId] } } - let result = await entityTypesHelper.list(query, ['name'], req.pageNo, req.pageSize) + let result = await entityTypesHelper.list(query, ['name'], req.pageNo, req.pageSize, req.searchText) return resolve(result) } catch (error) { @@ -114,7 +114,13 @@ module.exports = class EntityTypes extends Abstract { return new Promise(async (resolve, reject) => { try { // Call 'entityTypesHelper.list' to find entity types based on provided query, projection, and skipFields - let result = await entityTypesHelper.list(req.body.query, req.body.projection, req.pageNo, req.pageSize) + let result = await entityTypesHelper.list( + req.body.query, + req.body.projection, + req.pageNo, + req.pageSize, + req.searchText + ) return resolve(result) } catch (error) { diff --git a/src/module/entities/helper.js b/src/module/entities/helper.js index 32e294b..1145cb4 100644 --- a/src/module/entities/helper.js +++ b/src/module/entities/helper.js @@ -1101,9 +1101,10 @@ module.exports = class UserProjectsHelper { * @param {Object} projection - projection to filter data * @param {Number} pageNo - page number * @param {Number} pageSize - page limit + * @param {String} searchText - search text */ - static find(bodyQuery, projection, pageNo, pageSize) { + static find(bodyQuery, projection, pageNo, pageSize, searchText) { return new Promise(async (resolve, reject) => { try { // Create facet object to attain pagination @@ -1117,6 +1118,17 @@ module.exports = class UserProjectsHelper { } bodyQuery = UTILS.convertMongoIds(bodyQuery) + + // add search filter to the bodyQuery + if (searchText != '') { + let searchData = [ + { + 'metaInformation.name': new RegExp(searchText, 'i'), + }, + ] + bodyQuery['$and'] = searchData + } + // Create projection object let projection1 let aggregateData diff --git a/src/module/entityTypes/helper.js b/src/module/entityTypes/helper.js index 762f24d..4b14038 100644 --- a/src/module/entityTypes/helper.js +++ b/src/module/entityTypes/helper.js @@ -370,10 +370,13 @@ module.exports = class UserProjectsHelper { * @name list * @param {Object} [bodyQuery = {}] - query value if required. * @param {Object} [projection = {}] - mongodb query project object + * @param {Number} [pageNo] - page no + * @param {Number} [pageSize] - page size + * @param {String} [searchText] - search text * @returns {JSON} - Details of entity. */ - static list(bodyQuery = {}, projection = [], pageNo, pageSize) { + static list(bodyQuery = {}, projection = [], pageNo, pageSize, searchText = '') { return new Promise(async (resolve, reject) => { try { // Create facet object to attain pagination @@ -387,6 +390,17 @@ module.exports = class UserProjectsHelper { } bodyQuery = UTILS.convertMongoIds(bodyQuery) + + // add search filter to the bodyQuery + if (searchText != '') { + let searchData = [ + { + name: new RegExp(searchText, 'i'), + }, + ] + bodyQuery['$and'] = searchData + } + // Create projection object let projection1 = {} let aggregateData From 89a676336d3f3b14fee15559a188954a4cf0714f Mon Sep 17 00:00:00 2001 From: prajwal Date: Tue, 27 May 2025 10:59:47 +0530 Subject: [PATCH 41/91] comments added for readability --- src/controllers/v1/userRoleExtension.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/v1/userRoleExtension.js b/src/controllers/v1/userRoleExtension.js index cfce4ed..abe57db 100644 --- a/src/controllers/v1/userRoleExtension.js +++ b/src/controllers/v1/userRoleExtension.js @@ -201,8 +201,8 @@ module.exports = class userRoleExtension extends Abstract { let userData = await userRoleExtensionHelper.find( req.body.query, req.body.projection, - req.pageSize, - req.pageSize * (req.pageNo - 1), + req.pageSize, // response limit - specifies the limit of documents needed in the response + req.pageSize * (req.pageNo - 1), // response offset - specifies the number of documents to be skipped true ) // Resolve the promise with the found user role extensions From 3c65590a878e34280ac6bfabc01d7b2ec91002d0 Mon Sep 17 00:00:00 2001 From: User3 Date: Tue, 27 May 2025 22:18:13 +0530 Subject: [PATCH 42/91] adding entityType Id to convertObjectId --- src/generics/helpers/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/generics/helpers/utils.js b/src/generics/helpers/utils.js index 8df55e7..4ba7691 100644 --- a/src/generics/helpers/utils.js +++ b/src/generics/helpers/utils.js @@ -240,7 +240,7 @@ function generateUniqueId() { // Helper function to convert mongo ids to objectIds to facilitate proper query in aggregate function function convertMongoIds(query) { - const keysToConvert = ['_id'] // Add other fields if needed + const keysToConvert = ['_id', 'entityTypeId'] // Add other fields if needed const convertValue = (value) => { if (Array.isArray(value)) { From 533d69a889f89439fabcde23678f8fc1a89da21e Mon Sep 17 00:00:00 2001 From: prajwal Date: Thu, 29 May 2025 12:14:02 +0530 Subject: [PATCH 43/91] migration script added to update entities documents --- ...TargetedEntityTypesInEntitiesCollection.js | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 src/migrations/addTargetedEntityTypesInEntitiesCollection.js diff --git a/src/migrations/addTargetedEntityTypesInEntitiesCollection.js b/src/migrations/addTargetedEntityTypesInEntitiesCollection.js new file mode 100644 index 0000000..b0ca448 --- /dev/null +++ b/src/migrations/addTargetedEntityTypesInEntitiesCollection.js @@ -0,0 +1,145 @@ +require('dotenv').config({ path: '../.env' }) +const path = require('path') +const { MongoClient } = require('mongodb') + +const MONGODB_URL = process.env.MONGODB_URL +const dbClient = new MongoClient(MONGODB_URL, { useUnifiedTopology: true }) + +let professionalSubRolesInfo = { + 'student-preschool-class-2': ['school'], + 'student-class-1-5': ['school'], + 'student-class-3-5': ['school'], + 'student-class-6-8': ['school'], + 'student-class-6-10': ['school'], + 'student-class-9-10': ['school'], + 'student-class-11-12': ['school'], + 'student-class-8-10': ['school'], + 'student-higher-education': ['school'], + 'student-pre-service-teacher': ['school'], + 'teacher-preschool-class-2': ['school'], + 'teacher-class-1-5': ['school'], + 'teacher-class-3-5': ['school'], + 'teacher-class-6-8': ['school'], + 'teacher-class-6-10': ['school'], + 'teacher-class-9-10': ['school'], + 'teacher-class-11-12': ['school'], + 'special-educators': ['school'], + 'physical-education-teacher': ['school'], + 'art-music-performing-teacher': ['school'], + counsellor: ['school'], + 'warden-caretaker': ['school'], + 'anganwadi-worker': ['school'], + 'anganwadi-helper': ['school'], + librarian: ['school'], + 'technician-lab-it': ['school'], + 'principal-head-teacher': ['school'], + 'vice-principal-asst-head-teacher': ['school'], + 'head-teacher-incharge': ['school'], + 'teacher-educator-SCERT': ['state'], + 'teacher-educator-DIET': ['state'], + 'teacher-educator-IASE': ['state'], + 'teacher-educator-univ-deptt': ['state'], + 'teacher-educator-TEI': ['state'], + 'teacher-educator-SIET': ['state'], + 'teacher-educator-CTE': ['state'], + 'teacher-educator-BASIC': ['state'], + 'block-resource-centre-coordinator-BRCC': ['block'], + 'cluster-resource-centre-oordinator-CRCC': ['cluster'], + 'state-coordinator': ['state'], + 'district-coordinator': ['school'], + 'assistant-district-coordinator': ['district'], + coordinator: ['school'], + 'mentor-advisor': ['school'], + 'resource-person-state-district-block': ['state'], + 'shikshak-sankul': ['school'], + 'principal-secretary-commissioner-secretary-school-education': ['school'], + 'additional-secretary-commissioner-school-education': ['school'], + 'joint-secretary-education': ['school'], + 'assistant-commissioner': ['school'], + 'additional-director': ['school'], + 'director-public-instructions-elementary-secondary': ['school'], + 'project-director-SPD': ['state'], + 'joint-director': ['school'], + 'assistant-state-project-director': ['state'], + 'additional-state-project-director': ['state'], + 'director-basic': ['school'], + 'head-autonomous-organization': ['school'], + 'director-SCERT': ['state'], + 'principal-DIET': ['school'], + 'collector-DM-DC': ['school'], + 'head-state-training-center': ['state'], + 'education-officer': ['school'], + 'chief-education-officer': ['state'], + 'district-education-officer-DEO': ['district'], + 'block-ducation-fficer-BEO': ['block'], + 'MIS-coordinator': ['school'], + 'subject-inspector': ['school'], + 'evaluation-officer': ['school'], + 'extension-officer': ['school'], + 'CDPO-child-development-project-officer': ['school'], + supervisor: ['school'], + 'program-officer': ['school'], + 'basic-shiksha-adhikari': ['school'], + 'director-primary-education': ['school'], + 'Desk-officer-education': ['school'], + 'director-secondary-and-higher-secondary-education': ['school'], + 'director-scheme': ['school'], + 'director-balbharati': ['school'], + 'director-state-education-board': ['state'], +} + +let cache = {} + +async function runMigration() { + try { + await dbClient.connect() + const db = dbClient.db() + + for (const [externalId, entityTypeNames] of Object.entries(professionalSubRolesInfo)) { + if (!entityTypeNames.length) continue + + // Step 1: Fetch all matching entities (with that externalId) + const entityDocs = await db + .collection('entities') + .find({ 'metaInformation.externalId': externalId }) + .toArray() + for (const entity of entityDocs) { + const tenantId = entity.tenantId + + // Step 2: Fetch matching entityTypes for this tenant + const matchingEntityTypes = await db + .collection('entityTypes') + .find( + { + name: { $in: entityTypeNames }, + tenantId: tenantId, + }, + { projection: { _id: 1, name: 1 } } + ) + .toArray() + + const targetedEntityTypes = matchingEntityTypes.map((type) => ({ + entityType: type.name, + entityTypeId: type._id.toString(), + })) + if (targetedEntityTypes.length) { + const updateResult = await db + .collection('entities') + .updateOne( + { _id: entity._id }, + { $set: { 'metaInformation.targetedEntityTypes': targetedEntityTypes } } + ) + console.log(`Updated entity ${entity._id} for tenant ${tenantId}`) + } else { + console.warn(`No matching entityTypes found for entity ${entity._id} (tenantId: ${tenantId})`) + } + } + } + } catch (err) { + console.error('Migration error:', err) + } finally { + await dbClient.close() + } +} + +runMigration() From 1e9c3fc76180dc4c44a5a25b7e50e48092f66b58 Mon Sep 17 00:00:00 2001 From: prajwal Date: Thu, 29 May 2025 12:15:58 +0530 Subject: [PATCH 44/91] removed unused code from the script --- src/migrations/addTargetedEntityTypesInEntitiesCollection.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/migrations/addTargetedEntityTypesInEntitiesCollection.js b/src/migrations/addTargetedEntityTypesInEntitiesCollection.js index b0ca448..429f36f 100644 --- a/src/migrations/addTargetedEntityTypesInEntitiesCollection.js +++ b/src/migrations/addTargetedEntityTypesInEntitiesCollection.js @@ -88,8 +88,6 @@ let professionalSubRolesInfo = { 'director-state-education-board': ['state'], } -let cache = {} - async function runMigration() { try { await dbClient.connect() From efc110d0602b98de68a4b801a846e20fe59fa869 Mon Sep 17 00:00:00 2001 From: MallanagoudaB Date: Fri, 30 May 2025 17:21:32 +0530 Subject: [PATCH 45/91] "added-targetedEntityTypes-field" --- src/api-doc/api-doc.yaml | 173 ++++++++++++++++++++++++--------- src/controllers/v1/entities.js | 2 +- src/models/entities.js | 1 + src/module/entities/helper.js | 101 +++++++++++++++++++ 4 files changed, 230 insertions(+), 47 deletions(-) diff --git a/src/api-doc/api-doc.yaml b/src/api-doc/api-doc.yaml index ee5aa45..150c0a5 100644 --- a/src/api-doc/api-doc.yaml +++ b/src/api-doc/api-doc.yaml @@ -890,6 +890,25 @@ paths: items: type: object properties: + metaInformation: + type: object + properties: + targetedEntityTypes: + type: array + items: + type: object + properties: + entityType: + type: string + entityTypeId: + type: string + required: + - entityType + - entityTypeId + externalId: + type: string + name: + type: string childHierarchyPath: type: array items: @@ -898,6 +917,10 @@ paths: type: string updatedBy: type: string + orgIds: + type: array + items: + type: string _id: type: string deleted: @@ -913,11 +936,10 @@ paths: type: string code: type: string - metaInformation: - type: object - properties: - externalId: - type: string + userId: + type: string + tenantId: + type: string updatedAt: type: string createdAt: @@ -930,25 +952,34 @@ paths: message: ENTITY_ADDED status: 200 result: - - childHierarchyPath: + - metaInformation: + targetedEntityTypes: + - entityType: state + entityTypeId: 683953548f365ab56c8022e9 + - entityType: block + entityTypeId: 6839535b8f365ab56c8022ed + externalId: KA28 + name: Karnataka + childHierarchyPath: - district - - beat + - block - cluster - school - - block - createdBy: SYSTEM - updatedBy: SYSTEM - _id: 6634b0767411a605fdbaca71 + createdBy: '208' + updatedBy: '208' + orgIds: + - ALL + _id: 683996e732316a1ac4f6f150 deleted: false - entityTypeId: 5f32d8228e0dc83124040567 - entityType: school + entityTypeId: 683953808f365ab56c8022f5 + entityType: professional_role registryDetails: - locationId: entity123 - code: entity123 - metaInformation: - externalId: entity123 - updatedAt: '2024-05-03T09:37:58.425Z' - createdAt: '2024-05-03T09:37:58.425Z' + locationId: KA28 + code: KA28 + userId: '208' + tenantId: '24' + updatedAt: '2025-05-30T11:30:47.909Z' + createdAt: '2025-05-30T11:30:47.909Z' __v: 0 '400': description: Bad Request. @@ -979,22 +1010,26 @@ paths: type: string name: type: string - entityType: - type: string childHierarchyPath: type: array items: type: string + targetedEntityTypes: + type: array + items: + type: string examples: sampleBodyData: value: - externalId: entity123 - name: entityName + externalId: KA28 + name: Karnataka childHierarchyPath: - district - - beat + - block - cluster - school + targetedEntityTypes: + - state - block /v1/entities/details/{_id}?&language={code}: get: @@ -1148,8 +1183,29 @@ paths: metaInformation: type: object properties: + targetedEntityTypes: + type: array + items: + type: object + properties: + entityType: + type: string + entityTypeId: + type: string + required: + - entityType + - entityTypeId externalId: type: string + name: + type: string + registryDetails: + type: object + properties: + locationId: + type: string + code: + type: string childHierarchyPath: type: array items: @@ -1158,6 +1214,10 @@ paths: type: string updatedBy: type: string + orgIds: + type: array + items: + type: string deleted: type: boolean _id: @@ -1166,6 +1226,10 @@ paths: type: string entityType: type: string + userId: + type: string + tenantId: + type: string updatedAt: type: string createdAt: @@ -1175,24 +1239,33 @@ paths: examples: SuccessResponse: value: - message: ENTITY_UPDATATED + message: ENTITY_UPDATED status: 200 result: metaInformation: - externalId: SCH - name: school + targetedEntityTypes: + - entityType: state + entityTypeId: 683953548f365ab56c8022e9 + - entityType: block + entityTypeId: 6839535b8f365ab56c8022ed + externalId: KA28 + name: Karnataka registryDetails: - locationId: rajAPSTATEDummy4 - code: rajAPSTATEDummy4 + locationId: KA28 + code: KA28 childHierarchyPath: [] - createdBy: user123 - updatedBy: user123 + createdBy: '208' + updatedBy: '208' + orgIds: + - ALL deleted: false - _id: 66ea64fa68cd063346a10365 - entityTypeId: 6672ce0fc05aa58f89ba12f1 - entityType: state - updatedAt: '2024-09-18T10:07:39.407Z' - createdAt: '2024-09-18T05:28:26.148Z' + _id: 68395a55e5af17c215ce3408 + entityTypeId: 683953808f365ab56c8022f5 + entityType: professional_role + userId: '208' + tenantId: '24' + updatedAt: '2025-05-30T11:32:35.296Z' + createdAt: '2025-05-30T07:12:21.457Z' __v: 0 '400': description: Bad Request. @@ -1216,26 +1289,34 @@ paths: schema: type: object properties: - metaInformation.externalId: - type: string + metaInformation.targetedEntityTypes: + type: array + items: + type: object + properties: + entityType: + type: string + entityTypeId: + type: string + required: + - entityType + - entityTypeId metaInformation.name: type: string childHierarchyPath: type: array items: type: string - createdBy: - type: string - updatedBy: - type: string examples: sampleBodyData: value: - metaInformation.externalId: SCH - metaInformation.name: school + metaInformation.targetedEntityTypes: + - entityType: state + entityTypeId: 683953548f365ab56c8022e9 + - entityType: block + entityTypeId: 6839535b8f365ab56c8022ed + metaInformation.name: 'Karnataka ' childHierarchyPath: [] - createdBy: user123 - updatedBy: user123 /v1/entities/mappingUpload: post: summary: This endpoint will map its childEntity to its parentEntity diff --git a/src/controllers/v1/entities.js b/src/controllers/v1/entities.js index 7d9c582..145ff01 100644 --- a/src/controllers/v1/entities.js +++ b/src/controllers/v1/entities.js @@ -1059,7 +1059,7 @@ module.exports = class Entities extends Abstract { translationFile = JSON.parse(req.files.translationFile.data.toString()) } // Call 'entitiesHelper.bulkUpdate' to update entities based on CSV data and user details - let newEntityData = await entitiesHelper.bulkUpdate(entityCSVData, translationFile, userDetails) + let newEntityData = await entitiesHelper.bulkUpdate(entityCSVData, translationFile, req.userDetails) // Check if entities were updated successfully if (newEntityData.length > 0) { diff --git a/src/models/entities.js b/src/models/entities.js index a38d52a..60447c3 100644 --- a/src/models/entities.js +++ b/src/models/entities.js @@ -17,6 +17,7 @@ module.exports = { metaInformation: { externalId: { type: String, index: true }, name: { type: String, index: true }, + targetedEntityTypes: { type: Array, index: true }, }, childHierarchyPath: Array, userId: { diff --git a/src/module/entities/helper.js b/src/module/entities/helper.js index 1145cb4..244ee64 100644 --- a/src/module/entities/helper.js +++ b/src/module/entities/helper.js @@ -1328,6 +1328,34 @@ module.exports = class UserProjectsHelper { childHierarchyPath = validatedChildHierarchy.map(String) } + const formattedTargetedEntityTypes = [] + if (singleEntity.targetedEntityTypes) { + for (const entityType of singleEntity.targetedEntityTypes) { + try { + const entityTypeDocument = await entityTypeQueries.findOne( + { name: entityType, tenantId: tenantId }, + { _id: 1 } + ) + + if (entityTypeDocument) { + formattedTargetedEntityTypes.push({ + entityType: entityType, + entityTypeId: entityTypeDocument._id.toString(), + }) + } else { + throw { + status: HTTP_STATUS_CODE.bad_request.status, + message: CONSTANTS.apiResponses.ENTITY_NOT_FOUND, + } + } + } catch (error) { + return reject(error) + } + } + } + + singleEntity.targetedEntityTypes = formattedTargetedEntityTypes + // Construct the entity document to be created let entityDoc = { entityTypeId: entityTypeDocument._id, @@ -1639,6 +1667,36 @@ module.exports = class UserProjectsHelper { // entityCreation['allowedRoles'] = await allowedRoles(singleEntity.allowedRoles) // delete singleEntity.allowedRoles // } + const formattedTargetedEntityTypes = [] + if (singleEntity.targetedEntityTypes) { + const entityTypesArray = singleEntity.targetedEntityTypes + .replace(/^"(.*)"$/, '$1') // remove starting and ending quotes + .split(',') + .map((type) => type.trim()) + for (const entityType of entityTypesArray) { + try { + const entityTypeDocument = await entityTypeQueries.findOne( + { name: entityType, tenantId: tenantId }, + { _id: 1 } + ) + + if (entityTypeDocument) { + formattedTargetedEntityTypes.push({ + entityType: entityType, + entityTypeId: entityTypeDocument._id.toString(), + }) + } else { + throw { + status: HTTP_STATUS_CODE.bad_request.status, + message: CONSTANTS.apiResponses.ENTITY_NOT_FOUND, + } + } + } catch (error) { + return reject(error) + } + } + } + singleEntity.targetedEntityTypes = formattedTargetedEntityTypes if (singleEntity.childHierarchyPath) { entityCreation['childHierarchyPath'] = JSON.parse(singleEntity['childHierarchyPath']) } @@ -1792,6 +1850,28 @@ module.exports = class UserProjectsHelper { updateData['translations'] = translationFile[updateData['metaInformation.name']] } + if (entityCSVData.targetedEntityTypes) { + await Promise.all( + entityCSVData.targetedEntityTypes.map(async (entityTypeData) => { + // Validate that both entityType and entityTypeId exist in the entityType DB + let entityTypeFilterQuery = { + name: entityTypeData.entityType, + _id: ObjectId(entityTypeData.entityTypeId), + tenantId: tenantId, + } + let existingEntityType = await entityTypeQueries.findOne(entityTypeFilterQuery) + + if (!existingEntityType) { + // If any entityType is invalid, reject the request + throw { + status: HTTP_STATUS_CODE.bad_request.status, + message: `EntityType '${entityTypeData.entityType}' with ID '${entityTypeData.entityTypeId}' & tenantId ${tenantId} does not exist.`, + } + } + }) + ) + } + if (Object.keys(updateData).length > 0) { let updateEntity = await entitiesQueries.findOneAndUpdate( { _id: singleEntity['_SYSTEM_ID'], tenantId: tenantId }, @@ -1857,6 +1937,27 @@ module.exports = class UserProjectsHelper { } } } + if (bodyData['metaInformation.targetedEntityTypes']) { + await Promise.all( + bodyData['metaInformation.targetedEntityTypes'].map(async (entityTypeData) => { + // Validate that both entityType and entityTypeId exist in the entityType DB + let entityTypeFilterQuery = { + name: entityTypeData.entityType, + _id: ObjectId(entityTypeData.entityTypeId), + tenantId: tenantId, + } + let existingEntityType = await entityTypeQueries.findOne(entityTypeFilterQuery) + + if (!existingEntityType) { + // If any entityType is invalid, reject the request + throw { + status: HTTP_STATUS_CODE.bad_request.status, + message: `EntityType '${entityTypeData.entityType}' with ID '${entityTypeData.entityTypeId}' & tenantId ${tenantId} does not exist.`, + } + } + }) + ) + } // Update the entity using findOneAndUpdate let entityInformation = await entitiesQueries.findOneAndUpdate( From c95a517d1da4b1d5be2886a8a7c37b5d1e717cd7 Mon Sep 17 00:00:00 2001 From: borkarsaish65 Date: Mon, 2 Jun 2025 18:29:50 +0530 Subject: [PATCH 46/91] system admin change + filter modification + model changes --- src/controllers/v1/entityTypes.js | 3 -- src/generics/constants/api-responses.js | 2 + src/generics/constants/endpoints.js | 1 + src/generics/middleware/authenticator.js | 36 +++++++++------ src/generics/services/users.js | 53 +++++++++++++++++++++ src/migrations/fixOrgIdsFromCollections.js | 54 ++++++++++++++++++++++ src/models/entities.js | 4 +- src/models/entityTypes.js | 4 +- src/models/userRoleExtension.js | 4 +- src/module/entities/helper.js | 8 ++-- src/module/entityTypes/helper.js | 9 +++- src/module/userRoleExtension/helper.js | 2 +- 12 files changed, 149 insertions(+), 31 deletions(-) create mode 100644 src/migrations/fixOrgIdsFromCollections.js diff --git a/src/controllers/v1/entityTypes.js b/src/controllers/v1/entityTypes.js index 2c11a14..5d8877c 100644 --- a/src/controllers/v1/entityTypes.js +++ b/src/controllers/v1/entityTypes.js @@ -67,9 +67,6 @@ module.exports = class EntityTypes extends Abstract { query['tenantId'] = req.userDetails.tenantAndOrgInfo ? req.userDetails.tenantAndOrgInfo.tenantId : req.userDetails.userInformation.tenantId - query['orgIds'] = req.userDetails.tenantAndOrgInfo - ? { $in: req.userDetails.tenantAndOrgInfo.orgIds } - : { $in: [req.userDetails.userInformation.organizationId] } // handle currentOrgOnly filter if (req.query['currentOrgOnly'] && req.query['currentOrgOnly'] == 'true') { diff --git a/src/generics/constants/api-responses.js b/src/generics/constants/api-responses.js index a84fea1..d388cc6 100644 --- a/src/generics/constants/api-responses.js +++ b/src/generics/constants/api-responses.js @@ -74,4 +74,6 @@ module.exports = { NOT_VALID_ID_AND_EXTERNALID: 'NOT_VALID_ID_AND_EXTERNALID', TENANT_ID_MISSING_CODE: 'ERR_TENANT_ID_MISSING', TENANT_ID_MISSING_MESSAGE: 'Require tenantId in header', + INVALID_ROLE_FOR_CREATION_ERR: 'ERR_INVALID_ROLE', + INVALID_ROLE_FOR_CREATION_MSG: 'Invalid role provided for resource creation', } diff --git a/src/generics/constants/endpoints.js b/src/generics/constants/endpoints.js index 2a8b9c2..e3053ac 100644 --- a/src/generics/constants/endpoints.js +++ b/src/generics/constants/endpoints.js @@ -8,4 +8,5 @@ module.exports = { // End points to be added here ORGANIZATION_READ: '/v1/organization/read', + TENANT_READ: '/v1/tenant/read', } diff --git a/src/generics/middleware/authenticator.js b/src/generics/middleware/authenticator.js index 54bcc61..1e06462 100644 --- a/src/generics/middleware/authenticator.js +++ b/src/generics/middleware/authenticator.js @@ -254,7 +254,8 @@ module.exports = async function (req, res, next, token = '') { let fetchSingleOrgIdFunc = await fetchSingleOrgIdFromProvidedData( decodedToken.data.tenant_id.toString(), decodedToken.data.organization_ids, - req.headers['orgid'] + req.headers['orgid'], + token ) if (!fetchSingleOrgIdFunc.success) { @@ -326,11 +327,12 @@ module.exports = async function (req, res, next, token = '') { * * @param {String} tenantId - ID of the tenant * @param {String} orgId - Comma separated string of org IDs or 'ALL' + * @param {String} token - The authentication token * @returns {Object} - Success with validOrgIds array or failure with error object */ - async function validateIfOrgsBelongsToTenant(tenantId, orgId) { + async function validateIfOrgsBelongsToTenant(tenantId, orgId, token) { let orgIdArr = Array.isArray(orgId) ? orgId : typeof orgId === 'string' ? orgId.split(',') : [] - let orgDetails = await userService.fetchOrgDetails(tenantId) + let orgDetails = await userService.fetchTenantDetails(tenantId, token) let validOrgIds = null if (orgIdArr.includes('ALL') || orgIdArr.includes('all')) { @@ -341,8 +343,8 @@ module.exports = async function (req, res, next, token = '') { !orgDetails.success || !orgDetails.data || !(Object.keys(orgDetails.data).length > 0) || - !orgDetails.data.related_orgs || - !(orgDetails.data.related_orgs.length > 0) + !orgDetails.data.organizations || + !(orgDetails.data.organizations.length > 0) ) { let errorObj = {} errorObj.errCode = CONSTANTS.apiResponses.ORG_DETAILS_FETCH_UNSUCCESSFUL_CODE @@ -352,7 +354,9 @@ module.exports = async function (req, res, next, token = '') { } // convert the types of items to string - orgDetails.data.related_orgs = orgDetails.data.related_orgs.map(String) + orgDetails.data.related_orgs = orgDetails.data.organizations.map((data) => { + return data.code.toString() + }) // aggregate valid orgids let relatedOrgIds = orgDetails.data.related_orgs @@ -376,9 +380,10 @@ module.exports = async function (req, res, next, token = '') { * @param {String} tenantId - ID of the tenant * @param {String[]} orgIdArr - Array of orgIds to choose from * @param {String} orgIdFromHeader - The orgId provided in the request headers + * @param {String} token - The authentication token * @returns {Promise} - Returns a promise resolving to an object containing the success status, orgId, or error details */ - async function fetchSingleOrgIdFromProvidedData(tenantId, orgIdArr, orgIdFromHeader) { + async function fetchSingleOrgIdFromProvidedData(tenantId, orgIdArr, orgIdFromHeader, token) { try { // Check if orgIdFromHeader is provided and valid if (orgIdFromHeader && orgIdFromHeader != '') { @@ -386,7 +391,7 @@ module.exports = async function (req, res, next, token = '') { throw CONSTANTS.apiResponses.TENANTID_AND_ORGID_REQUIRED_IN_TOKEN_CODE } - let validateOrgsResult = await validateIfOrgsBelongsToTenant(tenantId, orgIdFromHeader) + let validateOrgsResult = await validateIfOrgsBelongsToTenant(tenantId, orgIdFromHeader, token) if (!validateOrgsResult.success) { throw CONSTANTS.apiResponses.TENANTID_AND_ORGID_REQUIRED_IN_TOKEN_CODE @@ -472,7 +477,8 @@ module.exports = async function (req, res, next, token = '') { let validateOrgsResult = await validateIfOrgsBelongsToTenant( req.headers['tenantid'], - req.headers['orgid'] + req.headers['orgid'], + token ) if (!validateOrgsResult.success) { @@ -498,7 +504,8 @@ module.exports = async function (req, res, next, token = '') { let validateOrgsResult = await validateIfOrgsBelongsToTenant( req.headers['tenantid'], - req.headers['orgid'] + req.headers['orgid'], + token ) if (!validateOrgsResult.success) { return res.status(responseCode['unauthorized'].status).send(respUtil(validateOrgsResult.errorObj)) @@ -508,14 +515,13 @@ module.exports = async function (req, res, next, token = '') { req.headers['tenantid'] = decodedToken.data.tenant_id.toString() req.headers['orgid'] = [decodedToken.data.organization_id.toString()] } else { - rspObj.errCode = reqMsg.INVALID_ROLE.INVALID_CODE - rspObj.errMsg = reqMsg.INVALID_ROLE.INVALID_MESSAGE - rspObj.responseCode = responseCode.unauthorized.status - return res.status(responseCode['unauthorized'].status).send(respUtil(rspObj)) + rspObj.errCode = CONSTANTS.apiResponses.INVALID_ROLE_FOR_CREATION_ERR + rspObj.errMsg = CONSTANTS.apiResponses.INVALID_ROLE_FOR_CREATION_MSG + return res.status(HTTP_STATUS_CODE['unauthorized'].status).send(respUtil(rspObj)) } decodedToken.data.tenantAndOrgInfo['tenantId'] = req.headers['tenantid'].toString() - decodedToken.data.tenantAndOrgInfo['orgIds'] = req.headers['orgid'] + decodedToken.data.tenantAndOrgInfo['orgId'] = req.headers['orgid'] } } catch (err) { rspObj.errCode = CONSTANTS.apiResponses.TOKEN_MISSING_CODE diff --git a/src/generics/services/users.js b/src/generics/services/users.js index d30a70a..c1d6226 100644 --- a/src/generics/services/users.js +++ b/src/generics/services/users.js @@ -56,7 +56,60 @@ const fetchOrgDetails = function (organisationIdentifier, userToken) { } }) } +/** + * Fetches the tenant details for a given tenant ID along with org it is associated with. + * @param {string} tenantId - The code/id of the organization. + * @param {String} userToken - user token + * @returns {Promise} A promise that resolves with the organization details or rejects with an error. + */ +const fetchTenantDetails = function (tenantId, userToken) { + return new Promise(async (resolve, reject) => { + try { + let url = + interfaceServiceUrl + + process.env.USER_SERVICE_BASE_URL + + CONSTANTS.endpoints.TENANT_READ + + '/' + + tenantId + const options = { + headers: { + 'content-type': 'application/json', + 'X-auth-token': userToken, + }, + } + + request.get(url, options, userReadCallback) + let result = { + success: true, + } + function userReadCallback(err, data) { + if (err) { + result.success = false + } else { + let response = JSON.parse(data.body) + if (response.responseCode === HTTP_STATUS_CODE['ok'].code) { + result['data'] = response.result + result.success = true + } else { + result.success = false + } + } + return resolve(result) + } + setTimeout(function () { + return resolve( + (result = { + success: false, + }) + ) + }, CONSTANTS.common.SERVER_TIME_OUT) + } catch (error) { + return reject(error) + } + }) +} module.exports = { fetchOrgDetails: fetchOrgDetails, + fetchTenantDetails: fetchTenantDetails, } diff --git a/src/migrations/fixOrgIdsFromCollections.js b/src/migrations/fixOrgIdsFromCollections.js new file mode 100644 index 0000000..4c5f4a4 --- /dev/null +++ b/src/migrations/fixOrgIdsFromCollections.js @@ -0,0 +1,54 @@ +/** + * name : fixOrgIdsFromCollections.js + * author : Saish R B + * created-date : 26-May-2025 + * Description : Migration script to update orgIds to scope + */ + +require('dotenv').config({ path: '../.env' }) +const { MongoClient } = require('mongodb') +const MONGODB_URL = process.env.MONGODB_URL +const DB = process.env.DB + +const dbClient = new MongoClient(MONGODB_URL) + +async function modifyCollection(collectionName) { + console.log(`Starting migration for collection: ${collectionName}`) + await dbClient.connect() + const db = dbClient.db(DB) // default DB from connection string + const collection = db.collection(collectionName) + + const cursor = collection.find({ + orgIds: { $exists: true, $type: 'array' }, + }) + + while (await cursor.hasNext()) { + console.log(`processing for collection: ${collectionName}`) + const doc = await cursor.next() + + console.log(`Processing document with _id: ${doc._id}`) + + const orgIds = doc.orgIds + + if (orgIds.length === 0 || !orgIds) { + console.log(`Skipping document with _id: ${doc._id} as it contains 'ALL' or is empty.`) + continue + } + + let dataToBeUpdated = {} + dataToBeUpdated['orgId'] = orgIds[0] + await collection.updateOne({ _id: doc._id }, { $unset: { orgIds: '' }, $set: { ...dataToBeUpdated } }) + } + + await dbClient.close() + console.log(`Collection "${collectionName}" migration completed.`) +} + +async function runMigration() { + // Example call, you can call modifyCollection with different collection names + await modifyCollection('entities') + await modifyCollection('entityTypes') + await modifyCollection('userRoleExtension') +} + +runMigration() diff --git a/src/models/entities.js b/src/models/entities.js index a38d52a..fa192e6 100644 --- a/src/models/entities.js +++ b/src/models/entities.js @@ -42,8 +42,8 @@ module.exports = { index: true, require: true, }, - orgIds: { - type: Array, + orgId: { + type: String, require: true, index: true, }, diff --git a/src/models/entityTypes.js b/src/models/entityTypes.js index 932b46c..6a23dd8 100644 --- a/src/models/entityTypes.js +++ b/src/models/entityTypes.js @@ -37,8 +37,8 @@ module.exports = { index: true, require: true, }, - orgIds: { - type: Array, + orgId: { + type: String, require: true, index: true, }, diff --git a/src/models/userRoleExtension.js b/src/models/userRoleExtension.js index f3032c7..2e800d6 100644 --- a/src/models/userRoleExtension.js +++ b/src/models/userRoleExtension.js @@ -43,8 +43,8 @@ module.exports = { index: true, require: true, }, - orgIds: { - type: Array, + orgId: { + type: String, require: true, index: true, }, diff --git a/src/module/entities/helper.js b/src/module/entities/helper.js index 1145cb4..718f3e0 100644 --- a/src/module/entities/helper.js +++ b/src/module/entities/helper.js @@ -1274,7 +1274,7 @@ module.exports = class UserProjectsHelper { // Find the entities document based on the entityType in queryParams let tenantId = userDetails.tenantAndOrgInfo.tenantId - let orgId = CONSTANTS.common.ALL + let orgId = userDetails.tenantAndOrgInfo.orgId[0] let entityTypeDocument = await entityTypeQueries.findOne( { name: queryParams.type, tenantId: tenantId }, { _id: 1 } @@ -1340,7 +1340,7 @@ module.exports = class UserProjectsHelper { createdBy: userDetails.userInformation.userId, userId: userDetails.userInformation.userId, tenantId: tenantId, - orgIds: orgId, + orgId: orgId, } entityDocuments.push(entityDoc) @@ -1602,7 +1602,7 @@ module.exports = class UserProjectsHelper { // Find the entity type document based on the provided entityType let tenantId = userDetails.tenantAndOrgInfo.tenantId - let orgId = CONSTANTS.common.ALL + let orgId = userDetails.tenantAndOrgInfo.orgId[0] let entityTypeDocument = await entityTypeQueries.findOne( { name: entityType, @@ -1633,7 +1633,7 @@ module.exports = class UserProjectsHelper { updatedBy: userId, createdBy: userId, tenantId: tenantId, - orgIds: orgId, + orgId: orgId, } // if (singleEntity.allowedRoles && singleEntity.allowedRoles.length > 0) { // entityCreation['allowedRoles'] = await allowedRoles(singleEntity.allowedRoles) diff --git a/src/module/entityTypes/helper.js b/src/module/entityTypes/helper.js index 4b14038..4074702 100644 --- a/src/module/entityTypes/helper.js +++ b/src/module/entityTypes/helper.js @@ -26,6 +26,7 @@ module.exports = class UserProjectsHelper { * @returns {JSON} - uploaded entity information. */ static bulkCreate(entityTypesCSVData, userDetails) { + console.log(userDetails, '<--userDetails in bulkCreate entityTypesCSVData') return new Promise(async (resolve, reject) => { try { const entityTypesUploadedData = await Promise.all( @@ -33,7 +34,7 @@ module.exports = class UserProjectsHelper { try { entityType = UTILS.valueParser(entityType) entityType['tenantId'] = userDetails.tenantAndOrgInfo.tenantId - entityType['orgIds'] = CONSTANTS.common.ALL + entityType['orgId'] = userDetails.tenantAndOrgInfo.orgId[0] entityType.registryDetails = {} let removedKeys = [] @@ -113,6 +114,7 @@ module.exports = class UserProjectsHelper { entityType.status = CONSTANTS.apiResponses.FAILURE } } catch (error) { + console.log(error, '<--error in bulkCreate entityTypesCSVData') entityType['_SYSTEM_ID'] = '' entityType.status = CONSTANTS.apiResponses.FAILURE entityType.message = CONSTANTS.apiResponses.FAILURE @@ -124,6 +126,7 @@ module.exports = class UserProjectsHelper { return resolve(entityTypesUploadedData) } catch (error) { + console.log(error, '<--error in bulkCreate entityTypesCSVData') return reject(error) } }) @@ -142,7 +145,7 @@ module.exports = class UserProjectsHelper { try { let entityType = body entityType['tenantId'] = userDetails.tenantAndOrgInfo.tenantId - entityType['orgIds'] = CONSTANTS.common.ALL + entityType['orgId'] = userDetails.tenantAndOrgInfo.orgId[0] if (entityType.profileFields) { entityType.profileFields = entityType.profileFields.split(',') || [] @@ -427,6 +430,8 @@ module.exports = class UserProjectsHelper { ] } + console.log(bodyQuery, '<--bodyQuery') + // Retrieve entity type data based on the provided query and projection const result = await entityTypeQueries.getAggregate(aggregateData) return resolve({ diff --git a/src/module/userRoleExtension/helper.js b/src/module/userRoleExtension/helper.js index 4aeedda..f3b5a7b 100644 --- a/src/module/userRoleExtension/helper.js +++ b/src/module/userRoleExtension/helper.js @@ -43,7 +43,7 @@ module.exports = class userRoleExtensionHelper { }) ) body['tenantId'] = userDetails.tenantAndOrgInfo.tenantId - body['orgIds'] = CONSTANTS.common.ALL + body['orgId'] = userDetails.tenantAndOrgInfo.orgId[0] // Call the queries function to create a new user role extension with the provided body data let newUserRole = await userRoleExtensionQueries.create(body) From 521db8d1efb413dd23e715cfd32248b0c493dbf7 Mon Sep 17 00:00:00 2001 From: borkarsaish65 Date: Mon, 2 Jun 2025 18:31:45 +0530 Subject: [PATCH 47/91] feat: review 2.0 --- src/module/entityTypes/helper.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/module/entityTypes/helper.js b/src/module/entityTypes/helper.js index 4074702..fc957a3 100644 --- a/src/module/entityTypes/helper.js +++ b/src/module/entityTypes/helper.js @@ -114,7 +114,6 @@ module.exports = class UserProjectsHelper { entityType.status = CONSTANTS.apiResponses.FAILURE } } catch (error) { - console.log(error, '<--error in bulkCreate entityTypesCSVData') entityType['_SYSTEM_ID'] = '' entityType.status = CONSTANTS.apiResponses.FAILURE entityType.message = CONSTANTS.apiResponses.FAILURE @@ -126,7 +125,6 @@ module.exports = class UserProjectsHelper { return resolve(entityTypesUploadedData) } catch (error) { - console.log(error, '<--error in bulkCreate entityTypesCSVData') return reject(error) } }) From 240e2f042440ff9fbdcb842118dcf593917e237d Mon Sep 17 00:00:00 2001 From: borkarsaish65 Date: Tue, 3 Jun 2025 15:52:28 +0530 Subject: [PATCH 48/91] savepoint --- src/migrations/fixOrgIdsFromCollections.js | 65 ++++++++++++++++------ 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/src/migrations/fixOrgIdsFromCollections.js b/src/migrations/fixOrgIdsFromCollections.js index 4c5f4a4..3ecb0b0 100644 --- a/src/migrations/fixOrgIdsFromCollections.js +++ b/src/migrations/fixOrgIdsFromCollections.js @@ -12,43 +12,72 @@ const DB = process.env.DB const dbClient = new MongoClient(MONGODB_URL) +const BATCH_SIZE = 100 + async function modifyCollection(collectionName) { console.log(`Starting migration for collection: ${collectionName}`) - await dbClient.connect() - const db = dbClient.db(DB) // default DB from connection string + + const db = dbClient.db(DB) const collection = db.collection(collectionName) - const cursor = collection.find({ - orgIds: { $exists: true, $type: 'array' }, - }) + const cursor = collection.find( + { + orgIds: { $exists: true, $type: 'array' }, + }, + { + projection: { _id: 1, orgIds: 1 }, + } + ) + + let batch = [] while (await cursor.hasNext()) { console.log(`processing for collection: ${collectionName}`) const doc = await cursor.next() - console.log(`Processing document with _id: ${doc._id}`) + if (!doc.orgIds || doc.orgIds.length === 0) { + console.log(`Skipping _id: ${doc._id} - empty orgIds`) + continue + } - const orgIds = doc.orgIds + batch.push({ + updateOne: { + filter: { _id: doc._id }, + update: { + $unset: { orgIds: '' }, + $set: { orgId: doc.orgIds[0] }, + }, + }, + }) - if (orgIds.length === 0 || !orgIds) { - console.log(`Skipping document with _id: ${doc._id} as it contains 'ALL' or is empty.`) - continue + // Process batch + if (batch.length >= BATCH_SIZE) { + await collection.bulkWrite(batch, { ordered: false }) + console.log(`Processed ${batch.length} docs in ${collectionName}`) + batch = [] } + } - let dataToBeUpdated = {} - dataToBeUpdated['orgId'] = orgIds[0] - await collection.updateOne({ _id: doc._id }, { $unset: { orgIds: '' }, $set: { ...dataToBeUpdated } }) + // Final remaining batch + if (batch.length > 0) { + await collection.bulkWrite(batch, { ordered: false }) + console.log(`Processed remaining ${batch.length} docs in ${collectionName}`) } - await dbClient.close() console.log(`Collection "${collectionName}" migration completed.`) } async function runMigration() { - // Example call, you can call modifyCollection with different collection names - await modifyCollection('entities') - await modifyCollection('entityTypes') - await modifyCollection('userRoleExtension') + try { + await dbClient.connect() + await modifyCollection('entities') + await modifyCollection('entityTypes') + await modifyCollection('userRoleExtension') + } catch (err) { + console.error('Migration failed:', err) + } finally { + await dbClient.close() + } } runMigration() From 862379a3b26c8cd2085c9bc4027226ddf35b5e75 Mon Sep 17 00:00:00 2001 From: borkarsaish65 Date: Tue, 3 Jun 2025 15:53:07 +0530 Subject: [PATCH 49/91] pr comment resolution --- src/controllers/v1/entities.js | 1 + src/controllers/v1/entityTypes.js | 1 + src/controllers/v1/userRoleExtension.js | 1 + src/generics/constants/api-responses.js | 4 +-- src/generics/helpers/utils.js | 33 ++++++++++++++++++++++++ src/generics/middleware/authenticator.js | 21 +++++++-------- 6 files changed, 47 insertions(+), 14 deletions(-) diff --git a/src/controllers/v1/entities.js b/src/controllers/v1/entities.js index 7d9c582..bf3d2d5 100644 --- a/src/controllers/v1/entities.js +++ b/src/controllers/v1/entities.js @@ -71,6 +71,7 @@ module.exports = class Entities extends Abstract { return new Promise(async (resolve, reject) => { try { // Calls the 'find' function from 'entitiesHelper' to retrieve entity data + req.body.query = UTILS.stripOrgIds(req.body.query) let entityData = await entitiesHelper.find( req.body.query, req.body.projection, diff --git a/src/controllers/v1/entityTypes.js b/src/controllers/v1/entityTypes.js index 5d8877c..b4451b9 100644 --- a/src/controllers/v1/entityTypes.js +++ b/src/controllers/v1/entityTypes.js @@ -110,6 +110,7 @@ module.exports = class EntityTypes extends Abstract { async find(req) { return new Promise(async (resolve, reject) => { try { + req.body.query = UTILS.stripOrgIds(req.body.query) // Call 'entityTypesHelper.list' to find entity types based on provided query, projection, and skipFields let result = await entityTypesHelper.list( req.body.query, diff --git a/src/controllers/v1/userRoleExtension.js b/src/controllers/v1/userRoleExtension.js index abe57db..b1234ee 100644 --- a/src/controllers/v1/userRoleExtension.js +++ b/src/controllers/v1/userRoleExtension.js @@ -197,6 +197,7 @@ module.exports = class userRoleExtension extends Abstract { find(req) { return new Promise(async (resolve, reject) => { try { + req.body.query = UTILS.stripOrgIds(req.body.query) // Call the helper function to find the user role extensions let userData = await userRoleExtensionHelper.find( req.body.query, diff --git a/src/generics/constants/api-responses.js b/src/generics/constants/api-responses.js index d388cc6..d78e7f8 100644 --- a/src/generics/constants/api-responses.js +++ b/src/generics/constants/api-responses.js @@ -74,6 +74,6 @@ module.exports = { NOT_VALID_ID_AND_EXTERNALID: 'NOT_VALID_ID_AND_EXTERNALID', TENANT_ID_MISSING_CODE: 'ERR_TENANT_ID_MISSING', TENANT_ID_MISSING_MESSAGE: 'Require tenantId in header', - INVALID_ROLE_FOR_CREATION_ERR: 'ERR_INVALID_ROLE', - INVALID_ROLE_FOR_CREATION_MSG: 'Invalid role provided for resource creation', + ROLE_PERMISSION_DENIED_ERR: 'ERR_INVALID_ROLE', + ROLE_PERMISSION_DENIED_MSG: 'Invalid role provided for resource creation', } diff --git a/src/generics/helpers/utils.js b/src/generics/helpers/utils.js index 4ba7691..07444bb 100644 --- a/src/generics/helpers/utils.js +++ b/src/generics/helpers/utils.js @@ -273,6 +273,37 @@ function convertMongoIds(query) { return query } +/** + * Strip orgIds from query object and log a warning. + * @function + * @name stripOrgIds + * @param {Object} query - The query object containing orgIds. + * @returns {Object} - The query object without orgIds. + * @deprecated orgIds is deprecated and should not be used in queries. + */ + +function stripOrgIds(query) { + const { orgIds, orgId, ...rest } = query + if (orgIds || orgId) { + console.warn('orgIds/orgId deprecated.') + } + return rest +} + +/** + * Convert an array of organization objects to an array of stringified org IDs. + * @function + * @name convertOrgIdsToString + * @param {Array<{code: number|string}>} array - Array of objects each containing a `code` property. + * @returns {string[]} - Array of stringified `code` values. + */ + +function convertOrgIdsToString(array) { + return array.map((data) => { + return data.code.toString() + }) +} + module.exports = { camelCaseToTitleCase: camelCaseToTitleCase, lowerCase: lowerCase, @@ -287,4 +318,6 @@ module.exports = { operatorValidation: operatorValidation, generateUniqueId: generateUniqueId, convertMongoIds: convertMongoIds, + stripOrgIds: stripOrgIds, + convertOrgIdsToString: convertOrgIdsToString, } diff --git a/src/generics/middleware/authenticator.js b/src/generics/middleware/authenticator.js index 1e06462..0005035 100644 --- a/src/generics/middleware/authenticator.js +++ b/src/generics/middleware/authenticator.js @@ -339,12 +339,11 @@ module.exports = async function (req, res, next, token = '') { validOrgIds = ['ALL'] } else { if ( - !orgDetails || - !orgDetails.success || - !orgDetails.data || - !(Object.keys(orgDetails.data).length > 0) || - !orgDetails.data.organizations || - !(orgDetails.data.organizations.length > 0) + !orgDetails?.success || + !orgDetails?.data || + Object.keys(orgDetails.data).length === 0 || + !Array.isArray(orgDetails.data.organizations) || + orgDetails.data.organizations.length === 0 ) { let errorObj = {} errorObj.errCode = CONSTANTS.apiResponses.ORG_DETAILS_FETCH_UNSUCCESSFUL_CODE @@ -353,10 +352,8 @@ module.exports = async function (req, res, next, token = '') { return { success: false, errorObj: errorObj } } - // convert the types of items to string - orgDetails.data.related_orgs = orgDetails.data.organizations.map((data) => { - return data.code.toString() - }) + orgDetails.data.related_orgs = UTILS.convertOrgIdsToString(orgDetails.data.related_orgs) + // aggregate valid orgids let relatedOrgIds = orgDetails.data.related_orgs @@ -515,8 +512,8 @@ module.exports = async function (req, res, next, token = '') { req.headers['tenantid'] = decodedToken.data.tenant_id.toString() req.headers['orgid'] = [decodedToken.data.organization_id.toString()] } else { - rspObj.errCode = CONSTANTS.apiResponses.INVALID_ROLE_FOR_CREATION_ERR - rspObj.errMsg = CONSTANTS.apiResponses.INVALID_ROLE_FOR_CREATION_MSG + rspObj.errCode = CONSTANTS.apiResponses.ROLE_PERMISSION_DENIED_ERR + rspObj.errMsg = CONSTANTS.apiResponses.ROLE_PERMISSION_DENIED_MSG return res.status(HTTP_STATUS_CODE['unauthorized'].status).send(respUtil(rspObj)) } From ba79f68cdca7d99fe130bcdd1e0415033efb65d1 Mon Sep 17 00:00:00 2001 From: borkarsaish65 Date: Tue, 3 Jun 2025 15:55:55 +0530 Subject: [PATCH 50/91] self review 2 --- src/controllers/v1/entityTypes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/v1/entityTypes.js b/src/controllers/v1/entityTypes.js index b4451b9..26fb12a 100644 --- a/src/controllers/v1/entityTypes.js +++ b/src/controllers/v1/entityTypes.js @@ -71,7 +71,7 @@ module.exports = class EntityTypes extends Abstract { // handle currentOrgOnly filter if (req.query['currentOrgOnly'] && req.query['currentOrgOnly'] == 'true') { organizationId = req.userDetails.userInformation.organizationId - query['orgIds'] = { $in: [organizationId] } + query['orgId'] = { $in: [organizationId] } } let result = await entityTypesHelper.list(query, ['name'], req.pageNo, req.pageSize, req.searchText) From ff2b2c84c414080cdc0e831621aaf8e86f5e943e Mon Sep 17 00:00:00 2001 From: borkarsaish65 Date: Tue, 3 Jun 2025 16:28:05 +0530 Subject: [PATCH 51/91] review comment 3 --- src/migrations/fixOrgIdsFromCollections.js | 62 +++++++++++++--------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/src/migrations/fixOrgIdsFromCollections.js b/src/migrations/fixOrgIdsFromCollections.js index 3ecb0b0..7a4174e 100644 --- a/src/migrations/fixOrgIdsFromCollections.js +++ b/src/migrations/fixOrgIdsFromCollections.js @@ -8,7 +8,10 @@ require('dotenv').config({ path: '../.env' }) const { MongoClient } = require('mongodb') const MONGODB_URL = process.env.MONGODB_URL -const DB = process.env.DB + +if (!MONGODB_URL) { + throw new Error('Missing MONGODB_URL or DB in environment variables') +} const dbClient = new MongoClient(MONGODB_URL) @@ -17,7 +20,7 @@ const BATCH_SIZE = 100 async function modifyCollection(collectionName) { console.log(`Starting migration for collection: ${collectionName}`) - const db = dbClient.db(DB) + const db = dbClient.db() const collection = db.collection(collectionName) const cursor = collection.find( @@ -32,29 +35,33 @@ async function modifyCollection(collectionName) { let batch = [] while (await cursor.hasNext()) { - console.log(`processing for collection: ${collectionName}`) - const doc = await cursor.next() - console.log(`Processing document with _id: ${doc._id}`) - if (!doc.orgIds || doc.orgIds.length === 0) { - console.log(`Skipping _id: ${doc._id} - empty orgIds`) - continue - } + try { + console.log(`processing for collection: ${collectionName}`) + const doc = await cursor.next() + console.log(`Processing document with _id: ${doc._id}`) + if (!doc.orgIds || doc.orgIds.length === 0) { + console.log(`Skipping _id: ${doc._id} - empty orgIds`) + continue + } - batch.push({ - updateOne: { - filter: { _id: doc._id }, - update: { - $unset: { orgIds: '' }, - $set: { orgId: doc.orgIds[0] }, + batch.push({ + updateOne: { + filter: { _id: doc._id }, + update: { + $unset: { orgIds: '' }, + $set: { orgId: doc.orgIds[0] }, + }, }, - }, - }) - - // Process batch - if (batch.length >= BATCH_SIZE) { - await collection.bulkWrite(batch, { ordered: false }) - console.log(`Processed ${batch.length} docs in ${collectionName}`) - batch = [] + }) + + // Process batch + if (batch.length >= BATCH_SIZE) { + await collection.bulkWrite(batch, { ordered: false }) + console.log(`Processed ${batch.length} docs in ${collectionName}`) + batch = [] + } + } catch (err) { + console.log(err, '<-- Error processing document') } } @@ -70,9 +77,12 @@ async function modifyCollection(collectionName) { async function runMigration() { try { await dbClient.connect() - await modifyCollection('entities') - await modifyCollection('entityTypes') - await modifyCollection('userRoleExtension') + const COLLECTIONS = ['entities', 'entityTypes', 'userRoleExtension'] + + for (const collection of COLLECTIONS) { + console.log('collection', collection) + await modifyCollection(collection) + } } catch (err) { console.error('Migration failed:', err) } finally { From 88823fd28f06888c3ffe8f5564e3fdaaf081b104 Mon Sep 17 00:00:00 2001 From: borkarsaish65 Date: Tue, 3 Jun 2025 16:28:50 +0530 Subject: [PATCH 52/91] review comment 3_1 --- src/migrations/fixOrgIdsFromCollections.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/migrations/fixOrgIdsFromCollections.js b/src/migrations/fixOrgIdsFromCollections.js index 7a4174e..2d8be49 100644 --- a/src/migrations/fixOrgIdsFromCollections.js +++ b/src/migrations/fixOrgIdsFromCollections.js @@ -80,7 +80,6 @@ async function runMigration() { const COLLECTIONS = ['entities', 'entityTypes', 'userRoleExtension'] for (const collection of COLLECTIONS) { - console.log('collection', collection) await modifyCollection(collection) } } catch (err) { From c7dd8a6f064396ed5b143f1be445e1c7de58a6ac Mon Sep 17 00:00:00 2001 From: prajwal Date: Tue, 3 Jun 2025 18:48:36 +0530 Subject: [PATCH 53/91] entities/add entities/update entities/bulkCreate entities/bulkUpdate apis updated --- src/module/entities/helper.js | 152 +++++++++++++++------------------- 1 file changed, 65 insertions(+), 87 deletions(-) diff --git a/src/module/entities/helper.js b/src/module/entities/helper.js index 244ee64..8374c11 100644 --- a/src/module/entities/helper.js +++ b/src/module/entities/helper.js @@ -1327,33 +1327,23 @@ module.exports = class UserProjectsHelper { // Convert the names in 'validatedChildHierarchy' to strings and assign them to 'childHierarchyPath' childHierarchyPath = validatedChildHierarchy.map(String) } - - const formattedTargetedEntityTypes = [] - if (singleEntity.targetedEntityTypes) { - for (const entityType of singleEntity.targetedEntityTypes) { - try { - const entityTypeDocument = await entityTypeQueries.findOne( - { name: entityType, tenantId: tenantId }, - { _id: 1 } - ) - - if (entityTypeDocument) { - formattedTargetedEntityTypes.push({ - entityType: entityType, - entityTypeId: entityTypeDocument._id.toString(), - }) - } else { - throw { - status: HTTP_STATUS_CODE.bad_request.status, - message: CONSTANTS.apiResponses.ENTITY_NOT_FOUND, - } - } - } catch (error) { - return reject(error) - } - } + if (singleEntity['targetedEntityTypes']) { + singleEntity.targetedEntityTypes = singleEntity.targetedEntityTypes.map((item) => item.trim()) } + const formattedTargetedEntityTypes = await entityTypeQueries.entityTypesDocument( + { + name: { $in: singleEntity.targetedEntityTypes }, + tenantId: tenantId, + }, + ['name', '_id'] + ) + formattedTargetedEntityTypes.forEach((entityType) => { + entityType['entityTypeId'] = entityType._id.toString() + entityType['entityType'] = entityType.name + delete entityType._id + delete entityType.name + }) singleEntity.targetedEntityTypes = formattedTargetedEntityTypes // Construct the entity document to be created @@ -1667,34 +1657,26 @@ module.exports = class UserProjectsHelper { // entityCreation['allowedRoles'] = await allowedRoles(singleEntity.allowedRoles) // delete singleEntity.allowedRoles // } - const formattedTargetedEntityTypes = [] + let formattedTargetedEntityTypes = [] if (singleEntity.targetedEntityTypes) { const entityTypesArray = singleEntity.targetedEntityTypes .replace(/^"(.*)"$/, '$1') // remove starting and ending quotes .split(',') .map((type) => type.trim()) - for (const entityType of entityTypesArray) { - try { - const entityTypeDocument = await entityTypeQueries.findOne( - { name: entityType, tenantId: tenantId }, - { _id: 1 } - ) - - if (entityTypeDocument) { - formattedTargetedEntityTypes.push({ - entityType: entityType, - entityTypeId: entityTypeDocument._id.toString(), - }) - } else { - throw { - status: HTTP_STATUS_CODE.bad_request.status, - message: CONSTANTS.apiResponses.ENTITY_NOT_FOUND, - } - } - } catch (error) { - return reject(error) - } - } + formattedTargetedEntityTypes = await entityTypeQueries.entityTypesDocument( + { + name: { $in: entityTypesArray }, + tenantId: tenantId, + }, + ['name', '_id'] + ) + + formattedTargetedEntityTypes.forEach((entityType) => { + entityType['entityTypeId'] = entityType._id.toString() + entityType['entityType'] = entityType.name + delete entityType._id + delete entityType.name + }) } singleEntity.targetedEntityTypes = formattedTargetedEntityTypes if (singleEntity.childHierarchyPath) { @@ -1850,28 +1832,26 @@ module.exports = class UserProjectsHelper { updateData['translations'] = translationFile[updateData['metaInformation.name']] } - if (entityCSVData.targetedEntityTypes) { - await Promise.all( - entityCSVData.targetedEntityTypes.map(async (entityTypeData) => { - // Validate that both entityType and entityTypeId exist in the entityType DB - let entityTypeFilterQuery = { - name: entityTypeData.entityType, - _id: ObjectId(entityTypeData.entityTypeId), - tenantId: tenantId, - } - let existingEntityType = await entityTypeQueries.findOne(entityTypeFilterQuery) - - if (!existingEntityType) { - // If any entityType is invalid, reject the request - throw { - status: HTTP_STATUS_CODE.bad_request.status, - message: `EntityType '${entityTypeData.entityType}' with ID '${entityTypeData.entityTypeId}' & tenantId ${tenantId} does not exist.`, - } - } - }) + let targetedEntityTypes = entityCSVData[0].targetedEntityTypes + .split(',') + .map((item) => item.trim()) + if (targetedEntityTypes.length > 0) { + const formattedTargetedEntityTypes = await entityTypeQueries.entityTypesDocument( + { + name: { $in: targetedEntityTypes }, + tenantId: tenantId, + }, + ['name', '_id'] ) - } + formattedTargetedEntityTypes.forEach((entityType) => { + entityType['entityTypeId'] = entityType._id.toString() + entityType['entityType'] = entityType.name + delete entityType._id + delete entityType.name + }) + updateData['metaInformation.targetedEntityTypes'] = formattedTargetedEntityTypes + } if (Object.keys(updateData).length > 0) { let updateEntity = await entitiesQueries.findOneAndUpdate( { _id: singleEntity['_SYSTEM_ID'], tenantId: tenantId }, @@ -1937,28 +1917,26 @@ module.exports = class UserProjectsHelper { } } } - if (bodyData['metaInformation.targetedEntityTypes']) { - await Promise.all( - bodyData['metaInformation.targetedEntityTypes'].map(async (entityTypeData) => { - // Validate that both entityType and entityTypeId exist in the entityType DB - let entityTypeFilterQuery = { - name: entityTypeData.entityType, - _id: ObjectId(entityTypeData.entityTypeId), - tenantId: tenantId, - } - let existingEntityType = await entityTypeQueries.findOne(entityTypeFilterQuery) - if (!existingEntityType) { - // If any entityType is invalid, reject the request - throw { - status: HTTP_STATUS_CODE.bad_request.status, - message: `EntityType '${entityTypeData.entityType}' with ID '${entityTypeData.entityTypeId}' & tenantId ${tenantId} does not exist.`, - } - } - }) + if (bodyData['targetedEntityTypes']) { + bodyData.targetedEntityTypes = bodyData.targetedEntityTypes.map((item) => item.trim()) + const formattedTargetedEntityTypes = await entityTypeQueries.entityTypesDocument( + { + name: { $in: bodyData.targetedEntityTypes }, + tenantId: tenantId, + }, + ['name', '_id'] ) - } + formattedTargetedEntityTypes.forEach((entityType) => { + entityType['entityTypeId'] = entityType._id.toString() + entityType['entityType'] = entityType.name + delete entityType._id + delete entityType.name + }) + delete bodyData.targetedEntityTypes + bodyData['metaInformation.targetedEntityTypes'] = formattedTargetedEntityTypes + } // Update the entity using findOneAndUpdate let entityInformation = await entitiesQueries.findOneAndUpdate( { _id: ObjectId(entityId), tenantId: tenantId }, From a7a196f9e19a06ae4a8098045425c329ff885e00 Mon Sep 17 00:00:00 2001 From: prajwal Date: Tue, 3 Jun 2025 19:08:40 +0530 Subject: [PATCH 54/91] updated entities model --- src/models/entities.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/models/entities.js b/src/models/entities.js index 60447c3..a38d52a 100644 --- a/src/models/entities.js +++ b/src/models/entities.js @@ -17,7 +17,6 @@ module.exports = { metaInformation: { externalId: { type: String, index: true }, name: { type: String, index: true }, - targetedEntityTypes: { type: Array, index: true }, }, childHierarchyPath: Array, userId: { From 3d32c8f495e8a7c1a9c34409bcc7eb9a43b802f5 Mon Sep 17 00:00:00 2001 From: borkarsaish65 Date: Tue, 3 Jun 2025 19:26:44 +0530 Subject: [PATCH 55/91] savepoint --- ...TargetedEntityTypesInEntitiesCollection.js | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/migrations/addTargetedEntityTypesInEntitiesCollection.js b/src/migrations/addTargetedEntityTypesInEntitiesCollection.js index 429f36f..c3eb1c4 100644 --- a/src/migrations/addTargetedEntityTypesInEntitiesCollection.js +++ b/src/migrations/addTargetedEntityTypesInEntitiesCollection.js @@ -35,22 +35,22 @@ let professionalSubRolesInfo = { 'principal-head-teacher': ['school'], 'vice-principal-asst-head-teacher': ['school'], 'head-teacher-incharge': ['school'], - 'teacher-educator-SCERT': ['state'], - 'teacher-educator-DIET': ['state'], - 'teacher-educator-IASE': ['state'], - 'teacher-educator-univ-deptt': ['state'], - 'teacher-educator-TEI': ['state'], - 'teacher-educator-SIET': ['state'], - 'teacher-educator-CTE': ['state'], - 'teacher-educator-BASIC': ['state'], - 'block-resource-centre-coordinator-BRCC': ['block'], - 'cluster-resource-centre-oordinator-CRCC': ['cluster'], - 'state-coordinator': ['state'], - 'district-coordinator': ['school'], - 'assistant-district-coordinator': ['district'], + 'teacher-educator-SCERT': ['school'], + 'teacher-educator-DIET': ['school'], + 'teacher-educator-IASE': ['school'], + 'teacher-educator-univ-deptt': ['school'], + 'teacher-educator-TEI': ['school'], + 'teacher-educator-SIET': ['school'], + 'teacher-educator-CTE': ['school'], + 'teacher-educator-BASIC': ['school'], + 'block-resource-centre-coordinator-BRCC': ['block', 'cluster', 'school'], + 'cluster-resource-centre-oordinator-CRCC': ['cluster', 'school'], + 'state-coordinator': ['state', 'district', 'block', 'cluster', 'school'], + 'district-coordinator': ['district', 'block', 'cluster', 'school'], + 'assistant-district-coordinator': ['district', 'block', 'cluster', 'school'], coordinator: ['school'], 'mentor-advisor': ['school'], - 'resource-person-state-district-block': ['state'], + 'resource-person-state-district-block': ['state', 'district', 'block', 'cluster', 'school'], 'shikshak-sankul': ['school'], 'principal-secretary-commissioner-secretary-school-education': ['school'], 'additional-secretary-commissioner-school-education': ['school'], @@ -58,20 +58,20 @@ let professionalSubRolesInfo = { 'assistant-commissioner': ['school'], 'additional-director': ['school'], 'director-public-instructions-elementary-secondary': ['school'], - 'project-director-SPD': ['state'], + 'project-director-SPD': ['state', 'district', 'block', 'cluster', 'school'], 'joint-director': ['school'], - 'assistant-state-project-director': ['state'], - 'additional-state-project-director': ['state'], + 'assistant-state-project-director': ['state', 'district', 'block', 'cluster', 'school'], + 'additional-state-project-director': ['state', 'district', 'block', 'cluster', 'school'], 'director-basic': ['school'], 'head-autonomous-organization': ['school'], - 'director-SCERT': ['state'], + 'director-SCERT': ['state', 'district', 'block', 'cluster', 'school'], 'principal-DIET': ['school'], 'collector-DM-DC': ['school'], - 'head-state-training-center': ['state'], + 'head-state-training-center': ['state', 'district', 'block', 'cluster', 'school'], 'education-officer': ['school'], - 'chief-education-officer': ['state'], - 'district-education-officer-DEO': ['district'], - 'block-ducation-fficer-BEO': ['block'], + 'chief-education-officer': ['state', 'district', 'block', 'cluster', 'school'], + 'district-education-officer-DEO': ['district', 'block', 'cluster', 'school'], + 'block-ducation-fficer-BEO': ['block', 'cluster', 'school'], 'MIS-coordinator': ['school'], 'subject-inspector': ['school'], 'evaluation-officer': ['school'], @@ -85,7 +85,7 @@ let professionalSubRolesInfo = { 'director-secondary-and-higher-secondary-education': ['school'], 'director-scheme': ['school'], 'director-balbharati': ['school'], - 'director-state-education-board': ['state'], + 'director-state-education-board': ['state', 'district', 'block', 'cluster', 'school'], } async function runMigration() { From f6f1fabc1e6d97e269211f3e77321f2139261923 Mon Sep 17 00:00:00 2001 From: borkarsaish65 Date: Tue, 3 Jun 2025 19:28:02 +0530 Subject: [PATCH 56/91] feat:script data fix --- ...TargetedEntityTypesInEntitiesCollection.js | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/migrations/addTargetedEntityTypesInEntitiesCollection.js b/src/migrations/addTargetedEntityTypesInEntitiesCollection.js index 429f36f..c3eb1c4 100644 --- a/src/migrations/addTargetedEntityTypesInEntitiesCollection.js +++ b/src/migrations/addTargetedEntityTypesInEntitiesCollection.js @@ -35,22 +35,22 @@ let professionalSubRolesInfo = { 'principal-head-teacher': ['school'], 'vice-principal-asst-head-teacher': ['school'], 'head-teacher-incharge': ['school'], - 'teacher-educator-SCERT': ['state'], - 'teacher-educator-DIET': ['state'], - 'teacher-educator-IASE': ['state'], - 'teacher-educator-univ-deptt': ['state'], - 'teacher-educator-TEI': ['state'], - 'teacher-educator-SIET': ['state'], - 'teacher-educator-CTE': ['state'], - 'teacher-educator-BASIC': ['state'], - 'block-resource-centre-coordinator-BRCC': ['block'], - 'cluster-resource-centre-oordinator-CRCC': ['cluster'], - 'state-coordinator': ['state'], - 'district-coordinator': ['school'], - 'assistant-district-coordinator': ['district'], + 'teacher-educator-SCERT': ['school'], + 'teacher-educator-DIET': ['school'], + 'teacher-educator-IASE': ['school'], + 'teacher-educator-univ-deptt': ['school'], + 'teacher-educator-TEI': ['school'], + 'teacher-educator-SIET': ['school'], + 'teacher-educator-CTE': ['school'], + 'teacher-educator-BASIC': ['school'], + 'block-resource-centre-coordinator-BRCC': ['block', 'cluster', 'school'], + 'cluster-resource-centre-oordinator-CRCC': ['cluster', 'school'], + 'state-coordinator': ['state', 'district', 'block', 'cluster', 'school'], + 'district-coordinator': ['district', 'block', 'cluster', 'school'], + 'assistant-district-coordinator': ['district', 'block', 'cluster', 'school'], coordinator: ['school'], 'mentor-advisor': ['school'], - 'resource-person-state-district-block': ['state'], + 'resource-person-state-district-block': ['state', 'district', 'block', 'cluster', 'school'], 'shikshak-sankul': ['school'], 'principal-secretary-commissioner-secretary-school-education': ['school'], 'additional-secretary-commissioner-school-education': ['school'], @@ -58,20 +58,20 @@ let professionalSubRolesInfo = { 'assistant-commissioner': ['school'], 'additional-director': ['school'], 'director-public-instructions-elementary-secondary': ['school'], - 'project-director-SPD': ['state'], + 'project-director-SPD': ['state', 'district', 'block', 'cluster', 'school'], 'joint-director': ['school'], - 'assistant-state-project-director': ['state'], - 'additional-state-project-director': ['state'], + 'assistant-state-project-director': ['state', 'district', 'block', 'cluster', 'school'], + 'additional-state-project-director': ['state', 'district', 'block', 'cluster', 'school'], 'director-basic': ['school'], 'head-autonomous-organization': ['school'], - 'director-SCERT': ['state'], + 'director-SCERT': ['state', 'district', 'block', 'cluster', 'school'], 'principal-DIET': ['school'], 'collector-DM-DC': ['school'], - 'head-state-training-center': ['state'], + 'head-state-training-center': ['state', 'district', 'block', 'cluster', 'school'], 'education-officer': ['school'], - 'chief-education-officer': ['state'], - 'district-education-officer-DEO': ['district'], - 'block-ducation-fficer-BEO': ['block'], + 'chief-education-officer': ['state', 'district', 'block', 'cluster', 'school'], + 'district-education-officer-DEO': ['district', 'block', 'cluster', 'school'], + 'block-ducation-fficer-BEO': ['block', 'cluster', 'school'], 'MIS-coordinator': ['school'], 'subject-inspector': ['school'], 'evaluation-officer': ['school'], @@ -85,7 +85,7 @@ let professionalSubRolesInfo = { 'director-secondary-and-higher-secondary-education': ['school'], 'director-scheme': ['school'], 'director-balbharati': ['school'], - 'director-state-education-board': ['state'], + 'director-state-education-board': ['state', 'district', 'block', 'cluster', 'school'], } async function runMigration() { From 0171379d35648115b38226a5887feea8f1ce42a2 Mon Sep 17 00:00:00 2001 From: prajwal Date: Tue, 3 Jun 2025 19:47:18 +0530 Subject: [PATCH 57/91] code optimisation --- src/models/entities.js | 1 + src/module/entities/helper.js | 97 ++++++++++++++--------------------- 2 files changed, 40 insertions(+), 58 deletions(-) diff --git a/src/models/entities.js b/src/models/entities.js index a38d52a..24e9e85 100644 --- a/src/models/entities.js +++ b/src/models/entities.js @@ -17,6 +17,7 @@ module.exports = { metaInformation: { externalId: { type: String, index: true }, name: { type: String, index: true }, + targetedEntityTypes: { type: Array }, }, childHierarchyPath: Array, userId: { diff --git a/src/module/entities/helper.js b/src/module/entities/helper.js index 8374c11..bd4eaed 100644 --- a/src/module/entities/helper.js +++ b/src/module/entities/helper.js @@ -1329,23 +1329,14 @@ module.exports = class UserProjectsHelper { } if (singleEntity['targetedEntityTypes']) { singleEntity.targetedEntityTypes = singleEntity.targetedEntityTypes.map((item) => item.trim()) + let targetedEntityTypes = await populateTargetedEntityTypesData( + singleEntity.targetedEntityTypes, + tenantId + ) + singleEntity.targetedEntityTypes = targetedEntityTypes + } else { + singleEntity.targetedEntityTypes = [] } - const formattedTargetedEntityTypes = await entityTypeQueries.entityTypesDocument( - { - name: { $in: singleEntity.targetedEntityTypes }, - tenantId: tenantId, - }, - ['name', '_id'] - ) - - formattedTargetedEntityTypes.forEach((entityType) => { - entityType['entityTypeId'] = entityType._id.toString() - entityType['entityType'] = entityType.name - delete entityType._id - delete entityType.name - }) - singleEntity.targetedEntityTypes = formattedTargetedEntityTypes - // Construct the entity document to be created let entityDoc = { entityTypeId: entityTypeDocument._id, @@ -1663,22 +1654,14 @@ module.exports = class UserProjectsHelper { .replace(/^"(.*)"$/, '$1') // remove starting and ending quotes .split(',') .map((type) => type.trim()) - formattedTargetedEntityTypes = await entityTypeQueries.entityTypesDocument( - { - name: { $in: entityTypesArray }, - tenantId: tenantId, - }, - ['name', '_id'] + formattedTargetedEntityTypes = await populateTargetedEntityTypesData( + entityTypesArray, + tenantId ) - - formattedTargetedEntityTypes.forEach((entityType) => { - entityType['entityTypeId'] = entityType._id.toString() - entityType['entityType'] = entityType.name - delete entityType._id - delete entityType.name - }) + singleEntity.targetedEntityTypes = formattedTargetedEntityTypes + } else { + singleEntity.targetedEntityTypes = [] } - singleEntity.targetedEntityTypes = formattedTargetedEntityTypes if (singleEntity.childHierarchyPath) { entityCreation['childHierarchyPath'] = JSON.parse(singleEntity['childHierarchyPath']) } @@ -1836,21 +1819,12 @@ module.exports = class UserProjectsHelper { .split(',') .map((item) => item.trim()) if (targetedEntityTypes.length > 0) { - const formattedTargetedEntityTypes = await entityTypeQueries.entityTypesDocument( - { - name: { $in: targetedEntityTypes }, - tenantId: tenantId, - }, - ['name', '_id'] + updateData['metaInformation.targetedEntityTypes'] = await populateTargetedEntityTypesData( + targetedEntityTypes, + tenantId ) - - formattedTargetedEntityTypes.forEach((entityType) => { - entityType['entityTypeId'] = entityType._id.toString() - entityType['entityType'] = entityType.name - delete entityType._id - delete entityType.name - }) - updateData['metaInformation.targetedEntityTypes'] = formattedTargetedEntityTypes + } else { + updateData['metaInformation.targetedEntityTypes'] = [] } if (Object.keys(updateData).length > 0) { let updateEntity = await entitiesQueries.findOneAndUpdate( @@ -1920,22 +1894,11 @@ module.exports = class UserProjectsHelper { if (bodyData['targetedEntityTypes']) { bodyData.targetedEntityTypes = bodyData.targetedEntityTypes.map((item) => item.trim()) - const formattedTargetedEntityTypes = await entityTypeQueries.entityTypesDocument( - { - name: { $in: bodyData.targetedEntityTypes }, - tenantId: tenantId, - }, - ['name', '_id'] + bodyData['metaInformation.targetedEntityTypes'] = await populateTargetedEntityTypesData( + bodyData.targetedEntityTypes, + tenantId ) - - formattedTargetedEntityTypes.forEach((entityType) => { - entityType['entityTypeId'] = entityType._id.toString() - entityType['entityType'] = entityType.name - delete entityType._id - delete entityType.name - }) delete bodyData.targetedEntityTypes - bodyData['metaInformation.targetedEntityTypes'] = formattedTargetedEntityTypes } // Update the entity using findOneAndUpdate let entityInformation = await entitiesQueries.findOneAndUpdate( @@ -2280,3 +2243,21 @@ function addTagsInEntities(entityMetaInformation) { } return entityMetaInformation } + +async function populateTargetedEntityTypesData(targetedEntityTypes, tenantId) { + const formattedTargetedEntityTypes = await entityTypeQueries.entityTypesDocument( + { + name: { $in: targetedEntityTypes }, + tenantId: tenantId, + }, + ['name', '_id'] + ) + + formattedTargetedEntityTypes.forEach((entityType) => { + entityType['entityTypeId'] = entityType._id.toString() + entityType['entityType'] = entityType.name + delete entityType._id + delete entityType.name + }) + return formattedTargetedEntityTypes +} From 929704200d3045cad6e936cc9f6f8b831761a714 Mon Sep 17 00:00:00 2001 From: prajwal Date: Tue, 3 Jun 2025 20:16:21 +0530 Subject: [PATCH 58/91] code optimisation --- src/module/entities/helper.js | 81 +++++++++++++++++------------------ 1 file changed, 39 insertions(+), 42 deletions(-) diff --git a/src/module/entities/helper.js b/src/module/entities/helper.js index bd4eaed..96956fc 100644 --- a/src/module/entities/helper.js +++ b/src/module/entities/helper.js @@ -1327,16 +1327,12 @@ module.exports = class UserProjectsHelper { // Convert the names in 'validatedChildHierarchy' to strings and assign them to 'childHierarchyPath' childHierarchyPath = validatedChildHierarchy.map(String) } - if (singleEntity['targetedEntityTypes']) { - singleEntity.targetedEntityTypes = singleEntity.targetedEntityTypes.map((item) => item.trim()) - let targetedEntityTypes = await populateTargetedEntityTypesData( - singleEntity.targetedEntityTypes, - tenantId - ) - singleEntity.targetedEntityTypes = targetedEntityTypes - } else { - singleEntity.targetedEntityTypes = [] - } + singleEntity.targetedEntityTypes = Array.isArray(singleEntity.targetedEntityTypes) + ? await populateTargetedEntityTypesData( + singleEntity.targetedEntityTypes.map((item) => item.trim()), + tenantId + ) + : [] // Construct the entity document to be created let entityDoc = { entityTypeId: entityTypeDocument._id, @@ -1648,20 +1644,19 @@ module.exports = class UserProjectsHelper { // entityCreation['allowedRoles'] = await allowedRoles(singleEntity.allowedRoles) // delete singleEntity.allowedRoles // } - let formattedTargetedEntityTypes = [] + let entityTypesArray = [] if (singleEntity.targetedEntityTypes) { - const entityTypesArray = singleEntity.targetedEntityTypes + entityTypesArray = singleEntity.targetedEntityTypes .replace(/^"(.*)"$/, '$1') // remove starting and ending quotes .split(',') .map((type) => type.trim()) - formattedTargetedEntityTypes = await populateTargetedEntityTypesData( - entityTypesArray, - tenantId - ) - singleEntity.targetedEntityTypes = formattedTargetedEntityTypes - } else { - singleEntity.targetedEntityTypes = [] } + + singleEntity.targetedEntityTypes = + Array.isArray(entityTypesArray) && entityTypesArray.length > 0 + ? await populateTargetedEntityTypesData(entityTypesArray, tenantId) + : [] + if (singleEntity.childHierarchyPath) { entityCreation['childHierarchyPath'] = JSON.parse(singleEntity['childHierarchyPath']) } @@ -1818,14 +1813,12 @@ module.exports = class UserProjectsHelper { let targetedEntityTypes = entityCSVData[0].targetedEntityTypes .split(',') .map((item) => item.trim()) - if (targetedEntityTypes.length > 0) { - updateData['metaInformation.targetedEntityTypes'] = await populateTargetedEntityTypesData( - targetedEntityTypes, - tenantId - ) - } else { - updateData['metaInformation.targetedEntityTypes'] = [] - } + + updateData['metaInformation.targetedEntityTypes'] = + Array.isArray(targetedEntityTypes) && targetedEntityTypes.length > 0 + ? await populateTargetedEntityTypesData(targetedEntityTypes, tenantId) + : [] + if (Object.keys(updateData).length > 0) { let updateEntity = await entitiesQueries.findOneAndUpdate( { _id: singleEntity['_SYSTEM_ID'], tenantId: tenantId }, @@ -2245,19 +2238,23 @@ function addTagsInEntities(entityMetaInformation) { } async function populateTargetedEntityTypesData(targetedEntityTypes, tenantId) { - const formattedTargetedEntityTypes = await entityTypeQueries.entityTypesDocument( - { - name: { $in: targetedEntityTypes }, - tenantId: tenantId, - }, - ['name', '_id'] - ) - - formattedTargetedEntityTypes.forEach((entityType) => { - entityType['entityTypeId'] = entityType._id.toString() - entityType['entityType'] = entityType.name - delete entityType._id - delete entityType.name - }) - return formattedTargetedEntityTypes + try { + const formattedTargetedEntityTypes = await entityTypeQueries.entityTypesDocument( + { + name: { $in: targetedEntityTypes }, + tenantId: tenantId, + }, + ['name', '_id'] + ) + + formattedTargetedEntityTypes.forEach((entityType) => { + entityType['entityTypeId'] = entityType._id.toString() + entityType['entityType'] = entityType.name + delete entityType._id + delete entityType.name + }) + return formattedTargetedEntityTypes + } catch (err) { + console.log(err) + } } From c891b8b63632009ef02bb37f50b594192698cd56 Mon Sep 17 00:00:00 2001 From: prajwal Date: Tue, 3 Jun 2025 20:19:35 +0530 Subject: [PATCH 59/91] code optimisations --- src/module/entities/helper.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/module/entities/helper.js b/src/module/entities/helper.js index 96956fc..3e4796b 100644 --- a/src/module/entities/helper.js +++ b/src/module/entities/helper.js @@ -2255,6 +2255,10 @@ async function populateTargetedEntityTypesData(targetedEntityTypes, tenantId) { }) return formattedTargetedEntityTypes } catch (err) { - console.log(err) + console.error('Error populating targeted entity types:', err) + return { + succes: false, + message: err.message, + } } } From 85d29573845de5da53efad0d7e64cc3d25dd3b79 Mon Sep 17 00:00:00 2001 From: prajwal Date: Tue, 3 Jun 2025 20:24:36 +0530 Subject: [PATCH 60/91] code optimisations --- src/module/entities/helper.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/module/entities/helper.js b/src/module/entities/helper.js index 3e4796b..d32918c 100644 --- a/src/module/entities/helper.js +++ b/src/module/entities/helper.js @@ -2255,10 +2255,6 @@ async function populateTargetedEntityTypesData(targetedEntityTypes, tenantId) { }) return formattedTargetedEntityTypes } catch (err) { - console.error('Error populating targeted entity types:', err) - return { - succes: false, - message: err.message, - } + return [] } } From fb91d32d4e41ee0d6f2fd5d656f6a2d38c1122de Mon Sep 17 00:00:00 2001 From: borkarsaish65 Date: Tue, 3 Jun 2025 20:32:25 +0530 Subject: [PATCH 61/91] removed console.log --- src/module/entityTypes/helper.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/module/entityTypes/helper.js b/src/module/entityTypes/helper.js index fc957a3..58c8775 100644 --- a/src/module/entityTypes/helper.js +++ b/src/module/entityTypes/helper.js @@ -428,8 +428,6 @@ module.exports = class UserProjectsHelper { ] } - console.log(bodyQuery, '<--bodyQuery') - // Retrieve entity type data based on the provided query and projection const result = await entityTypeQueries.getAggregate(aggregateData) return resolve({ From 9075d9b6fb6ace89313d62344a0d03483c5ea81a Mon Sep 17 00:00:00 2001 From: priyanka-TL Date: Thu, 5 Jun 2025 20:47:59 +0530 Subject: [PATCH 62/91] readme --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index ba78c7d..9e6574b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ # elevate-entity-service + Elevate entity service +Repository for managing entities data + # entity-management From b7a55c71da2bd2e7a0790c1422be306a45c8c542 Mon Sep 17 00:00:00 2001 From: priyanka-TL Date: Fri, 6 Jun 2025 16:25:46 +0530 Subject: [PATCH 63/91] updated --- src/controllers/v1/entities.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/controllers/v1/entities.js b/src/controllers/v1/entities.js index 2542b4d..a8c5ffa 100644 --- a/src/controllers/v1/entities.js +++ b/src/controllers/v1/entities.js @@ -68,6 +68,7 @@ module.exports = class Entities extends Abstract { */ find(req) { + console.log('Reached in find controller') return new Promise(async (resolve, reject) => { try { // Calls the 'find' function from 'entitiesHelper' to retrieve entity data From f92b95b913fd88eac7602437f49ab95d2ff131a3 Mon Sep 17 00:00:00 2001 From: priyanka-TL Date: Fri, 6 Jun 2025 18:30:05 +0530 Subject: [PATCH 64/91] script for data deletion --- src/migrations/cleanUpEntityData.js | 76 +++++++++++++++++++++++++++++ src/migrations/readme | 33 +++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 src/migrations/cleanUpEntityData.js create mode 100644 src/migrations/readme diff --git a/src/migrations/cleanUpEntityData.js b/src/migrations/cleanUpEntityData.js new file mode 100644 index 0000000..127a95f --- /dev/null +++ b/src/migrations/cleanUpEntityData.js @@ -0,0 +1,76 @@ +const MongoClient = require('mongodb').MongoClient +require('dotenv').config() + +async function deleteEntitiesInBatches(mongoUrl) { + const batchSize = 1000 // Configurable batch size + const entityTypes = ['state', 'district', 'block', 'cluster', 'school'] + + // Validate MongoDB URL + if (!mongoUrl) { + console.error('Error: MongoDB URL must be provided as a command-line argument or in .env as MONGODB_URL') + process.exit(1) + } + + let client + + try { + // Connect to MongoDB + client = await MongoClient.connect(mongoUrl, { useNewUrlParser: true, useUnifiedTopology: true }) + console.log('Connected to MongoDB') + + const db = client.db() // Use default database from URL + const collection = db.collection('entities') + + // Count total matching documents + const totalDocs = await collection.countDocuments({ entityType: { $in: entityTypes } }) + console.log(`Total documents to delete: ${totalDocs}`) + + if (totalDocs === 0) { + console.log('No documents found matching the criteria. Exiting.') + return + } + + // Delete in batches + let deletedCount = 0 + while (deletedCount < totalDocs) { + // Find a batch of document IDs to delete + const batchDocs = await collection + .find({ entityType: { $in: entityTypes } }) + .limit(batchSize) + .project({ _id: 1 }) + .toArray() + + if (batchDocs.length === 0) { + break // No more documents to delete + } + + // Extract IDs for deletion + const batchIds = batchDocs.map((doc) => doc._id) + + // Delete documents by IDs + const batchResult = await collection.deleteMany({ _id: { $in: batchIds } }) + const batchDeleted = batchResult.deletedCount + deletedCount += batchDeleted + console.log(`Deleted ${batchDeleted} documents in this batch. Total deleted: ${deletedCount}`) + } + + console.log(`Deletion complete. Total documents deleted: ${deletedCount}`) + } catch (error) { + console.error('Error during deletion:', error.message) + process.exit(1) + } finally { + if (client) { + await client.close() + console.log('MongoDB connection closed') + } + } +} + +// Get MongoDB URL from command-line argument or environment variable +const mongoUrl = process.argv[2] || process.env.MONGODB_URL + +// Run the script +deleteEntitiesInBatches(mongoUrl).catch((error) => { + console.error('Script failed:', error.message) + process.exit(1) +}) diff --git a/src/migrations/readme b/src/migrations/readme new file mode 100644 index 0000000..2b739b5 --- /dev/null +++ b/src/migrations/readme @@ -0,0 +1,33 @@ +# MongoDB Entity Data Cleanup Script + +This Node.js script deletes all documents from the MongoDB `entities` collection where `entityType` is one of `state`, `district`, `block`, `cluster`, or `school`. It processes deletions in batches for efficiency and supports MongoDB 4.x with the MongoDB Node.js driver v3.x. The script accepts a MongoDB URL as a command-line argument or falls back to an environment variable. + +## Prerequisites + +- **Node.js**: Version 14 or later. +- **MongoDB Server**: Version 4.x (e.g., 4.0 or 4.2). +- **NPM Packages**: `mongodb@3.6.12`, `dotenv`. +- **MongoDB URL**: A valid connection string (e.g., `mongodb://localhost:27017/elevate-entity`). + +## Usage + +The script can be executed **outside** or **inside** a Docker container. It deletes documents in batches of 1000 (configurable) and logs progress. + +### Option 1: Run Outside Docker + +1. Save the script as `cleanUpEntityData.js` (provided in the repository or separately). + +2. Run the script with a MongoDB URL as a command-line argument: + ```bash + node cleanUpEntityData.js mongodb://localhost:27017/prod-saas-elevate-entity + ``` + +### Option 2: Run Inside Docker + +1. Save the script as `cleanUpEntityData.js` inside entity service docker container + +2. Run the container with the MongoDB URL as an argument: + + ```bash + node cleanUpEntityData.js + ``` From 56827a340127f02ee4919d7060db8bf3bfaede2a Mon Sep 17 00:00:00 2001 From: priyanka-TL Date: Fri, 6 Jun 2025 19:43:07 +0530 Subject: [PATCH 65/91] moved --- src/{migrations => scripts}/cleanUpEntityData.js | 0 src/{migrations => scripts}/readme | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/{migrations => scripts}/cleanUpEntityData.js (100%) rename src/{migrations => scripts}/readme (100%) diff --git a/src/migrations/cleanUpEntityData.js b/src/scripts/cleanUpEntityData.js similarity index 100% rename from src/migrations/cleanUpEntityData.js rename to src/scripts/cleanUpEntityData.js diff --git a/src/migrations/readme b/src/scripts/readme similarity index 100% rename from src/migrations/readme rename to src/scripts/readme From c4d0a7371c2de71e07d0b604d4f3bb829d24c0dd Mon Sep 17 00:00:00 2001 From: prajwal Date: Wed, 18 Jun 2025 18:08:49 +0530 Subject: [PATCH 66/91] authenticator file changes --- src/generics/middleware/authenticator.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/generics/middleware/authenticator.js b/src/generics/middleware/authenticator.js index 0005035..a86006a 100644 --- a/src/generics/middleware/authenticator.js +++ b/src/generics/middleware/authenticator.js @@ -352,8 +352,9 @@ module.exports = async function (req, res, next, token = '') { return { success: false, errorObj: errorObj } } - orgDetails.data.related_orgs = UTILS.convertOrgIdsToString(orgDetails.data.related_orgs) - + orgDetails.data.related_orgs = orgDetails.data.organizations.map((data) => { + return data.code.toString() + }) // aggregate valid orgids let relatedOrgIds = orgDetails.data.related_orgs From 8adb9fe004a42b003088e5f212d7e7ebc191c8bc Mon Sep 17 00:00:00 2001 From: vishnu Date: Thu, 19 Jun 2025 16:39:41 +0530 Subject: [PATCH 67/91] evn variable config updated --- src/.env.sample | 7 +++++-- src/envVariables.js | 9 +++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/.env.sample b/src/.env.sample index 25e49f4..e137e98 100644 --- a/src/.env.sample +++ b/src/.env.sample @@ -19,5 +19,8 @@ IS_AUTH_TOKEN_BEARER=false AUTH_METHOD = native #or keycloak_public_key KEYCLOAK_PUBLIC_KEY_PATH = path to the pem/secret file -ADMIN_TOKEN_HEADER_NAME = admin-access-token // admin access token header name -ADMIN_ACCESS_TOKEN = ivopeiovcie-----------lvkkdvkdm // admin access token \ No newline at end of file +ADMIN_TOKEN_HEADER_NAME = admin-access-token // admin access token header name +ADMIN_ACCESS_TOKEN = ivopeiovcie-----------lvkkdvkdm // admin access token + +INTERFACE_SERVICE_URL = http://localhost:5000/interface-management // interface service url +USER_SERVICE_BASE_URL = user // user service base url \ No newline at end of file diff --git a/src/envVariables.js b/src/envVariables.js index 3cfa319..65f7d5c 100644 --- a/src/envVariables.js +++ b/src/envVariables.js @@ -56,6 +56,15 @@ let enviromentVariables = { message: 'Required admin access token', optional: false, }, + INTERFACE_SERVICE_URL: { + message: 'Required interface service url', + optional: false, + }, + USER_SERVICE_BASE_URL: { + message: 'Required user service base url', + optional: true, + default: '/user', + }, } let success = true From aeb19c87c5f4f2d9c03058391e912a368491dd60 Mon Sep 17 00:00:00 2001 From: MallanagoudaB Date: Wed, 25 Jun 2025 21:43:44 +0530 Subject: [PATCH 68/91] ReadMe-For-Entities-Mapping --- src/entityCreation/README.md | 141 ++++++++++++++++++++++++++ src/entityCreation/removeDuplicate.js | 35 +++++++ src/entityCreation/splitEntities.js | 42 ++++++++ 3 files changed, 218 insertions(+) create mode 100644 src/entityCreation/README.md create mode 100644 src/entityCreation/removeDuplicate.js create mode 100644 src/entityCreation/splitEntities.js diff --git a/src/entityCreation/README.md b/src/entityCreation/README.md new file mode 100644 index 0000000..ab802f8 --- /dev/null +++ b/src/entityCreation/README.md @@ -0,0 +1,141 @@ +# ๐Ÿงฑ Entity Management - Creation Flow + +This guide provides step-by-step instructions to manage entities in the SAAS platform across different roles and environments. + +--- + +## ๐Ÿ” Auth Keys & Tokens + +### ๐Ÿ”ธ Org Admin - QA + +| Key | Value | +| ----------------------- | ------------------------- | +| `internal-access-token` | `{internal-access-token}` | +| `X-auth-token` | `{Token}` | + +### ๐Ÿ”ธ Super Admin - QA + +| Key | Value | +| ----------------------- | ------------------------- | +| `internal-access-token` | `{internal-access-token}` | +| `X-auth-token` | `{Token}` | +| `admin-auth-token` | `{admin-auth-token}` | +| `tenantId` | `shikshagraha` | +| `orgId` | `blr` | + +--- + +## ๐ŸŒ Origin URLs (Per Organization) + +| Organization | Origin URL | +| ------------ | ------------------------------- | +| Shikshalokam | `shikshalokam-qa.tekdinext.com` | +| Shikshagraha | `shikshagrah-qa.tekdinext.com` | +| Super Admin | `default-qa.tekdinext.com` | + +--- + +## ๐Ÿ”‘ Login API + +
+Login API + +```bash +curl --location 'https://saas-qa.tekdinext.com/user/v1/account/login' \ +--header 'Content-Type: application/json' \ +--header 'origin: shikshalokam-qa.tekdinext.com' \ +--data-raw '{ + "identifier": "orgadmin@sl.com", + "password": "PASSword###11" +}' +``` + +
+ +--- + +**NOTE**: If you are an Organization Admin, please ensure that the headers include the values specified under the Organization Admin column. +If you are a Super Admin, please include the corresponding values from the Super Admin column in the request headers. + +## ๐Ÿงฑ Add Entity Type + +
+Add Entity Type API + +```bash +curl --location 'https://saas-qa.tekdinext.com/entity-management/v1/entityTypes/create' \ +--header 'internal-access-token: {internal-access-token}' \ +--header 'content-type: application/json' \ +--header 'X-auth-token: {{tokenToPass}}' \ +--header 'admin-auth-token: {admin-auth-token}' \ +--header 'tenantId: shikshagraha' \ +--header 'orgid: blr' \ +--data '{ + "name": "professional_role", + "registryDetails": { + "name": "schoolRegistry" + }, + "isObservable": true, + "toBeMappedToParentEntities": true +}' +``` + +
+ +--- + +## ๐Ÿซ Bulk Upload Entities + +**CSV File**: [Karnataka School Upload CSV](https://drive.google.com/file/d/1SwOh11gmhehhrKH7SygA40DpYRE6IIjI/view) + +
+Bulk Upload API + +```bash +curl --location 'https://saas-qa.tekdinext.com/entity-management/v1/entities/bulkCreate?type=school' \ +--header 'internal-access-token: {internal-access-token}' \ +--header 'content-type: multipart/form-data' \ +--header 'x-auth-token: {{TokenToPass}}' \ +--form 'entities=@"/home/user4/Downloads/Karnata-upload data SG prod - schoolUpload.csv"' +``` + +
+ +--- + +## ๐Ÿงพ Generate Mapping CSV + +**CSV File**: [CSV to Generate Mapping Upload CSV ](https://drive.google.com/file/d/1n9pFGfZKaj77OBXfsDnwL5WEOHzpq6jr/view?usp=sharing) + +
+Generate Mapping CSV API + +```bash +curl --location 'https://saas-qa.tekdinext.com/admin-entity-management/v1/entities/createMappingCsv' \ +--header 'x-auth-token: {{TokenToPass}}' \ +--header 'content-type: multipart/form-data' \ +--header 'internal-access-token: {internal-access-token}' \ +--form 'entityCSV=@"/home/user4/Downloads/chunk_0.csv"' +``` + +
+ +--- + +## ๐Ÿ”— Upload Entity Mapping + +**CSV File**: [Mapping Upload CSV](https://drive.google.com/file/d/1SVvi-F0y2YcwNfBpAOYzMZVeh4TbJCxd/view?usp=sharing) + +**Note**: Please ensure that the result CSV generated from the createMappingCsv function is passed accordingly. + +
+Mapping Upload API + +```bash +curl --location 'https://saas-qa.tekdinext.com/entity-management/v1/entities/mappingUpload' \ +--header 'internal-access-token: {internal-access-token}' \ +--header 'x-auth-token: {{TokenToPass}}' \ +--form 'entityMap=@"/home/user4/Downloads/base64-to-csv-converter (8).csv"' +``` + +
diff --git a/src/entityCreation/removeDuplicate.js b/src/entityCreation/removeDuplicate.js new file mode 100644 index 0000000..4b99510 --- /dev/null +++ b/src/entityCreation/removeDuplicate.js @@ -0,0 +1,35 @@ +const fs = require('fs') +const csv = require('csv-parser') +const createCsvWriter = require('csv-writer').createObjectCsvWriter + +const inputFilePath = 'input.csv' // Your input CSV +const outputFilePath = 'output_unique.csv' // Output with unique rows + +const seen = new Set() +const uniqueRows = [] + +fs.createReadStream(inputFilePath) + .pipe(csv()) + .on('data', (row) => { + const parentEntityId = row['parentEntityId'].trim() + const childEntityId = row['childEntityId'].trim() + const key = `${parentEntityId}-${childEntityId}` + + if (!seen.has(key)) { + seen.add(key) + uniqueRows.push({ parentEntityId, childEntityId }) + } + }) + .on('end', () => { + const csvWriter = createCsvWriter({ + path: outputFilePath, + header: [ + { id: 'parentEntityId', title: 'parentEntityId' }, + { id: 'childEntityId', title: 'childEntityId' }, + ], + }) + + csvWriter.writeRecords(uniqueRows).then(() => { + console.log(`โœ… Deduplicated data written to: ${outputFilePath}`) + }) + }) diff --git a/src/entityCreation/splitEntities.js b/src/entityCreation/splitEntities.js new file mode 100644 index 0000000..964a173 --- /dev/null +++ b/src/entityCreation/splitEntities.js @@ -0,0 +1,42 @@ +const fs = require('fs') +const csv = require('csv-parser') +const { createObjectCsvWriter } = require('csv-writer') + +const inputFilePath = 'input3.csv' // Input file name +const rowsPerFile = 5000 // Split size +const allRows = [] + +let headers = [] + +// Step 1: Read CSV data +fs.createReadStream(inputFilePath) + .pipe(csv()) + .on('headers', (csvHeaders) => { + // Ensure fixed header names + headers = ['parentEntiyId', 'childEntityId'].map((header) => ({ + id: header, + title: header, + })) + }) + .on('data', (row) => { + // Optional: only keep required fields to avoid extra columns + allRows.push({ + parentEntiyId: row.parentEntiyId, + childEntityId: row.childEntityId, + }) + }) + .on('end', async () => { + const totalFiles = Math.ceil(allRows.length / rowsPerFile) + + for (let i = 0; i < totalFiles; i++) { + const chunk = allRows.slice(i * rowsPerFile, (i + 1) * rowsPerFile) + + const csvWriter = createObjectCsvWriter({ + path: `data${i + 1}.csv`, + header: headers, + }) + + await csvWriter.writeRecords(chunk) + console.log(`data${i + 1}.csv written with ${chunk.length} records`) + } + }) From 0df699982b027d7fa93867c17b388b88383825e9 Mon Sep 17 00:00:00 2001 From: MallanagoudaB Date: Thu, 26 Jun 2025 11:36:54 +0530 Subject: [PATCH 69/91] removed-cred --- src/entityCreation/README.md | 14 +++++++------- src/entityCreation/splitEntities.js | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/entityCreation/README.md b/src/entityCreation/README.md index ab802f8..37ef37e 100644 --- a/src/entityCreation/README.md +++ b/src/entityCreation/README.md @@ -41,12 +41,12 @@ This guide provides step-by-step instructions to manage entities in the SAAS pla Login API ```bash -curl --location 'https://saas-qa.tekdinext.com/user/v1/account/login' \ +curl --location '{{baseURL}}/user/v1/account/login' \ --header 'Content-Type: application/json' \ --header 'origin: shikshalokam-qa.tekdinext.com' \ --data-raw '{ - "identifier": "orgadmin@sl.com", - "password": "PASSword###11" + "identifier": "email/phone", + "password": "password" }' ``` @@ -63,7 +63,7 @@ If you are a Super Admin, please include the corresponding values from the Super Add Entity Type API ```bash -curl --location 'https://saas-qa.tekdinext.com/entity-management/v1/entityTypes/create' \ +curl --location '{{baseURL}}/entity-management/v1/entityTypes/create' \ --header 'internal-access-token: {internal-access-token}' \ --header 'content-type: application/json' \ --header 'X-auth-token: {{tokenToPass}}' \ @@ -92,7 +92,7 @@ curl --location 'https://saas-qa.tekdinext.com/entity-management/v1/entityTypes/ Bulk Upload API ```bash -curl --location 'https://saas-qa.tekdinext.com/entity-management/v1/entities/bulkCreate?type=school' \ +curl --location '{{baseURL}}/entity-management/v1/entities/bulkCreate?type=school' \ --header 'internal-access-token: {internal-access-token}' \ --header 'content-type: multipart/form-data' \ --header 'x-auth-token: {{TokenToPass}}' \ @@ -111,7 +111,7 @@ curl --location 'https://saas-qa.tekdinext.com/entity-management/v1/entities/bul Generate Mapping CSV API ```bash -curl --location 'https://saas-qa.tekdinext.com/admin-entity-management/v1/entities/createMappingCsv' \ +curl --location '{{baseURL}}/entity-management/v1/entities/createMappingCsv' \ --header 'x-auth-token: {{TokenToPass}}' \ --header 'content-type: multipart/form-data' \ --header 'internal-access-token: {internal-access-token}' \ @@ -132,7 +132,7 @@ curl --location 'https://saas-qa.tekdinext.com/admin-entity-management/v1/entiti Mapping Upload API ```bash -curl --location 'https://saas-qa.tekdinext.com/entity-management/v1/entities/mappingUpload' \ +curl --location '{{baseURL}}/entity-management/v1/entities/mappingUpload' \ --header 'internal-access-token: {internal-access-token}' \ --header 'x-auth-token: {{TokenToPass}}' \ --form 'entityMap=@"/home/user4/Downloads/base64-to-csv-converter (8).csv"' diff --git a/src/entityCreation/splitEntities.js b/src/entityCreation/splitEntities.js index 964a173..5d9ea49 100644 --- a/src/entityCreation/splitEntities.js +++ b/src/entityCreation/splitEntities.js @@ -2,7 +2,7 @@ const fs = require('fs') const csv = require('csv-parser') const { createObjectCsvWriter } = require('csv-writer') -const inputFilePath = 'input3.csv' // Input file name +const inputFilePath = 'input.csv' // Input file name const rowsPerFile = 5000 // Split size const allRows = [] From a776c7f3cb07590c3fd41dd40d979479f082b85e Mon Sep 17 00:00:00 2001 From: MallanagoudaB Date: Thu, 26 Jun 2025 16:21:52 +0530 Subject: [PATCH 70/91] added-dic --- .../README.md | 44 ++++++++++++++++--- .../utilityScripts}/removeDuplicate.js | 0 .../utilityScripts}/splitEntities.js | 0 3 files changed, 37 insertions(+), 7 deletions(-) rename src/{entityCreation => entityTypeAndEntityOnboarding}/README.md (57%) rename src/{entityCreation => entityTypeAndEntityOnboarding/utilityScripts}/removeDuplicate.js (100%) rename src/{entityCreation => entityTypeAndEntityOnboarding/utilityScripts}/splitEntities.js (100%) diff --git a/src/entityCreation/README.md b/src/entityTypeAndEntityOnboarding/README.md similarity index 57% rename from src/entityCreation/README.md rename to src/entityTypeAndEntityOnboarding/README.md index 37ef37e..bc4514b 100644 --- a/src/entityCreation/README.md +++ b/src/entityTypeAndEntityOnboarding/README.md @@ -1,11 +1,13 @@ # ๐Ÿงฑ Entity Management - Creation Flow -This guide provides step-by-step instructions to manage entities in the SAAS platform across different roles and environments. +This guide provides comprehensive and step-by-step instructions for managing entities within the SAAS platform, tailored to different user roles and environments. It covers authentication, creation, and mapping of entities to ensure a seamless onboarding and operational experience. --- ## ๐Ÿ” Auth Keys & Tokens +Authentication headers required for API calls, based on user roles and the environment. + ### ๐Ÿ”ธ Org Admin - QA | Key | Value | @@ -27,6 +29,8 @@ This guide provides step-by-step instructions to manage entities in the SAAS pla ## ๐ŸŒ Origin URLs (Per Organization) +Domain URLs to be passed as the `origin` header during login requests. + | Organization | Origin URL | | ------------ | ------------------------------- | | Shikshalokam | `shikshalokam-qa.tekdinext.com` | @@ -37,6 +41,8 @@ This guide provides step-by-step instructions to manage entities in the SAAS pla ## ๐Ÿ”‘ Login API +Use this API to authenticate the user and generate a session token (`X-auth-token`). The token is mandatory for all secured API requests. +
Login API @@ -54,11 +60,17 @@ curl --location '{{baseURL}}/user/v1/account/login' \ --- -**NOTE**: If you are an Organization Admin, please ensure that the headers include the values specified under the Organization Admin column. -If you are a Super Admin, please include the corresponding values from the Super Admin column in the request headers. +**NOTE**: + +- If you are an **Organization Admin**, please ensure that the headers match the values listed under _Org Admin - QA_. +- If you are a **Super Admin**, use the credentials and headers mentioned under _Super Admin - QA_. + +--- ## ๐Ÿงฑ Add Entity Type +Use this API to create a new entity type in the system. An entity type represents a category like `school`, `cluster`, `block`, etc. +
Add Entity Type API @@ -86,7 +98,9 @@ curl --location '{{baseURL}}/entity-management/v1/entityTypes/create' \ ## ๐Ÿซ Bulk Upload Entities -**CSV File**: [Karnataka School Upload CSV](https://drive.google.com/file/d/1SwOh11gmhehhrKH7SygA40DpYRE6IIjI/view) +Use this API to bulk create entities of a specific type (e.g., schools, teachers) by uploading a formatted CSV file. + +๐Ÿ“„ **CSV File**: [Karnataka School Upload CSV](https://drive.google.com/file/d/1SwOh11gmhehhrKH7SygA40DpYRE6IIjI/view)
Bulk Upload API @@ -105,7 +119,9 @@ curl --location '{{baseURL}}/entity-management/v1/entities/bulkCreate?type=schoo ## ๐Ÿงพ Generate Mapping CSV -**CSV File**: [CSV to Generate Mapping Upload CSV ](https://drive.google.com/file/d/1n9pFGfZKaj77OBXfsDnwL5WEOHzpq6jr/view?usp=sharing) +This API helps generate a base mapping CSV from the bulk uploaded entity data. The generated CSV will be used to create mappings between parent and child entities. + +๐Ÿ“„ **CSV File**: [Download Template CSV](https://drive.google.com/file/d/1n9pFGfZKaj77OBXfsDnwL5WEOHzpq6jr/view?usp=sharing)
Generate Mapping CSV API @@ -124,9 +140,11 @@ curl --location '{{baseURL}}/entity-management/v1/entities/createMappingCsv' \ ## ๐Ÿ”— Upload Entity Mapping -**CSV File**: [Mapping Upload CSV](https://drive.google.com/file/d/1SVvi-F0y2YcwNfBpAOYzMZVeh4TbJCxd/view?usp=sharing) +This API maps child entities to their respective parent entities using the result CSV generated from the previous step (`createMappingCsv`). + +๐Ÿ“„ **CSV File**: [Sample Mapping Upload CSV](https://drive.google.com/file/d/1SVvi-F0y2YcwNfBpAOYzMZVeh4TbJCxd/view?usp=sharing) -**Note**: Please ensure that the result CSV generated from the createMappingCsv function is passed accordingly. +๐Ÿ“Œ **Note**: Always use the result CSV from the `createMappingCsv` step.
Mapping Upload API @@ -139,3 +157,15 @@ curl --location '{{baseURL}}/entity-management/v1/entities/mappingUpload' \ ```
+ +--- + +## โœ… Summary of Steps + +1. **Login** โ€“ Authenticate and retrieve your `X-auth-token`. +2. **Create Entity Type** โ€“ Define the category (type) of the entities you want to manage. +3. **Bulk Upload Entities** โ€“ Upload a list of entities using a formatted CSV file. +4. **Generate Mapping CSV** โ€“ Create a base CSV that outlines relationships between entities. +5. **Upload Entity Mapping** โ€“ Finalize the entity hierarchy by uploading the mapping CSV. + +--- diff --git a/src/entityCreation/removeDuplicate.js b/src/entityTypeAndEntityOnboarding/utilityScripts/removeDuplicate.js similarity index 100% rename from src/entityCreation/removeDuplicate.js rename to src/entityTypeAndEntityOnboarding/utilityScripts/removeDuplicate.js diff --git a/src/entityCreation/splitEntities.js b/src/entityTypeAndEntityOnboarding/utilityScripts/splitEntities.js similarity index 100% rename from src/entityCreation/splitEntities.js rename to src/entityTypeAndEntityOnboarding/utilityScripts/splitEntities.js From b8f19597b7e9a73c1eaca45c4381642609bc9dc5 Mon Sep 17 00:00:00 2001 From: prajwal Date: Fri, 27 Jun 2025 15:36:48 +0530 Subject: [PATCH 71/91] error handling in entities/details api added --- src/module/entities/helper.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/module/entities/helper.js b/src/module/entities/helper.js index 1017860..4c42d2f 100644 --- a/src/module/entities/helper.js +++ b/src/module/entities/helper.js @@ -1460,6 +1460,13 @@ module.exports = class UserProjectsHelper { // Fetch entity documents based on constructed query let entityDocument = await entitiesQueries.entityDocuments(query, 'all') + if (!entityDocument.length) { + throw { + status: HTTP_STATUS_CODE.bad_request.status, + message: CONSTANTS.apiResponses.ENTITY_NOT_FOUND, + } + } + // Initialize variables for parent entity details let entityDocumentForParent let parentInformation = {} From 3eb84ad76cd747b66202567a51d441a6b2d1cde4 Mon Sep 17 00:00:00 2001 From: priyanka-TL Date: Sat, 28 Jun 2025 20:58:24 +0530 Subject: [PATCH 72/91] updated ansible --- deployment/ansible.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/deployment/ansible.yml b/deployment/ansible.yml index 59bb8a5..a4f9aa3 100644 --- a/deployment/ansible.yml +++ b/deployment/ansible.yml @@ -11,14 +11,12 @@ #- debug: msg="{{ slurpfile['content'] | b64decode }}" #- debug: msg="the value of foo.txt is {{ contents }}" - name: Get vault credentials - shell: "curl --location --request GET '{{ vaultAddress }}elevate-entity-management' --header 'X-Vault-Token: {{ slurpfile['content'] | b64decode }}' | jq '.data' > '{{ project_path -}}/data2.json'" + shell: "curl --location --request GET '{{ vaultAddress }}elevate-entity-management' --header 'X-Vault-Token: {{ slurpfile['content'] | b64decode }}' | jq '.data' > '{{ project_path }}/data2.json'" register: credentials - debug: msg="{{ credentials }}" - name: Get gcp credentials - shell: "curl --location --request GET '{{ vaultAddress }}gcp' --header 'X-Vault-Token: {{ slurpfile['content'] | b64decode }}' | jq '.data.data' > '{{ project_path -}}/gcp.json'" + shell: "curl --location --request GET '{{ vaultAddress }}gcp' --header 'X-Vault-Token: {{ slurpfile['content'] | b64decode }}' | jq '.data.data' > '{{ project_path }}/gcp.json'" - name: Set some variable set_fact: @@ -44,7 +42,10 @@ update: yes version: "{{ gitBranch }}" - name: Update npm - shell: cd {{release_path}}/src && npm i && npm i redoc-cli + shell: cd {{release_path}}/src && npm i && npm i redoc-cli + + - name: Get config.json + shell: "curl --location --request GET '{{ vaultAddress }}projectSurveyEntityConfig' --header 'X-Vault-Token: {{ slurpfile['content'] | b64decode }}' | jq '.data.data' > '{{ release_path }}/config.json'" - name: Delete Old Folder shell: rm -rf {{ current_path }} && cd {{ project_path }} && mkdir entity-management From 74d558c713b360ef4fbc8b00e635417c3404eb88 Mon Sep 17 00:00:00 2001 From: prajwal Date: Wed, 2 Jul 2025 10:41:04 +0530 Subject: [PATCH 73/91] new entities api created to handle aggregate pipeline query --- src/controllers/v1/entities.js | 103 +++++++++++++++++++++++++++++++ src/generics/constants/common.js | 1 + src/module/entities/helper.js | 34 ++++++++++ 3 files changed, 138 insertions(+) diff --git a/src/controllers/v1/entities.js b/src/controllers/v1/entities.js index a8c5ffa..4c4d598 100644 --- a/src/controllers/v1/entities.js +++ b/src/controllers/v1/entities.js @@ -8,6 +8,7 @@ // Dependencies const entitiesHelper = require(MODULES_BASE_PATH + '/entities/helper') const csv = require('csvtojson') +const { ObjectId } = require('mongodb') const FileStream = require(PROJECT_ROOT_DIRECTORY + '/generics/file-stream') const entitiesQueries = require(DB_QUERY_BASE_PATH + '/entities') @@ -1097,4 +1098,106 @@ module.exports = class Entities extends Abstract { } }) } + + /** + * @api {post} /v1/entities/getAggregate Fetch entity data + * @apiVersion 1.0.0 + * @apiGroup Entities + * @apiHeader {String} internal-access-token Internal Access Token + * @apiSampleRequest /v1/entities/getAggregate + * @apiSampleRequestBody + * + [ + { + "$match": { + "_id": { + "$in": [] + } + } + }, + { + "$project": { + "groupIds": "$groups.school" + } + }, + // Unwind the array so we don't hold all in memory + { + "$unwind": "$groupIds" + }, + // Replace the root so we can lookup directly + { + "$replaceRoot": { + "newRoot": { + "_id": "$groupIds" + } + } + }, + // Lookup actual school entity details + { + "$lookup": { + "from": "entities", + "localField": "_id", + "foreignField": "_id", + "as": "groupEntityData" + } + }, + { + "$unwind": "$groupEntityData" + }, + { + "$skip": 50 + }, + { + "$limit": 2 + }, + { + "$replaceRoot": { + "newRoot": "$groupEntityData" + } + }, + { + "$project": { + "_id": 1, + "entityType": 1, + "metaInformation.externalId": 1, + "metaInformation.name": 1 + } + } + ] + * @param {Object} req - The request object. + * @apiParamExample {json} Response: + * "result": [ + { + _id: 67c82d9553812588916410d9, + entityType: 'school', + metaInformation: { + externalId: '29020100501', + name: 'GOVT- KANNADA BOYS HIGHER PRIMARY SCHOOL ALLUR SP' + } + }, + { + _id: 67c82da9538125889165479f, + entityType: 'school', + metaInformation: { + externalId: '29334100922', + name: 'SHRI HARI INDPENDENT PU COLLEGE YADAGIRI' + } + } + ] + */ + getAggregate(req) { + return new Promise(async (resolve, reject) => { + try { + const aggregatePipeline = req.body.pipelineData + let entityData = await entitiesHelper.getAggregate(aggregatePipeline) + return resolve(entityData) + } catch (error) { + return reject({ + status: error.status || HTTP_STATUS_CODE.internal_server_error.status, + message: error.message || HTTP_STATUS_CODE.internal_server_error.message, + errorObject: error, + }) + } + }) + } } diff --git a/src/generics/constants/common.js b/src/generics/constants/common.js index 5011c80..68e5957 100644 --- a/src/generics/constants/common.js +++ b/src/generics/constants/common.js @@ -26,6 +26,7 @@ module.exports = { '/userRoleExtension/update', '/userRoleExtension/delete', '/admin/createIndex', + '/entities/getAggregate', ], SYSTEM: 'SYSTEM', SUCCESS: 'SUCCESS', diff --git a/src/module/entities/helper.js b/src/module/entities/helper.js index 4c42d2f..a162514 100644 --- a/src/module/entities/helper.js +++ b/src/module/entities/helper.js @@ -2176,6 +2176,40 @@ module.exports = class UserProjectsHelper { } }) } + + /** + * Fetch entities. + * @method + * @name list + * @param {Array} aggregatePipeline - entity type. + * @returns {JSON} - Details of entity. + */ + + static getAggregate(aggregatePipeline) { + return new Promise(async (resolve, reject) => { + try { + // Convert the ids to mongoIds + aggregatePipeline = UTILS.convertMongoIds(aggregatePipeline) + let entityData = await entitiesQueries.getAggregate(aggregatePipeline) + if (!entityData.length) { + throw { + status: HTTP_STATUS_CODE.not_found.status, + message: CONSTANTS.apiResponses.ENTITY_NOT_FOUND, + } + } + + const count = entityData.length + const result = entityData + return resolve({ + message: CONSTANTS.apiResponses.ENTITY_INFORMATION_FETCHED, + result, + count, + }) + } catch (err) { + return reject(err) + } + }) + } } /** From dfc6732273a96ad00b5b1bf02dd0aefeb60bad13 Mon Sep 17 00:00:00 2001 From: prajwal Date: Wed, 2 Jul 2025 12:09:55 +0530 Subject: [PATCH 74/91] code-clean --- src/controllers/v1/entities.js | 1 - src/module/entities/helper.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/controllers/v1/entities.js b/src/controllers/v1/entities.js index 4c4d598..b2d704a 100644 --- a/src/controllers/v1/entities.js +++ b/src/controllers/v1/entities.js @@ -8,7 +8,6 @@ // Dependencies const entitiesHelper = require(MODULES_BASE_PATH + '/entities/helper') const csv = require('csvtojson') -const { ObjectId } = require('mongodb') const FileStream = require(PROJECT_ROOT_DIRECTORY + '/generics/file-stream') const entitiesQueries = require(DB_QUERY_BASE_PATH + '/entities') diff --git a/src/module/entities/helper.js b/src/module/entities/helper.js index a162514..f0433cd 100644 --- a/src/module/entities/helper.js +++ b/src/module/entities/helper.js @@ -2180,7 +2180,7 @@ module.exports = class UserProjectsHelper { /** * Fetch entities. * @method - * @name list + * @name getAggregate * @param {Array} aggregatePipeline - entity type. * @returns {JSON} - Details of entity. */ From 941f187ccc98c41fdb9e2285bca6602951dc57ae Mon Sep 17 00:00:00 2001 From: prajwal Date: Wed, 2 Jul 2025 13:40:13 +0530 Subject: [PATCH 75/91] comments addressed --- src/controllers/v1/entities.js | 12 ++++++------ src/generics/constants/common.js | 2 +- src/module/entities/helper.js | 12 ++++++------ src/module/entities/validator/v1.js | 7 +++++++ 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/controllers/v1/entities.js b/src/controllers/v1/entities.js index b2d704a..9e65f2c 100644 --- a/src/controllers/v1/entities.js +++ b/src/controllers/v1/entities.js @@ -1099,14 +1099,14 @@ module.exports = class Entities extends Abstract { } /** - * @api {post} /v1/entities/getAggregate Fetch entity data + * @api {post} /v1/entities/fetch Fetch entity data * @apiVersion 1.0.0 * @apiGroup Entities * @apiHeader {String} internal-access-token Internal Access Token - * @apiSampleRequest /v1/entities/getAggregate + * @apiSampleRequest /v1/entities/fetch * @apiSampleRequestBody * - [ + query = [ { "$match": { "_id": { @@ -1184,11 +1184,11 @@ module.exports = class Entities extends Abstract { } ] */ - getAggregate(req) { + fetch(req) { return new Promise(async (resolve, reject) => { try { - const aggregatePipeline = req.body.pipelineData - let entityData = await entitiesHelper.getAggregate(aggregatePipeline) + const filterQuery = req.body.query + let entityData = await entitiesHelper.fetch(filterQuery) return resolve(entityData) } catch (error) { return reject({ diff --git a/src/generics/constants/common.js b/src/generics/constants/common.js index 68e5957..ef365f3 100644 --- a/src/generics/constants/common.js +++ b/src/generics/constants/common.js @@ -26,7 +26,7 @@ module.exports = { '/userRoleExtension/update', '/userRoleExtension/delete', '/admin/createIndex', - '/entities/getAggregate', + '/entities/fetch', ], SYSTEM: 'SYSTEM', SUCCESS: 'SUCCESS', diff --git a/src/module/entities/helper.js b/src/module/entities/helper.js index f0433cd..220a2ea 100644 --- a/src/module/entities/helper.js +++ b/src/module/entities/helper.js @@ -2180,20 +2180,20 @@ module.exports = class UserProjectsHelper { /** * Fetch entities. * @method - * @name getAggregate - * @param {Array} aggregatePipeline - entity type. + * @name fetch + * @param {Array} bodyData - entity type. * @returns {JSON} - Details of entity. */ - static getAggregate(aggregatePipeline) { + static fetch(bodyData) { return new Promise(async (resolve, reject) => { try { // Convert the ids to mongoIds - aggregatePipeline = UTILS.convertMongoIds(aggregatePipeline) - let entityData = await entitiesQueries.getAggregate(aggregatePipeline) + bodyData = UTILS.convertMongoIds(bodyData) + let entityData = await entitiesQueries.getAggregate(bodyData) if (!entityData.length) { throw { - status: HTTP_STATUS_CODE.not_found.status, + status: HTTP_STATUS_CODE.bad_request.status, message: CONSTANTS.apiResponses.ENTITY_NOT_FOUND, } } diff --git a/src/module/entities/validator/v1.js b/src/module/entities/validator/v1.js index 655d9f3..1145bee 100644 --- a/src/module/entities/validator/v1.js +++ b/src/module/entities/validator/v1.js @@ -108,6 +108,13 @@ module.exports = (req) => { registryMappingUpload: function () { req.checkQuery('entityType').exists().withMessage('required entity type') }, + fetch: function () { + req.checkBody('query') + .exists() + .withMessage('Query required in req.body.') + .custom((value) => Array.isArray(value) && value.length > 0) + .withMessage('Query must be a non-empty array.') + }, } if (entitiesValidator[req.params.method]) { From 300bcca3a1860fe17f9ed234f77a0de9d8c12e8d Mon Sep 17 00:00:00 2001 From: MallanagoudaB Date: Wed, 2 Jul 2025 16:36:52 +0530 Subject: [PATCH 76/91] fix-for-Create-Mapping --- src/controllers/v1/entities.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/v1/entities.js b/src/controllers/v1/entities.js index a8c5ffa..1787d00 100644 --- a/src/controllers/v1/entities.js +++ b/src/controllers/v1/entities.js @@ -280,7 +280,7 @@ module.exports = class Entities extends Abstract { createMappingCsv(req) { return new Promise(async (resolve, reject) => { try { - let tenantId = req.userDetails.userInformation.tenantId + let tenantId = req.userDetails.tenantAndOrgInfo.tenantId // Parse CSV data from the uploaded file in the request body let entityCSVData = await csv().fromString(req.files.entityCSV.data.toString()) From 59023bff406d246a291b1e4025df6fcaf087c1eb Mon Sep 17 00:00:00 2001 From: MallanagoudaB Date: Thu, 3 Jul 2025 12:04:04 +0530 Subject: [PATCH 77/91] healthCheck-for-entity --- src/healthCheck/health-check.js | 86 ++++++++++---------------------- src/healthCheck/health.config.js | 36 +++++++++++++ src/healthCheck/index.js | 25 ++++++++-- src/healthCheck/mongodb.js | 31 ------------ src/package.json | 1 + 5 files changed, 83 insertions(+), 96 deletions(-) create mode 100644 src/healthCheck/health.config.js delete mode 100644 src/healthCheck/mongodb.js diff --git a/src/healthCheck/health-check.js b/src/healthCheck/health-check.js index e3732cb..b69f713 100644 --- a/src/healthCheck/health-check.js +++ b/src/healthCheck/health-check.js @@ -1,76 +1,42 @@ /** - * name : health.js. + * name : health-check.js. * author : Priyanka Pradeep * created-date : 21-Mar-2024 * Description : Health check helper functionality. -*/ + */ // Dependencies -const mongodb = require("./mongodb") -const { v1: uuidv1 } = require('uuid') - -const obj = { - MONGO_DB: { - NAME: 'Mongo.db', - FAILED_CODE: 'MONGODB_HEALTH_FAILED', - FAILED_MESSAGE: 'Mongo db is not connected' - }, - NAME: 'EntityServiceHealthCheck', - API_VERSION: '1.0' -} +const { healthCheckHandler } = require('elevate-services-health-check') +const healthCheckConfig = require('./health.config') let health_check = async function (req, res) { - - let checks = [] - let mongodbConnection = await mongodb.health_check() - checks.push(singleCheckObj("MONGO_DB", mongodbConnection)) - - - let checkServices = checks.filter(check => check.healthy === false) - - let result = { - name: obj.NAME, - version: obj.API_VERSION, - healthy: checkServices.length > 0 ? false : true, - checks: checks - } - - let responseData = response(req, result) - res.status(200).json(responseData) + const response = await healthCheckHandler(healthCheckConfig, req.query.serviceName) + res.status(200).json(response) } let healthCheckStatus = function (req, res) { - let responseData = response(req) - res.status(200).json(responseData) -} - -let singleCheckObj = function (serviceName, isHealthy) { - return { - name: obj[serviceName].NAME, - healthy: isHealthy, - err: !isHealthy ? obj[serviceName].FAILED_CODE : "", - errMsg: !isHealthy ? obj[serviceName].FAILED_MESSAGE : "" - } -} + let responseData = response(req) + res.status(200).json(responseData) +} // enabled:process.env.NODE_ENV !== 'development', let response = function (req, result) { - return { - "id": "improvementService.Health.API", - "ver": "1.0", - "ts": new Date(), - "params": { - "resmsgid": uuidv1(), - "msgid": req.headers['msgid'] || req.headers.msgid || uuidv1(), - "status": "successful", - "err": "null", - "errMsg": "null" - }, - "status": 200, - result: result - } + return { + id: 'Entity.Management.service.Health.API', + ver: '1.0', + ts: new Date(), + params: { + resmsgid: uuidv1(), + msgid: req.headers['msgid'] || req.headers.msgid || uuidv1(), + status: 'successful', + err: 'null', + errMsg: 'null', + }, + status: 200, + result: result, + } } module.exports = { - health_check: health_check, - healthCheckStatus: healthCheckStatus -} \ No newline at end of file + health_check: health_check, + healthCheckStatus: healthCheckStatus, +} diff --git a/src/healthCheck/health.config.js b/src/healthCheck/health.config.js new file mode 100644 index 0000000..aca89ae --- /dev/null +++ b/src/healthCheck/health.config.js @@ -0,0 +1,36 @@ +/** + * name : health.config.js. + * author : Mallanagouda R Biradar + * created-date : 30-Jun-2025 + * Description : Health check config file + */ + +module.exports = { + name: 'EntityManagementService', + version: '1.0.0', + checks: { + mongodb: { + enabled: true, + url: process.env.MONGODB_URL, + }, + microservices: [ + { + name: 'UserService', + url: 'http://localhost:3001/user/health?serviceName=EntityManagementService', // Replace with actual URL - use environment variable if needed + enabled: true, + request: { + method: 'GET', + header: { + 'internal-access-token': process.env.INTERNAL_TOKEN, + }, + body: {}, + }, + + expectedResponse: { + status: 200, + 'params.status': 'successful', + }, + }, + ], + }, +} diff --git a/src/healthCheck/index.js b/src/healthCheck/index.js index 3fcab3c..802671b 100644 --- a/src/healthCheck/index.js +++ b/src/healthCheck/index.js @@ -3,11 +3,26 @@ * author : Aman Karki. * created-date : 01-Feb-2021. * Description : Health check Root file. -*/ + */ -let healthCheckService = require("./health-check") +let healthCheckService = require('./health-check') module.exports = function (app) { - app.get("/health", healthCheckService.health_check) - app.get("/healthCheckStatus", healthCheckService.healthCheckStatus) -} \ No newline at end of file + app.get('/entity/health', async (req, res) => { + try { + await healthCheckService.health_check(req, res) + } catch (err) { + console.error('Health check failed:', err.message || err) + res.status(500).json({ healthy: false, message: err.message || 'Internal Server Error' }) + } + }) + + app.get('/healthCheckStatus', async (req, res) => { + try { + await healthCheckService.healthCheckStatus(req, res) + } catch (err) { + console.error('HealthCheckStatus failed:', err.message || err) + res.status(500).json({ healthy: false, message: err.message || 'Internal Server Error' }) + } + }) +} diff --git a/src/healthCheck/mongodb.js b/src/healthCheck/mongodb.js deleted file mode 100644 index efcf57c..0000000 --- a/src/healthCheck/mongodb.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * name : mongodb.js. - * author : Aman Karki. - * created-date : 01-Feb-2021. - * Description : Mongodb health check functionality. -*/ - -// Dependencies - -const mongoose = require("mongoose") - -function health_check() { - return new Promise(async (resolve, reject) => { - - const db = mongoose.createConnection( - process.env.MONGODB_URL + ":" + process.env.MONGODB_PORT + "/" + process.env.MONGODB_DATABASE_NAME - ) - - db.on("error", function () { - return resolve(false) - }) - db.once("open", function () { - db.close(function () { }) - return resolve(true) - }) - }) -} - -module.exports = { - health_check: health_check -} \ No newline at end of file diff --git a/src/package.json b/src/package.json index 31ef7a9..ddf69e3 100644 --- a/src/package.json +++ b/src/package.json @@ -41,6 +41,7 @@ "csvtojson": "^2.0.10", "dotenv": "^8.2.0", "elevate-logger": "^3.1.0", + "elevate-services-health-check": "^0.0.2", "eslint": "^8.16.0", "express": "^4.17.1", "express-fileupload": "^1.5.0", From cba25397c7b4f12326c3daaacd68261dbe899e1f Mon Sep 17 00:00:00 2001 From: prajwal Date: Fri, 4 Jul 2025 16:27:59 +0530 Subject: [PATCH 78/91] safe-test --- src/controllers/v1/entities.js | 108 +---------------- src/generics/constants/common.js | 1 - src/module/entities/helper.js | 198 +++++++++++++++++++------------ 3 files changed, 125 insertions(+), 182 deletions(-) diff --git a/src/controllers/v1/entities.js b/src/controllers/v1/entities.js index 9e65f2c..03eb643 100644 --- a/src/controllers/v1/entities.js +++ b/src/controllers/v1/entities.js @@ -78,7 +78,11 @@ module.exports = class Entities extends Abstract { req.body.projection, req.pageNo, req.pageSize, - req.searchText + req.searchText, + req.query.aggregateValue ? req.query.aggregateValue : null, + req.query.aggregateStaging == 'true' ? true : false, + req.query.aggregateSort == 'true' ? true : false, + req.body.aggregateProjection ? req.body.aggregateProjection : [] ) return resolve(entityData) } catch (error) { @@ -1097,106 +1101,4 @@ module.exports = class Entities extends Abstract { } }) } - - /** - * @api {post} /v1/entities/fetch Fetch entity data - * @apiVersion 1.0.0 - * @apiGroup Entities - * @apiHeader {String} internal-access-token Internal Access Token - * @apiSampleRequest /v1/entities/fetch - * @apiSampleRequestBody - * - query = [ - { - "$match": { - "_id": { - "$in": [] - } - } - }, - { - "$project": { - "groupIds": "$groups.school" - } - }, - // Unwind the array so we don't hold all in memory - { - "$unwind": "$groupIds" - }, - // Replace the root so we can lookup directly - { - "$replaceRoot": { - "newRoot": { - "_id": "$groupIds" - } - } - }, - // Lookup actual school entity details - { - "$lookup": { - "from": "entities", - "localField": "_id", - "foreignField": "_id", - "as": "groupEntityData" - } - }, - { - "$unwind": "$groupEntityData" - }, - { - "$skip": 50 - }, - { - "$limit": 2 - }, - { - "$replaceRoot": { - "newRoot": "$groupEntityData" - } - }, - { - "$project": { - "_id": 1, - "entityType": 1, - "metaInformation.externalId": 1, - "metaInformation.name": 1 - } - } - ] - * @param {Object} req - The request object. - * @apiParamExample {json} Response: - * "result": [ - { - _id: 67c82d9553812588916410d9, - entityType: 'school', - metaInformation: { - externalId: '29020100501', - name: 'GOVT- KANNADA BOYS HIGHER PRIMARY SCHOOL ALLUR SP' - } - }, - { - _id: 67c82da9538125889165479f, - entityType: 'school', - metaInformation: { - externalId: '29334100922', - name: 'SHRI HARI INDPENDENT PU COLLEGE YADAGIRI' - } - } - ] - */ - fetch(req) { - return new Promise(async (resolve, reject) => { - try { - const filterQuery = req.body.query - let entityData = await entitiesHelper.fetch(filterQuery) - return resolve(entityData) - } catch (error) { - return reject({ - status: error.status || HTTP_STATUS_CODE.internal_server_error.status, - message: error.message || HTTP_STATUS_CODE.internal_server_error.message, - errorObject: error, - }) - } - }) - } } diff --git a/src/generics/constants/common.js b/src/generics/constants/common.js index ef365f3..5011c80 100644 --- a/src/generics/constants/common.js +++ b/src/generics/constants/common.js @@ -26,7 +26,6 @@ module.exports = { '/userRoleExtension/update', '/userRoleExtension/delete', '/admin/createIndex', - '/entities/fetch', ], SYSTEM: 'SYSTEM', SUCCESS: 'SUCCESS', diff --git a/src/module/entities/helper.js b/src/module/entities/helper.js index 220a2ea..9d80ee1 100644 --- a/src/module/entities/helper.js +++ b/src/module/entities/helper.js @@ -1104,68 +1104,144 @@ module.exports = class UserProjectsHelper { * @param {String} searchText - search text */ - static find(bodyQuery, projection, pageNo, pageSize, searchText) { + static find( + bodyQuery, + projection, + pageNo, + pageSize, + searchText, + aggregateValue, + aggregateStaging, + aggregateSort, + aggregateProjection = [] + ) { return new Promise(async (resolve, reject) => { try { - // Create facet object to attain pagination - let facetQuery = {} - facetQuery['$facet'] = {} - facetQuery['$facet']['totalCount'] = [{ $count: 'count' }] - if (pageSize === '' && pageNo === '') { - facetQuery['$facet']['data'] = [{ $skip: 0 }] - } else { - facetQuery['$facet']['data'] = [{ $skip: pageSize * (pageNo - 1) }, { $limit: pageSize }] - } - + let aggregateData bodyQuery = UTILS.convertMongoIds(bodyQuery) - // add search filter to the bodyQuery - if (searchText != '') { - let searchData = [ + if (aggregateStaging == true) { + let skip = (pageNo - 1) * pageSize + let projection1 = {} + if (aggregateProjection.length > 0) { + aggregateProjection.forEach((value) => { + projection1[value] = 1 + }) + } + aggregateData = [ { - 'metaInformation.name': new RegExp(searchText, 'i'), + $match: bodyQuery, }, - ] - bodyQuery['$and'] = searchData - } - - // Create projection object - let projection1 - let aggregateData - if (Array.isArray(projection) && projection.length > 0) { - projection1 = {} - projection.forEach((projectedData) => { - projection1[projectedData] = 1 - }) - aggregateData = [ - { $match: bodyQuery }, { - $sort: { updatedAt: -1 }, + $project: { + groupIds: aggregateValue, + }, }, - { $project: projection1 }, - facetQuery, - ] - } else { - aggregateData = [ - { $match: bodyQuery }, + // Unwind the array so we don't hold all in memory { - $sort: { updatedAt: -1 }, + $unwind: '$groupIds', }, - facetQuery, + // Replace the root so we can lookup directly + { + $replaceRoot: { newRoot: { _id: '$groupIds' } }, + }, + // Lookup actual school entity details + { + $lookup: { + from: 'entities', + localField: '_id', + foreignField: '_id', + as: 'groupEntityData', + }, + }, + { + $unwind: '$groupEntityData', + }, + ...(searchText + ? [ + { + $match: { + 'groupEntityData.metaInformation.name': { + $regex: searchText, + $options: 'i', // case-insensitive search + }, + // $text: { $search: searchText } + }, + }, + ] + : []), + { + $skip: skip, + }, + { + $limit: pageSize, + }, + { + $replaceRoot: { newRoot: '$groupEntityData' }, + }, + ...(aggregateProjection.length > 0 ? [{ $project: projection1 }] : []), ] + } else { + // Create facet object to attain pagination + let facetQuery = {} + facetQuery['$facet'] = {} + facetQuery['$facet']['totalCount'] = [{ $count: 'count' }] + if (pageSize === '' && pageNo === '') { + facetQuery['$facet']['data'] = [{ $skip: 0 }] + } else { + facetQuery['$facet']['data'] = [{ $skip: pageSize * (pageNo - 1) }, { $limit: pageSize }] + } + + // add search filter to the bodyQuery + if (searchText != '') { + let searchData = [ + { + 'metaInformation.name': new RegExp(searchText, 'i'), + }, + ] + bodyQuery['$and'] = searchData + } + + // Create projection object + let projection1 + if (Array.isArray(projection) && projection.length > 0) { + projection1 = {} + projection.forEach((projectedData) => { + projection1[projectedData] = 1 + }) + aggregateData = [{ $match: bodyQuery }, { $project: projection1 }, facetQuery] + } else { + aggregateData = [{ $match: bodyQuery }, facetQuery] + } } - const result = await entitiesQueries.getAggregate(aggregateData) - if (!(result.length > 0) || !result[0].data || !(result[0].data.length > 0)) { - throw { - status: HTTP_STATUS_CODE.not_found.status, - message: CONSTANTS.apiResponses.ENTITY_NOT_FOUND, + if (aggregateSort == true) { + aggregateData.push({ $sort: { updateAt: -1 } }) + } + + // console.log(aggregateData) + // console.log(JSON.stringify(aggregateData, null, 2)) + let result = await entitiesQueries.getAggregate(aggregateData) + if (aggregateStaging == true) { + if (!Array.isArray(result) || !(result.length > 0)) { + throw { + status: HTTP_STATUS_CODE.not_found.status, + message: CONSTANTS.apiResponses.ENTITY_NOT_FOUND, + } + } + } else { + if (!(result.length > 0) || !result[0].data || !(result[0].data.length > 0)) { + throw { + status: HTTP_STATUS_CODE.not_found.status, + message: CONSTANTS.apiResponses.ENTITY_NOT_FOUND, + } } + result = result[0].data } return resolve({ success: true, message: CONSTANTS.apiResponses.ASSETS_FETCHED_SUCCESSFULLY, - result: result[0].data, + result: result, }) } catch (error) { return reject(error) @@ -2176,40 +2252,6 @@ module.exports = class UserProjectsHelper { } }) } - - /** - * Fetch entities. - * @method - * @name fetch - * @param {Array} bodyData - entity type. - * @returns {JSON} - Details of entity. - */ - - static fetch(bodyData) { - return new Promise(async (resolve, reject) => { - try { - // Convert the ids to mongoIds - bodyData = UTILS.convertMongoIds(bodyData) - let entityData = await entitiesQueries.getAggregate(bodyData) - if (!entityData.length) { - throw { - status: HTTP_STATUS_CODE.bad_request.status, - message: CONSTANTS.apiResponses.ENTITY_NOT_FOUND, - } - } - - const count = entityData.length - const result = entityData - return resolve({ - message: CONSTANTS.apiResponses.ENTITY_INFORMATION_FETCHED, - result, - count, - }) - } catch (err) { - return reject(err) - } - }) - } } /** From 6b97cac38cc6c47d523cc71b59d5d81afa3aae16 Mon Sep 17 00:00:00 2001 From: prajwal Date: Fri, 4 Jul 2025 18:01:55 +0530 Subject: [PATCH 79/91] removed unwanted code --- src/module/entities/validator/v1.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/module/entities/validator/v1.js b/src/module/entities/validator/v1.js index 1145bee..655d9f3 100644 --- a/src/module/entities/validator/v1.js +++ b/src/module/entities/validator/v1.js @@ -108,13 +108,6 @@ module.exports = (req) => { registryMappingUpload: function () { req.checkQuery('entityType').exists().withMessage('required entity type') }, - fetch: function () { - req.checkBody('query') - .exists() - .withMessage('Query required in req.body.') - .custom((value) => Array.isArray(value) && value.length > 0) - .withMessage('Query must be a non-empty array.') - }, } if (entitiesValidator[req.params.method]) { From 9827676752fdb44168e46c1995912b65d811ad34 Mon Sep 17 00:00:00 2001 From: prajwal Date: Fri, 4 Jul 2025 18:06:44 +0530 Subject: [PATCH 80/91] removed unused code --- src/module/entities/helper.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/module/entities/helper.js b/src/module/entities/helper.js index 9d80ee1..4e0f275 100644 --- a/src/module/entities/helper.js +++ b/src/module/entities/helper.js @@ -1165,7 +1165,6 @@ module.exports = class UserProjectsHelper { $regex: searchText, $options: 'i', // case-insensitive search }, - // $text: { $search: searchText } }, }, ] From 27d0529d7e0ed50ef065819aa53f2caa8aeda0c0 Mon Sep 17 00:00:00 2001 From: prajwal Date: Fri, 4 Jul 2025 18:22:44 +0530 Subject: [PATCH 81/91] function signature added --- src/module/entities/helper.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/module/entities/helper.js b/src/module/entities/helper.js index 4e0f275..98037d3 100644 --- a/src/module/entities/helper.js +++ b/src/module/entities/helper.js @@ -1102,6 +1102,10 @@ module.exports = class UserProjectsHelper { * @param {Number} pageNo - page number * @param {Number} pageSize - page limit * @param {String} searchText - search text + * @param {String} aggregateValue - aggregate group value + * @param {Boolean} aggregateStaging - is aggregate staging present + * @param {Boolean} aggregateSort - is sorting present in aggregate staging + * @param {Array} aggregateProjection - projection fields for aggregation */ static find( @@ -1218,8 +1222,6 @@ module.exports = class UserProjectsHelper { aggregateData.push({ $sort: { updateAt: -1 } }) } - // console.log(aggregateData) - // console.log(JSON.stringify(aggregateData, null, 2)) let result = await entitiesQueries.getAggregate(aggregateData) if (aggregateStaging == true) { if (!Array.isArray(result) || !(result.length > 0)) { From de89ed72be7ea4faa943feb5b98ac69999b46ed4 Mon Sep 17 00:00:00 2001 From: prajwal Date: Fri, 4 Jul 2025 18:38:33 +0530 Subject: [PATCH 82/91] function signature updated --- src/module/entities/helper.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/module/entities/helper.js b/src/module/entities/helper.js index 98037d3..962ff24 100644 --- a/src/module/entities/helper.js +++ b/src/module/entities/helper.js @@ -1101,11 +1101,12 @@ module.exports = class UserProjectsHelper { * @param {Object} projection - projection to filter data * @param {Number} pageNo - page number * @param {Number} pageSize - page limit - * @param {String} searchText - search text - * @param {String} aggregateValue - aggregate group value - * @param {Boolean} aggregateStaging - is aggregate staging present - * @param {Boolean} aggregateSort - is sorting present in aggregate staging - * @param {Array} aggregateProjection - projection fields for aggregation + * @param {String} searchText - Text string used for filtering entities using a search. + * @param {String} aggregateValue - Path to the field to aggregate (e.g., 'groups.school') used for grouping or lookups. + * @param {Boolean} aggregateStaging - Flag indicating whether aggregation stages should be used in the pipeline (true = include stages). + * @param {Boolean} aggregateSort - Flag indicating whether sorting is required within the aggregation pipeline. + * @param {Array} aggregateProjection - Array of projection fields to apply within the aggregation pipeline (used when `aggregateStaging` is true). + * @returns {Array} Entity Documents */ static find( From 85a4a3862aa81eeed24ba9e74aa35b2d9748bd0c Mon Sep 17 00:00:00 2001 From: prajwal Date: Mon, 7 Jul 2025 13:03:38 +0530 Subject: [PATCH 83/91] comments addressed --- src/controllers/v1/entities.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/controllers/v1/entities.js b/src/controllers/v1/entities.js index 03eb643..e9cd9b4 100644 --- a/src/controllers/v1/entities.js +++ b/src/controllers/v1/entities.js @@ -43,6 +43,17 @@ module.exports = class Entities extends Abstract { * @apiVersion 1.0.0 * @apiName find * @param {Object} req - The request object. + * @param {Object} req.body.query - MongoDB filter query to match specific entity documents. + * @param {Object} req.body.projection - Fields to include or exclude in the result set. + * @param {Number} req.pageNo - Page number for pagination. + * @param {Number} req.pageSize - Number of documents to return per page. + * @param {String} req.searchText - Optional search string for text-based filtering. + * @param {String|null} req.query.aggregateValue - Field path to be used for aggregation (e.g., "groups.school"); set to `null` if not used. + * @param {Boolean} req.query.aggregateStaging - Whether to apply aggregation stages in the pipeline. + * @param {Boolean} req.query.aggregateSort - Whether to apply sorting within the aggregation pipeline. + * @param {Array} req.body.aggregateProjection - Optional array of projection stages for aggregation. + * + * @returns {Promise} - A Promise resolving to a list of matched entity documents with pagination. * @apiGroup Entities * @apiSampleRequest { "query" : { From ac4b68e9057f1b2f0c9f35de9984ea6bac3d3634 Mon Sep 17 00:00:00 2001 From: prajwal Date: Mon, 7 Jul 2025 13:37:00 +0530 Subject: [PATCH 84/91] comments addressed --- src/api-doc/api-doc.yaml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/api-doc/api-doc.yaml b/src/api-doc/api-doc.yaml index 150c0a5..0ed9333 100644 --- a/src/api-doc/api-doc.yaml +++ b/src/api-doc/api-doc.yaml @@ -1709,6 +1709,34 @@ paths: - location: body param: query msg: required query + parameters: + - in: query + name: page + description: Page number for pagination + schema: &ref_4 + type: number + - in: query + name: limit + description: Number of documents to return per page + schema: *ref_4 + - in: query + name: search + description: Optional search string for text-based filtering + schema: &ref_5 + type: string + - in: query + name: aggregateValue + description: Field path to be used for aggregation + schema: *ref_5 + - in: query + name: aggregateStaging + description: Whether to apply aggregation stages in the pipeline + schema: &ref_6 + type: boolean + - in: query + name: aggregateSort + description: Whether to apply sorting within the aggregation pipeline + schema: *ref_6 /v1/entities/list/_id?page={page_no}&limit={page_limit}&type={entity_type}: get: summary: List entities based on entity id From c0d15d21a522051aeeb09cc349eb4347fc2fc2ba Mon Sep 17 00:00:00 2001 From: MallanagoudaB Date: Mon, 7 Jul 2025 14:48:05 +0530 Subject: [PATCH 85/91] added-validation --- src/healthCheck/README.md | 41 ++++++++++++++++++++ src/healthCheck/health-check.js | 64 ++++++++++++++++++++++++++++++-- src/healthCheck/health.config.js | 9 ++--- src/healthCheck/index.js | 2 +- 4 files changed, 107 insertions(+), 9 deletions(-) create mode 100644 src/healthCheck/README.md diff --git a/src/healthCheck/README.md b/src/healthCheck/README.md new file mode 100644 index 0000000..3f58da2 --- /dev/null +++ b/src/healthCheck/README.md @@ -0,0 +1,41 @@ +# Health Check Configuration Guide + +This project uses the `elevate-services-health-check` package to perform health checks for internal components like MongoDB, Kafka, and dependent microservices. + +To enable this, create a configuration file (`health.config.js`) that defines what to check and how. + +--- + +## โœ… Sample Configuration + +```js +module.exports = { + name: 'EntityManagementService', // ๐Ÿ”น Service name shown in health check response + version: '1.0.0', // ๐Ÿ”น Service version shown in response + + checks: { + mongodb: { + enabled: true, // โœ… Required if MongoDB is used + url: process.env.MONGODB_URL, // ๐Ÿ” Recommended: use env variable + }, + microservices: [ + { + name: 'UserService', + url: `${process.env.USER_SERVICE_URL}/user/health?serviceName=${process.env.SERVICE_NAME}`, + enabled: true, + request: { + method: 'GET', + header: {}, + body: {}, + }, + + expectedResponse: { + status: 200, + 'params.status': 'successful', + 'result.healthy': true, + }, + }, + ], + }, +} +``` diff --git a/src/healthCheck/health-check.js b/src/healthCheck/health-check.js index b69f713..7bfa9f0 100644 --- a/src/healthCheck/health-check.js +++ b/src/healthCheck/health-check.js @@ -8,16 +8,36 @@ // Dependencies const { healthCheckHandler } = require('elevate-services-health-check') const healthCheckConfig = require('./health.config') +const { v1: uuidv1 } = require('uuid') let health_check = async function (req, res) { - const response = await healthCheckHandler(healthCheckConfig, req.query.serviceName) - res.status(200).json(response) + try { + validateHealthConfig(healthCheckConfig) + const response = await healthCheckHandler(healthCheckConfig, req.query.basicCheck, req.query.serviceName) + res.status(200).json(response) + } catch (err) { + console.error('Health config validation failed:', err.message || err) + res.status(400).json({ + id: 'mentoringService.Health.API', + ver: '1.0', + ts: new Date(), + params: { + resmsgid: uuidv1(), + msgid: req.headers['msgid'] || req.headers.msgid || uuidv1(), + status: 'failed', + err: 'CONFIG_VALIDATION_ERROR', + errMsg: err.message || 'Invalid config', + }, + status: 400, + result: {}, + }) + } } let healthCheckStatus = function (req, res) { let responseData = response(req) res.status(200).json(responseData) -} // enabled:process.env.NODE_ENV !== 'development', +} let response = function (req, result) { return { @@ -36,6 +56,44 @@ let response = function (req, result) { } } +function validateHealthConfig(config) { + if (!config.checks) { + throw new Error('Health config must include a `checks` object') + } + + const { kafka, postgres, redis, microservices } = config.checks + + const basicServices = [ + { name: 'kafka', value: kafka }, + { name: 'postgres', value: postgres }, + { name: 'redis', value: redis }, + ] + + for (const { name, value } of basicServices) { + if (value?.enabled && !value.url) { + throw new Error(`Missing 'url' for enabled service: ${name}`) + } + } + + if (Array.isArray(microservices)) { + microservices.forEach((service, index) => { + if (service.enabled) { + const missingKeys = [] + if (!service.name) missingKeys.push('name') + if (!service.url) missingKeys.push('url') + if (!service.request) missingKeys.push('request') + if (!service.expectedResponse) missingKeys.push('expectedResponse') + + if (missingKeys.length > 0) { + throw new Error( + `Missing required fields for enabled microservice at index ${index}: ${missingKeys.join(', ')}` + ) + } + } + }) + } +} + module.exports = { health_check: health_check, healthCheckStatus: healthCheckStatus, diff --git a/src/healthCheck/health.config.js b/src/healthCheck/health.config.js index aca89ae..459d428 100644 --- a/src/healthCheck/health.config.js +++ b/src/healthCheck/health.config.js @@ -6,7 +6,7 @@ */ module.exports = { - name: 'EntityManagementService', + name: process.env.SERVICE_NAME, version: '1.0.0', checks: { mongodb: { @@ -16,19 +16,18 @@ module.exports = { microservices: [ { name: 'UserService', - url: 'http://localhost:3001/user/health?serviceName=EntityManagementService', // Replace with actual URL - use environment variable if needed + url: `${process.env.USER_SERVICE_URL}/user/health?serviceName=${process.env.SERVICE_NAME}`, enabled: true, request: { method: 'GET', - header: { - 'internal-access-token': process.env.INTERNAL_TOKEN, - }, + header: {}, body: {}, }, expectedResponse: { status: 200, 'params.status': 'successful', + 'result.healthy': true, }, }, ], diff --git a/src/healthCheck/index.js b/src/healthCheck/index.js index 802671b..680695b 100644 --- a/src/healthCheck/index.js +++ b/src/healthCheck/index.js @@ -8,7 +8,7 @@ let healthCheckService = require('./health-check') module.exports = function (app) { - app.get('/entity/health', async (req, res) => { + app.get('/health', async (req, res) => { try { await healthCheckService.health_check(req, res) } catch (err) { From 5efc3350c3abdc776aabcb93149d5c80a19e2dd2 Mon Sep 17 00:00:00 2001 From: MallanagoudaB Date: Tue, 8 Jul 2025 17:15:18 +0530 Subject: [PATCH 86/91] read-me-changes --- src/healthCheck/README.md | 157 ++++++++++++++++++++++++++------ src/healthCheck/health-check.js | 41 +-------- 2 files changed, 132 insertions(+), 66 deletions(-) diff --git a/src/healthCheck/README.md b/src/healthCheck/README.md index 3f58da2..a76aae8 100644 --- a/src/healthCheck/README.md +++ b/src/healthCheck/README.md @@ -1,41 +1,146 @@ -# Health Check Configuration Guide +# ๐Ÿฉบ Health Check Configuration Guide -This project uses the `elevate-services-health-check` package to perform health checks for internal components like MongoDB, Kafka, and dependent microservices. +This project uses the `elevate-project-services-health-check` package to monitor the health of various services like databases, message brokers, and internal microservices. -To enable this, create a configuration file (`health.config.js`) that defines what to check and how. +To enable this, create a configuration file named `health.config.js`. This file defines **what to check**, **how to check it**, and **what constitutes a healthy response**. --- -## โœ… Sample Configuration +## ๐Ÿ“ File Structure + +```bash +healthCheck/ +โ”œโ”€โ”€ health.config.js # โœ… Your health check configuration +โ””โ”€โ”€ ... +``` + +--- + +## โœ… Basic Structure ```js module.exports = { - name: 'EntityManagementService', // ๐Ÿ”น Service name shown in health check response - version: '1.0.0', // ๐Ÿ”น Service version shown in response + name: 'YourServiceName', + version: '1.0.0', + checks: { + // Define checks here + }, +} +``` + +--- + +## ๐Ÿงพ Top-Level Keys + +| Key | Type | Required | Description | +| --------- | -------- | -------- | ---------------------------------------------------------------- | +| `name` | `string` | โœ… | Name of the service. Displayed in the health check response. | +| `version` | `string` | โœ… | Current version of the service. Useful for tracking deployments. | +| `checks` | `object` | โœ… | Contains configuration for all enabled health checks. | + +--- + +## ๐Ÿ” `checks` Object + +This is the heart of your config. It allows you to define **which components to monitor** and **how**. + +### ๐Ÿงฉ Supported Built-in Checks + +Each service has the following structure: + +```js +: { + enabled: true, + url: process.env.SERVICE_URL, +} +``` + +### โœ… Common Services + +| Service | Purpose | Notes | +| ----------- | ------------------------------- | -------------------------------------------- | +| `mongodb` | Check MongoDB connection | `url` must point to a valid MongoDB URI | +| `postgres` | Check PostgreSQL database | Example: `postgres://user:pass@host:port/db` | +| `redis` | Check Redis connectivity | Can be local or remote | +| `kafka` | Check Kafka producer & consumer | Broker URL must be reachable | +| `gotenberg` | Check PDF conversion service | URL to Gotenberg's health endpoint | + +--- + +## ๐Ÿ” Microservices Health Checks +To validate dependent microservices, use the `microservices` array. + +```js +microservices: [ + { + name: 'ServiceName', + url: 'https://host/health', + enabled: true, + request: { + method: 'GET', + header: {}, + body: {}, + }, + expectedResponse: { + status: 200, + 'result.healthy': true, + 'meta.ok': 'yes', + }, + }, +] +``` + +### ๐Ÿง  Notes on `expectedResponse` + +- Supports **deep key matching** using dot notation (e.g., `result.healthy`) +- All keys must match their expected values +- If any value does not match, the service is marked unhealthy + +--- + +## ๐Ÿ“Œ Example `.env` Usage (Recommended) + +```env +MONGODB_URL=mongodb://localhost:27017/mydb +POSTGRES_URL=postgres://user:pass@localhost:5432/mydb +GOTENBERG_URL=http://localhost:3000 +KAFKA_URL=kafka://localhost:9092 +SURVEY_SERVICE_URL=http://localhost:4001/survey/health +``` + +--- + +## ๐Ÿšจ Best Practices + +- โœ… Always keep `enabled: true` only for services currently in use. +- โœ… Use environment variables to avoid hardcoding URLs and credentials. +- โœ… Validate your config during startup using a helper like `validateHealthConfig(config)`. +- ๐Ÿ›‘ Do not include sensitive tokens or secrets directly in the config. + +--- + +## โœ… Minimal Valid Configuration + +```js +module.exports = { + name: 'MyService', + version: '1.0.0', checks: { mongodb: { - enabled: true, // โœ… Required if MongoDB is used - url: process.env.MONGODB_URL, // ๐Ÿ” Recommended: use env variable + enabled: true, + url: process.env.MONGODB_URL, }, - microservices: [ - { - name: 'UserService', - url: `${process.env.USER_SERVICE_URL}/user/health?serviceName=${process.env.SERVICE_NAME}`, - enabled: true, - request: { - method: 'GET', - header: {}, - body: {}, - }, - - expectedResponse: { - status: 200, - 'params.status': 'successful', - 'result.healthy': true, - }, - }, - ], + redis: { + enabled: false, + }, + microservices: [], }, } ``` + +--- + +## ๐Ÿ“ž Need More? + +Supports Kafka send/receive, Redis ping, MongoDB & Postgres connectivity, HTTP validation for microservices, and response structure validation. diff --git a/src/healthCheck/health-check.js b/src/healthCheck/health-check.js index 7bfa9f0..2bb38d1 100644 --- a/src/healthCheck/health-check.js +++ b/src/healthCheck/health-check.js @@ -12,13 +12,12 @@ const { v1: uuidv1 } = require('uuid') let health_check = async function (req, res) { try { - validateHealthConfig(healthCheckConfig) const response = await healthCheckHandler(healthCheckConfig, req.query.basicCheck, req.query.serviceName) res.status(200).json(response) } catch (err) { console.error('Health config validation failed:', err.message || err) res.status(400).json({ - id: 'mentoringService.Health.API', + id: 'entityService.Health.API', ver: '1.0', ts: new Date(), params: { @@ -56,44 +55,6 @@ let response = function (req, result) { } } -function validateHealthConfig(config) { - if (!config.checks) { - throw new Error('Health config must include a `checks` object') - } - - const { kafka, postgres, redis, microservices } = config.checks - - const basicServices = [ - { name: 'kafka', value: kafka }, - { name: 'postgres', value: postgres }, - { name: 'redis', value: redis }, - ] - - for (const { name, value } of basicServices) { - if (value?.enabled && !value.url) { - throw new Error(`Missing 'url' for enabled service: ${name}`) - } - } - - if (Array.isArray(microservices)) { - microservices.forEach((service, index) => { - if (service.enabled) { - const missingKeys = [] - if (!service.name) missingKeys.push('name') - if (!service.url) missingKeys.push('url') - if (!service.request) missingKeys.push('request') - if (!service.expectedResponse) missingKeys.push('expectedResponse') - - if (missingKeys.length > 0) { - throw new Error( - `Missing required fields for enabled microservice at index ${index}: ${missingKeys.join(', ')}` - ) - } - } - }) - } -} - module.exports = { health_check: health_check, healthCheckStatus: healthCheckStatus, From 77843a7cef3e3882b815714b0bcee6b8970f0e9c Mon Sep 17 00:00:00 2001 From: MallanagoudaB Date: Wed, 9 Jul 2025 19:07:30 +0530 Subject: [PATCH 87/91] update-npm --- src/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/package.json b/src/package.json index ddf69e3..7f70679 100644 --- a/src/package.json +++ b/src/package.json @@ -41,7 +41,7 @@ "csvtojson": "^2.0.10", "dotenv": "^8.2.0", "elevate-logger": "^3.1.0", - "elevate-services-health-check": "^0.0.2", + "elevate-services-health-check": "^0.0.3", "eslint": "^8.16.0", "express": "^4.17.1", "express-fileupload": "^1.5.0", From 1c64aba3e29599029a6241ccaf58d0c95531e947 Mon Sep 17 00:00:00 2001 From: MallanagoudaB Date: Wed, 9 Jul 2025 19:09:35 +0530 Subject: [PATCH 88/91] update --- .../utilityScripts/removeDuplicate.js | 7 +++++++ .../utilityScripts/splitEntities.js | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/entityTypeAndEntityOnboarding/utilityScripts/removeDuplicate.js b/src/entityTypeAndEntityOnboarding/utilityScripts/removeDuplicate.js index 4b99510..ae6069f 100644 --- a/src/entityTypeAndEntityOnboarding/utilityScripts/removeDuplicate.js +++ b/src/entityTypeAndEntityOnboarding/utilityScripts/removeDuplicate.js @@ -1,3 +1,10 @@ +/** + * name : removeDuplicate.js + * author : Mallanagouda R Biradar + * created-date : 24-June-2025 + * Description : Remove Duplicates. + */ + const fs = require('fs') const csv = require('csv-parser') const createCsvWriter = require('csv-writer').createObjectCsvWriter diff --git a/src/entityTypeAndEntityOnboarding/utilityScripts/splitEntities.js b/src/entityTypeAndEntityOnboarding/utilityScripts/splitEntities.js index 5d9ea49..7514f9c 100644 --- a/src/entityTypeAndEntityOnboarding/utilityScripts/splitEntities.js +++ b/src/entityTypeAndEntityOnboarding/utilityScripts/splitEntities.js @@ -1,3 +1,10 @@ +/** + * name : splitEntities.js + * author : Mallanagouda R Biradar + * created-date : 24-June-2025 + * Description : Split the entities based on the limit pass. + */ + const fs = require('fs') const csv = require('csv-parser') const { createObjectCsvWriter } = require('csv-writer') From c1606a877c2f5c8d261485b0fd84153f9e0714f5 Mon Sep 17 00:00:00 2001 From: MallanagoudaB Date: Mon, 14 Jul 2025 12:29:18 +0530 Subject: [PATCH 89/91] change-in-name --- src/healthCheck/health-check.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/healthCheck/health-check.js b/src/healthCheck/health-check.js index 2bb38d1..e6eba76 100644 --- a/src/healthCheck/health-check.js +++ b/src/healthCheck/health-check.js @@ -1,7 +1,7 @@ /** * name : health-check.js. - * author : Priyanka Pradeep - * created-date : 21-Mar-2024 + * author : Mallanagouda R Biradar + * created-date : 30-Jun-2025 * Description : Health check helper functionality. */ From b6521121f791f4f553b7c215851a0e54abd673c5 Mon Sep 17 00:00:00 2001 From: borkarsaish65 Date: Mon, 14 Jul 2025 18:05:24 +0530 Subject: [PATCH 90/91] feat:license update --- LICENSE | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/LICENSE b/LICENSE index 367155f..68c813d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,5 @@ MIT License - -Copyright (c) 2021 Project Sunbird - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +Copyright (c) 2025 Shikshalokam +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file From e086c80a6fa2daad35742b8c3ae1764161e626c9 Mon Sep 17 00:00:00 2001 From: priyanka-TL Date: Mon, 14 Jul 2025 18:25:03 +0530 Subject: [PATCH 91/91] updated ansible file --- deployment/ansible.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/deployment/ansible.yml b/deployment/ansible.yml index 59bb8a5..7d66557 100644 --- a/deployment/ansible.yml +++ b/deployment/ansible.yml @@ -11,14 +11,12 @@ #- debug: msg="{{ slurpfile['content'] | b64decode }}" #- debug: msg="the value of foo.txt is {{ contents }}" - name: Get vault credentials - shell: "curl --location --request GET '{{ vaultAddress }}elevate-entity-management' --header 'X-Vault-Token: {{ slurpfile['content'] | b64decode }}' | jq '.data' > '{{ project_path -}}/data2.json'" + shell: "curl --location --request GET '{{ vaultAddress }}elevate-entity-management' --header 'X-Vault-Token: {{ slurpfile['content'] | b64decode }}' | jq '.data' > '{{ project_path }}/data2.json'" register: credentials - debug: msg="{{ credentials }}" - name: Get gcp credentials - shell: "curl --location --request GET '{{ vaultAddress }}gcp' --header 'X-Vault-Token: {{ slurpfile['content'] | b64decode }}' | jq '.data.data' > '{{ project_path -}}/gcp.json'" + shell: "curl --location --request GET '{{ vaultAddress }}gcp' --header 'X-Vault-Token: {{ slurpfile['content'] | b64decode }}' | jq '.data.data' > '{{ project_path }}/gcp.json'" - name: Set some variable set_fact: @@ -44,7 +42,10 @@ update: yes version: "{{ gitBranch }}" - name: Update npm - shell: cd {{release_path}}/src && npm i && npm i redoc-cli + shell: cd {{release_path}}/src && npm i && npm i redoc-cli + + - name: Get config.json + shell: "curl --location --request GET '{{ vaultAddress }}projectSurveyEntityConfig' --header 'X-Vault-Token: {{ slurpfile['content'] | b64decode }}' | jq '.data.data' > '{{ release_path }}/config.json'" - name: Delete Old Folder shell: rm -rf {{ current_path }} && cd {{ project_path }} && mkdir entity-management @@ -84,4 +85,4 @@ - name: debug info debug: - msg: "Pm2 log {{pm2Info}}" + msg: "Pm2 log {{pm2Info}}" \ No newline at end of file