diff --git a/src/.env.sample b/src/.env.sample index 7f036e54..0be2f7e8 100644 --- a/src/.env.sample +++ b/src/.env.sample @@ -180,5 +180,8 @@ OBSERVATION_DEEP_LINK_REGEX= "^https:\/\/dev\.elevate-ml\.shikshalokam\.org\/vie # Mongo DB MONGODB_URL=mongodb://localhost:27017/elevate-project +# detault data manager roles - Roles designated to access dashboard in the consumption side +DEFAULT_DATA_MANAGERS="program_manager,program_designer" + #Role name which have permission to rollout the resourced DEFAULT_ROLLOUT_ROLES='rollout_manager' diff --git a/src/api-doc/SCP.postman_collection.json b/src/api-doc/Self Creation Portal.postman_collection.json similarity index 93% rename from src/api-doc/SCP.postman_collection.json rename to src/api-doc/Self Creation Portal.postman_collection.json index fa3b5aa7..734faf06 100644 --- a/src/api-doc/SCP.postman_collection.json +++ b/src/api-doc/Self Creation Portal.postman_collection.json @@ -1,10 +1,10 @@ { "info": { - "_postman_id": "a3d30138-61e3-4e62-a4ae-a3ce3362b692", + "_postman_id": "caab3242-e913-47da-9a05-5ee0c48ea607", "name": "Self Creation Portal", - "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", - "_exporter_id": "6350183", - "_collection_link": "https://cloudy-eclipse-8615.postman.co/workspace/MentorED~099846e5-b1d5-4043-84b9-1e9ac3fc1e4f/collection/6350183-a3d30138-61e3-4e62-a4ae-a3ce3362b692?action=share&source=collection_link&creator=6350183" + "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json", + "_exporter_id": "12011146", + "_collection_link": "https://crimson-star-573488.postman.co/workspace/backend~c1daa04f-8872-40f8-9e04-dcefba7434d5/collection/12011146-caab3242-e913-47da-9a05-5ee0c48ea607?action=share&source=collection_link&creator=12011146" }, "item": [ { @@ -30,11 +30,7 @@ } } }, - "url": { - "raw": "{{SCPBaseUrl}}scp/v1/role-permission-mapping/create", - "host": ["{{SCPBaseUrl}}scp"], - "path": ["v1", "role-permission-mapping", "create"] - } + "url": "{{SCPBaseUrl}}scp/v1/role-permission-mapping/create" }, "response": [] }, @@ -83,11 +79,7 @@ "type": "text" } ], - "url": { - "raw": "{{SCPBaseUrl}}scp/v1/role-permission-mapping/list", - "host": ["{{SCPBaseUrl}}scp"], - "path": ["v1", "role-permission-mapping", "list"] - } + "url": "{{SCPBaseUrl}}scp/v1/role-permission-mapping/list" }, "response": [] } @@ -119,11 +111,7 @@ } } }, - "url": { - "raw": "{{SCPBaseUrl}}scp/v1/permissions/create", - "host": ["{{SCPBaseUrl}}scp"], - "path": ["v1", "permissions", "create"] - } + "url": "{{SCPBaseUrl}}scp/v1/permissions/create" }, "response": [] }, @@ -251,11 +239,7 @@ } } }, - "url": { - "raw": "{{SCPBaseUrl}}scp/v1/modules/create", - "host": ["{{SCPBaseUrl}}scp"], - "path": ["v1", "modules", "create"] - } + "url": "{{SCPBaseUrl}}scp/v1/modules/create" }, "response": [] }, @@ -381,11 +365,7 @@ } } }, - "url": { - "raw": "{{SCPBaseUrl}}scp/v1/actions/update", - "host": ["{{SCPBaseUrl}}scp"], - "path": ["v1", "actions", "update"] - } + "url": "{{SCPBaseUrl}}scp/v1/actions/update" }, "response": [] }, @@ -547,11 +527,7 @@ } } }, - "url": { - "raw": "{{SCPBaseUrl}}scp/v1/form/create", - "host": ["{{SCPBaseUrl}}scp"], - "path": ["v1", "form", "create"] - } + "url": "{{SCPBaseUrl}}scp/v1/form/create" }, "response": [] }, @@ -610,11 +586,7 @@ } } }, - "url": { - "raw": "{{SCPBaseUrl}}scp/v1/form/read", - "host": ["{{SCPBaseUrl}}scp"], - "path": ["v1", "form", "read"] - } + "url": "{{SCPBaseUrl}}scp/v1/form/read" }, "response": [] }, @@ -668,11 +640,7 @@ } } }, - "url": { - "raw": "{{SCPBaseUrl}}scp/v1/entities/create", - "host": ["{{SCPBaseUrl}}scp"], - "path": ["v1", "entities", "create"] - }, + "url": "{{SCPBaseUrl}}scp/v1/entities/create", "description": "NOTE: Pass type as roles/designation/expertise etc. whatever types needs to be created" }, "response": [] @@ -748,11 +716,7 @@ "type": "text" } ], - "url": { - "raw": "{{SCPBaseUrl}}scp/v1/entities/read", - "host": ["{{SCPBaseUrl}}scp"], - "path": ["v1", "entities", "read"] - }, + "url": "{{SCPBaseUrl}}scp/v1/entities/read", "description": "NOTE: Pass type as roles/designation/expertise etc. whatever types needs to be created" }, "response": [] @@ -844,11 +808,7 @@ } } }, - "url": { - "raw": "{{SCPBaseUrl}}scp/v1/entity-types/create", - "host": ["{{SCPBaseUrl}}scp"], - "path": ["v1", "entity-types", "create"] - }, + "url": "{{SCPBaseUrl}}scp/v1/entity-types/create", "description": "NOTE: Pass type as roles/designation/expertise etc. whatever types needs to be created" }, "response": [] @@ -873,11 +833,7 @@ } } }, - "url": { - "raw": "{{SCPBaseUrl}}scp/v1/entity-types/update/1", - "host": ["{{SCPBaseUrl}}scp"], - "path": ["v1", "entity-types", "update", "1"] - }, + "url": "{{SCPBaseUrl}}scp/v1/entity-types/update/1", "description": "NOET: All body req parameters are optional to update." }, "response": [] @@ -893,11 +849,7 @@ "type": "text" } ], - "url": { - "raw": "{{SCPBaseUrl}}scp/v1/entity-types/delete/1", - "host": ["{{SCPBaseUrl}}scp"], - "path": ["v1", "entity-types", "delete", "1"] - } + "url": "{{SCPBaseUrl}}scp/v1/entity-types/delete/1" }, "response": [] }, @@ -912,11 +864,7 @@ "type": "text" } ], - "url": { - "raw": "{{SCPBaseUrl}}scp/v1/entity-types/read", - "host": ["{{SCPBaseUrl}}scp"], - "path": ["v1", "entity-types", "read"] - }, + "url": "{{SCPBaseUrl}}scp/v1/entity-types/read", "description": "NOTE: Query Parmas\n1). type is mandatory\n2). deleted is optional\n3). status is optional" }, "response": [] @@ -983,11 +931,7 @@ "type": "text" } ], - "url": { - "raw": "{{SCPBaseUrl}}scp/v1/config/list", - "host": ["{{SCPBaseUrl}}scp"], - "path": ["v1", "config", "list"] - } + "url": "{{SCPBaseUrl}}scp/v1/config/list" }, "response": [] } @@ -1009,18 +953,14 @@ ], "body": { "mode": "raw", - "raw": "{\n \"show_reviewer_list\": true,\n \"min_approval\": 2,\n \"resource_type\": \"project\",\n \"review_type\": \"SEQUENTIAL\",\n \"organization_id\":2,\n \"review_stages\": [\n {\n \"role\": \"content_creator\",\n \"level\": 1\n }\n ]\n}", + "raw": "{\n \"show_reviewer_list\": true,\n \"min_approval\": 2,\n \"resource_type\": \"project\",\n \"review_type\": \"SEQUENTIAL\",\n \"review_stages\": [\n {\n \"role\": \"content_creator\",\n \"level\": 1\n }\n ],\n \"data_managers\" : [\"mentee\"]\n}", "options": { "raw": { "language": "json" } } }, - "url": { - "raw": "{{SCPBaseUrl}}scp/v1/organization-extensions/createConfig", - "host": ["{{SCPBaseUrl}}scp"], - "path": ["v1", "organization-extensions", "createConfig"] - } + "url": "{{selfCreationPortalBaseUrl}}scp/v1/organization-extensions/createConfig" }, "response": [] }, @@ -1082,11 +1022,7 @@ } } }, - "url": { - "raw": "{{SCPBaseUrl}}scp/v1/projects/update", - "host": ["{{SCPBaseUrl}}scp"], - "path": ["v1", "projects", "update"] - } + "url": "{{SCPBaseUrl}}scp/v1/projects/update" }, "response": [] }, @@ -1644,6 +1580,20 @@ } }, "response": [] + }, + { + "name": "Get Data Manager list", + "request": { + "method": "GET", + "header": [ + { + "key": "X-auth-token", + "value": "bearer {{token}}" + } + ], + "url": "{{selfCreationPortalBaseUrl}}scp/v1/rollouts/getDataManagers" + }, + "response": [] } ] }, @@ -1660,11 +1610,7 @@ "value": "bearer {{token}}" } ], - "url": { - "raw": "{{SCPBaseUrl}}scp/v1/certificates/list", - "host": ["{{SCPBaseUrl}}scp"], - "path": ["v1", "certificates", "list"] - } + "url": "{{SCPBaseUrl}}scp/v1/certificates/list" }, "response": [] }, @@ -1688,11 +1634,7 @@ } } }, - "url": { - "raw": "{{SCPBaseUrl}}scp/v1/certificate/update", - "host": ["{{SCPBaseUrl}}scp"], - "path": ["v1", "certificate", "update"] - } + "url": "{{SCPBaseUrl}}scp/v1/certificate/update" }, "response": [] }, @@ -1716,11 +1658,7 @@ } } }, - "url": { - "raw": "{{SCPBaseUrl}}scp/v1/certificate/update:/id", - "host": ["{{SCPBaseUrl}}scp"], - "path": ["v1", "certificate", "update:", "id"] - } + "url": "{{SCPBaseUrl}}scp/v1/certificate/update:/id" }, "response": [] } @@ -1763,11 +1701,7 @@ } } }, - "url": { - "raw": "{{UserBaseURL}}user/v1/account/create", - "host": ["{{UserBaseURL}}user"], - "path": ["v1", "account", "create"] - } + "url": "{{UserBaseURL}}user/v1/account/create" }, "response": [] }, @@ -1835,11 +1769,7 @@ } ] }, - "url": { - "raw": "{{UserBaseURL}}user/v1/account/login", - "host": ["{{UserBaseURL}}user"], - "path": ["v1", "account", "login"] - } + "url": "{{UserBaseURL}}user/v1/account/login" }, "response": [] }, @@ -1918,11 +1848,7 @@ } } }, - "url": { - "raw": "{{SCPBaseUrl}}scp/v1/cloud-services/file/getDownloadableUrl", - "host": ["{{SCPBaseUrl}}scp"], - "path": ["v1", "cloud-services", "file", "getDownloadableUrl"] - } + "url": "{{SCPBaseUrl}}scp/v1/cloud-services/file/getDownloadableUrl" }, "response": [] }, @@ -1952,11 +1878,7 @@ } } }, - "url": { - "raw": "{{SCPBaseUrl}}scp/v1/cloud-services/file/getSignedUrl", - "host": ["{{SCPBaseUrl}}scp"], - "path": ["v1", "cloud-services", "file", "getSignedUrl"] - } + "url": "{{SCPBaseUrl}}scp/v1/cloud-services/file/getSignedUrl" }, "response": [] } @@ -1985,11 +1907,7 @@ } } }, - "url": { - "raw": "{{UserBaseURL}}user/v1/organization/create", - "host": ["{{UserBaseURL}}user"], - "path": ["v1", "organization", "create"] - } + "url": "{{UserBaseURL}}user/v1/organization/create" }, "response": [] }, @@ -2013,11 +1931,7 @@ } } }, - "url": { - "raw": "{{UserBaseURL}}user/v1/organization/update/3", - "host": ["{{UserBaseURL}}user"], - "path": ["v1", "organization", "update", "3"] - } + "url": "{{UserBaseURL}}user/v1/organization/update/3" }, "response": [] }, @@ -2265,11 +2179,7 @@ "type": "text" } ], - "url": { - "raw": "{{SCPBaseUrl}}scp/v1/reviews/start/2417", - "host": ["{{SCPBaseUrl}}scp"], - "path": ["v1", "reviews", "start", "2417"] - } + "url": "{{SCPBaseUrl}}scp/v1/reviews/start/2417" }, "response": [] }, @@ -2364,11 +2274,7 @@ } } }, - "url": { - "raw": "{{SCPBaseUrl}}scp/v1/reviews/rejectOrReport/2406", - "host": ["{{SCPBaseUrl}}scp"], - "path": ["v1", "reviews", "rejectOrReport", "2406"] - } + "url": "{{SCPBaseUrl}}scp/v1/reviews/rejectOrReport/2406" }, "response": [] }, diff --git a/src/controllers/v1/rollouts.js b/src/controllers/v1/rollouts.js index dbab794f..36979feb 100644 --- a/src/controllers/v1/rollouts.js +++ b/src/controllers/v1/rollouts.js @@ -45,7 +45,27 @@ module.exports = class rollouts { } /** - * Details Rollout. + * getDataManagers list. + * @method + * @name getDataManagers + * @param {String} orgId - organization id + * @returns {JSON} - get the list of data managers + */ + + async getDataManagers(req) { + try { + const dataManagers = await rolloutService.getDataManagers( + req.decodedToken.organization_id, + req.pageNo, + req.pageSize + ) + return dataManagers + } catch (error) { + return error + } + } + + /* Details Rollout. * @method * @name details * @param {Object} req user request. diff --git a/src/database/migrations/20241127141243-create-org-configs-table.js b/src/database/migrations/20241127141243-create-org-configs-table.js new file mode 100644 index 00000000..7cd3eff3 --- /dev/null +++ b/src/database/migrations/20241127141243-create-org-configs-table.js @@ -0,0 +1,44 @@ +'use strict' +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + async up(queryInterface, Sequelize) { + await queryInterface.createTable('organization_configs', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: Sequelize.INTEGER, + }, + organization_id: { + allowNull: false, + primaryKey: true, + type: Sequelize.STRING, + }, + meta: { + allowNull: true, + type: Sequelize.JSONB, + }, + created_at: { + allowNull: false, + type: Sequelize.DATE, + }, + updated_at: { + allowNull: false, + type: Sequelize.DATE, + }, + deleted_at: { + type: Sequelize.DATE, + }, + }) + await queryInterface.addIndex('organization_configs', ['organization_id'], { + unique: true, + name: 'unique_organization_id', + where: { + deleted_at: null, + }, + }) + }, + async down(queryInterface, Sequelize) { + await queryInterface.dropTable('organization_configs') + }, +} diff --git a/src/database/models/organizationConfig.js b/src/database/models/organizationConfig.js new file mode 100644 index 00000000..a8073c59 --- /dev/null +++ b/src/database/models/organizationConfig.js @@ -0,0 +1,39 @@ +'use strict' +module.exports = (sequelize, DataTypes) => { + const organizationConfig = sequelize.define( + 'organizationConfig', + { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: DataTypes.INTEGER, + }, + organization_id: { + type: DataTypes.STRING, + allowNull: false, + primaryKey: true, + }, + meta: { + allowNull: true, + type: DataTypes.JSONB, + }, + }, + { + sequelize, + modelName: 'organizationConfig', + tableName: 'organization_configs', + freezeTableName: true, + paranoid: true, + indexes: [ + { + unique: true, + fields: ['organization_id', 'resource_type'], + name: 'unique_org_resource_type', + }, + ], + } + ) + + return organizationConfig +} diff --git a/src/database/queries/organizationConfig.js b/src/database/queries/organizationConfig.js new file mode 100644 index 00000000..6f2201da --- /dev/null +++ b/src/database/queries/organizationConfig.js @@ -0,0 +1,43 @@ +const organizationConfig = require('../models/index').organizationConfig + +exports.findOne = async (filter, attributes = []) => { + try { + return await organizationConfig.findOne({ + where: filter, + attributes, + raw: true, + }) + } catch (error) { + return error + } +} + +exports.create = async (data) => { + try { + return await organizationConfig.create(data, { returning: true }) + } catch (error) { + return error + } +} + +exports.update = async (filter, update, options = {}) => { + try { + return await organizationConfig.update(update, { + where: filter, + ...options, + }) + } catch (error) { + return error + } +} + +exports.upsert = async (values, filter, options = {}) => { + try { + return await organizationConfig.upsert(values, { + ...options, + where: filter, + }) + } catch (error) { + return error + } +} diff --git a/src/distributionColumns.psql b/src/distributionColumns.psql index 3be7ca2e..68ee97bb 100644 --- a/src/distributionColumns.psql +++ b/src/distributionColumns.psql @@ -11,4 +11,7 @@ SELECT create_distributed_table('review_resources', 'reviewer_id'); SELECT create_distributed_table('review_stages', 'organization_id'); SELECT create_distributed_table('reviews', 'organization_id'); SELECT create_distributed_table('activities', 'object_id'); +SELECT create_distributed_table('organization_configs', 'organization_id'); + + SELECT create_distributed_table('rollouts', 'organization_id'); \ No newline at end of file diff --git a/src/envVariables.js b/src/envVariables.js index a88138bf..fe1f69a2 100644 --- a/src/envVariables.js +++ b/src/envVariables.js @@ -194,6 +194,11 @@ let environmentVariables = { value: 'true', }, }, + DEFAULT_DATA_MANAGERS: { + message: 'Default data managers required.', + optional: true, + default: 'program_manager,program_designer', + }, DEFAULT_ROLLOUT_ROLES: { message: 'Default rollout role is required', optional: true, diff --git a/src/integration-tests/rollouts/responseSchema.js b/src/integration-tests/rollouts/responseSchema.js index bfd9302b..51c0e45a 100644 --- a/src/integration-tests/rollouts/responseSchema.js +++ b/src/integration-tests/rollouts/responseSchema.js @@ -1,3 +1,116 @@ +const getDataManagersSchema = { + type: 'object', + properties: { + responseCode: { + type: 'string', + }, + message: { + type: 'string', + }, + result: { + type: 'object', + properties: { + data: { + type: 'array', + items: [ + { + type: 'object', + properties: { + id: { + type: 'integer', + }, + name: { + type: 'string', + }, + email: { + type: 'string', + }, + about: { + type: 'null', + }, + image: { + type: 'null', + }, + organization: { + type: 'object', + properties: { + id: { + type: 'integer', + }, + code: { + type: 'string', + }, + name: { + type: 'string', + }, + }, + required: ['id', 'code', 'name'], + }, + }, + required: ['id', 'name', 'email', 'about', 'image', 'organization'], + }, + ], + }, + count: { + type: 'integer', + }, + }, + required: ['data', 'count'], + }, + meta: { + type: 'object', + properties: { + formsVersion: { + type: 'array', + items: {}, + }, + correlation: { + type: 'string', + }, + }, + required: [], + }, + }, + required: ['responseCode', 'message', 'result', 'meta'], +} +const getDataManagersEmptyResponseSchema = { + type: 'object', + properties: { + responseCode: { + type: 'string', + }, + message: { + type: 'string', + }, + result: { + type: 'object', + properties: { + data: { + type: 'array', + items: {}, + }, + count: { + type: 'integer', + }, + }, + required: ['data', 'count'], + }, + meta: { + type: 'object', + properties: { + formsVersion: { + type: 'array', + items: {}, + }, + correlation: { + type: 'string', + }, + }, + required: [], + }, + }, + required: ['responseCode', 'message', 'result', 'meta'], +} const getRolloutsListSchema = { type: 'object', properties: { @@ -311,6 +424,8 @@ const rolloutDetailResponseSchema = { required: ['responseCode', 'error', 'meta', 'message'], } module.exports = { + getDataManagersSchema, + getDataManagersEmptyResponseSchema, getRolloutsListSchema, getRolloutsListEmptyResponseSchema, rolloutDetailResponseSchema, diff --git a/src/integration-tests/rollouts/rollouts.spec.js b/src/integration-tests/rollouts/rollouts.spec.js index 7895b802..160054a0 100644 --- a/src/integration-tests/rollouts/rollouts.spec.js +++ b/src/integration-tests/rollouts/rollouts.spec.js @@ -17,6 +17,15 @@ describe('Rollout APIs', function () { } }) + it('Get list of data managers list', async () => { + let res = await request.get('/scp/v1/rollouts/getDataManagers').query({ page: 1, limit: 10 }) + expect(res.statusCode).toBe(200) + if (res.body?.result.length == 0) { + expect(res.body).toMatchSchema(schema.getDataManagersEmptyResponseSchema) + } + expect(res.body).toMatchSchema(schema.getDataManagersSchema) + }) + it('Get list of Rollouts', async () => { let res = await request.get('/scp/v1/rollouts/list').query({ page: 1, limit: 10 }) expect(res.statusCode).toBe(200) diff --git a/src/locales/en.json b/src/locales/en.json index d89e546e..2cac5d9e 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -135,8 +135,8 @@ "FAILED_TO_CREATE_CONFIG": "Organization config creation failed", "RESOURCE_STAGE_FETCHED": "Resource stage fetched", "ROLLOUT_NOT_FOUND": "Rollout not found", - "ROLLOUT_CREATED_SUCCESSFULLY": "Saved successfully", "DEPENDED_ENTITY_TYPE_NOT_FOUND": "Invalid depended entity type value.", + "DATA_MANAGER_LIST_FETCHED": "Data Manager list fetched successfully.", "ROLLOUT_LISTED_SUCCESSFULLY": "Rollout Listed successfully", "ROLLOUT_SAVED_SUCCESSFULLY": "Saved successfully", "CANT_CHANGE_RESOURCE": "You cant change the resource of published rollout", diff --git a/src/services/organization-extension.js b/src/services/organization-extension.js index 12b786b7..ce3eeee1 100644 --- a/src/services/organization-extension.js +++ b/src/services/organization-extension.js @@ -9,6 +9,7 @@ const _ = require('lodash') const userRequests = require('@requests/user') const utils = require('@generics/utils') const organizationExtensionsQueries = require('@database/queries/organizationExtensions') +const organizationConfigQueries = require('@database/queries/organizationConfig') module.exports = class orgExtensionsHelper { /** * Create Organization Config. @@ -21,8 +22,18 @@ module.exports = class orgExtensionsHelper { static async createConfig(bodyData, organization_id) { try { bodyData.organization_id = organization_id - const { resource_type, review_stages, review_type } = bodyData - + const { resource_type, review_stages, review_type, data_managers } = bodyData + // check if body have data_managers + if (data_managers?.length) { + await organizationConfigQueries.upsert( + { + organization_id, + meta: { data_managers }, + updated_at: new Date(), + }, + { organization_id } + ) + } const validResourceTypes = process.env.RESOURCE_TYPES.split(',') if (!validResourceTypes.includes(resource_type)) { return responses.failureResponse({ @@ -119,7 +130,19 @@ module.exports = class orgExtensionsHelper { }) } - const { review_stages, review_type } = bodyData + const { review_stages, review_type, data_managers } = bodyData + + // check if body have data_managers + if (data_managers?.length) { + await organizationConfigQueries.upsert( + { + organization_id, + meta: { data_managers }, + updated_at: new Date(), + }, + { organization_id } + ) + } const filter = { id: id, @@ -242,12 +265,29 @@ module.exports = class orgExtensionsHelper { organization_id, } let result = { + config: {}, resource: [], instance: { auto_save_interval: utils.convertToInteger(process.env.RESOURCE_AUTO_SAVE_TIMER), note_length: utils.convertToInteger(process.env.MAX_RESOURCE_NOTE_LENGTH), }, } + // fetch org config for organization_id + const orgConfig = await organizationConfigQueries.findOne( + { + organization_id, + }, + ['meta'] + ) + + if (orgConfig?.meta && Object.keys(orgConfig.meta).length > 0) { + result.config = orgConfig?.meta + } + + if (orgConfig?.meta?.data_managers?.length == 0 || orgConfig?.meta?.data_managers?.length == undefined) { + result.config.data_managers = process.env.DEFAULT_DATA_MANAGERS.split(',') + } + // attributes to fetch from organisation Extenstion const attributes = common.INSTANCE_LEVEL_CONFIG_ATTRIBUTES diff --git a/src/services/rollouts.js b/src/services/rollouts.js index d379b584..b79782ea 100644 --- a/src/services/rollouts.js +++ b/src/services/rollouts.js @@ -7,8 +7,8 @@ const common = require('@constants/common') const rolloutQueries = require('@database/queries/rollouts') const resourceService = require('@services/resource') const resourceQueries = require('@database/queries/resources') +const orgExtensionService = require('@services/organization-extension') const filesService = require('@services/files') -const orgExtension = require('@services/organization-extension') const userRequests = require('@requests/user') const { Op } = require('sequelize') module.exports = class RolloutsHelper { @@ -179,7 +179,9 @@ module.exports = class RolloutsHelper { } // fetch the org details from user service - const organizationDetails = await orgExtension.fetchOrganizationDetails([rollout.organization_id]) + const organizationDetails = await orgExtensionService.fetchOrganizationDetails([ + rollout.organization_id, + ]) if (organizationDetails?.[rollout.organization_id]) { resultData.organization = _.pick(organizationDetails[rollout.organization_id], [ 'id', @@ -202,7 +204,42 @@ module.exports = class RolloutsHelper { } /** - * Rollout List + * Get Data Managers list + * @method + * @name getDataManagers + * @param orgId - Organization Id + * @param pageNo - Page number + * @param pageSize - Page size + * @returns {JSON} - List of data managers + */ + static async getDataManagers(orgId, pageNo, pageSize) { + try { + // get org config based on orgId + const orgConfigs = await orgExtensionService.getConfig(orgId) + // identify the roles have data manager access + const dataManagerRoles = orgConfigs?.result?.config?.data_managers + // fetch the users from user service + const dataManagersList = await userRequests.list(dataManagerRoles.join(','), pageNo, pageSize, '', orgId) + let result = { + data: [], + count: 0, + } + + if (dataManagersList.success && dataManagersList?.data?.result?.data.length) { + result = dataManagersList?.data?.result + } + + return responses.successResponse({ + statusCode: httpStatusCode.ok, + message: 'DATA_MANAGER_LIST_FETCHED', + result, + }) + } catch (error) { + throw error + } + } + + /* Rollout List * @method * @name list * @param {String} organization_id @@ -219,6 +256,7 @@ module.exports = class RolloutsHelper { data: [], count: 0, } + let filters = { organization_id, user_id: loggedInUserId, @@ -282,7 +320,7 @@ module.exports = class RolloutsHelper { const userDetails = await this.fetchUserDetails([loggedInUserId]) // fetch the org details from user service - const orgDetails = await orgExtension.fetchOrganizationDetails(orgList) + const orgDetails = await orgExtensionService.fetchOrganizationDetails(orgList) let rolloutFinalList = [] diff --git a/src/validators/v1/organization-extensions.js b/src/validators/v1/organization-extensions.js index 794cbded..a6ad0de5 100644 --- a/src/validators/v1/organization-extensions.js +++ b/src/validators/v1/organization-extensions.js @@ -16,6 +16,13 @@ module.exports = { .withMessage('resource_type field is empty') .isIn(allowedResourceTypes) .withMessage(`resource_type is invalid, must be one of: ${allowedResourceTypes.join(', ')}`) + req.checkBody('data_managers') + .trim() + .optional({ checkFalsy: true }) + .notEmpty() + .withMessage('data_managers field is empty') + .matches(/^[A-Za-z]+(?:\s*,\s*[A-Za-z]+)*$/) + .withMessage('data_managers must be a comma-separated list of alphabetic strings') }, updateConfig: (req) => { @@ -35,6 +42,30 @@ module.exports = { .isIn(allowedResourceTypes) .withMessage(`resource_type is invalid, must be one of: ${allowedResourceTypes.join(', ')}`) + req.checkBody('data_managers') + .optional({ checkFalsy: true }) + .custom((value) => { + // Allow empty array explicitly + if (Array.isArray(value) && value.length === 0) { + return true + } + + // If it's an array, validate each element + if (Array.isArray(value)) { + return value.every((item) => typeof item === 'string' && /^[A-Za-z]+$/.test(item)) + } + + // If it's a string, validate the pattern + if (typeof value === 'string') { + return /^[A-Za-z]+(?:\s*,\s*[A-Za-z]+)*$/.test(value) + } + + // Reject other types + return false + }) + .withMessage( + 'data_managers must be a comma-separated list of alphabetic strings, an array of alphabetic strings, or an empty array' + ) req.checkBody('organization_id') .trim() .optional({ checkFalsy: true })