From 08bd39d62290f5d7ce52fcaf84521c653d78becf Mon Sep 17 00:00:00 2001 From: adithya_dinesh Date: Tue, 16 Sep 2025 17:41:08 +0530 Subject: [PATCH 01/11] @coderabbitai --- src/generics/utils.js | 17 +++++++++------ src/helpers/userInvite.js | 46 +++++++++++++++++++++++++++++++-------- 2 files changed, 47 insertions(+), 16 deletions(-) diff --git a/src/generics/utils.js b/src/generics/utils.js index 7eb3b01b..576325c6 100644 --- a/src/generics/utils.js +++ b/src/generics/utils.js @@ -792,6 +792,12 @@ async function fetchAndMapAllExternalEntities(entities, service, endPoint, tenan const url = constructUrl(externalBaseUrl, endPoint) const projection = ['_id', 'metaInformation.name', 'metaInformation.externalId', 'entityType'] let data = [] + let query = { + 'metaInformation.name': { + $in: entities, // Dynamically pass the array here + }, + tenantId: tenantCode, + } await axios({ method: 'post', @@ -801,12 +807,7 @@ async function fetchAndMapAllExternalEntities(entities, service, endPoint, tenan 'internal-access-token': process.env.INTERNAL_ACCESS_TOKEN, }, data: { - query: { - 'metaInformation.name': { - $in: entities, // Dynamically pass the array here - }, - tenantId: tenantCode, - }, + query, projection, }, }) @@ -818,7 +819,9 @@ async function fetchAndMapAllExternalEntities(entities, service, endPoint, tenan }) responseBody = data.reduce((acc, { _id, entityType, metaInformation }) => { - const key = metaInformation?.name?.replaceAll(/\s+/g, '').toLowerCase() + const key = `${metaInformation?.name?.replaceAll(/\s+/g, '').toLowerCase()}${entityType + .replaceAll(/\s+/g, '') + .toLowerCase()}` if (key) { acc[key] = { _id, name: metaInformation?.name, entityType, externalId: metaInformation.externalId } } diff --git a/src/helpers/userInvite.js b/src/helpers/userInvite.js index b229b2aa..216d7661 100644 --- a/src/helpers/userInvite.js +++ b/src/helpers/userInvite.js @@ -273,31 +273,59 @@ module.exports = class UserInviteHelper { // Extract and prepare meta fields row.meta = { block: row?.block - ? externalEntityNameIdMap?.[row.block?.replaceAll(/\s+/g, '').toLowerCase()]?._id || null + ? externalEntityNameIdMap?.[ + `${row.block?.replaceAll(/\s+/g, '').toLowerCase()}${'block' + .replaceAll(/\s+/g, '') + .toLowerCase()}` + ]?._id || null : '', state: row?.state - ? externalEntityNameIdMap?.[row.state?.replaceAll(/\s+/g, '').toLowerCase()]?._id || null + ? externalEntityNameIdMap?.[ + `${row.state?.replaceAll(/\s+/g, '').toLowerCase()}${'state' + .replaceAll(/\s+/g, '') + .toLowerCase()}` + ]?._id || null : '', school: row?.school - ? externalEntityNameIdMap?.[row.school?.replaceAll(/\s+/g, '').toLowerCase()]?._id || null + ? externalEntityNameIdMap?.[ + `${row.school?.replaceAll(/\s+/g, '').toLowerCase()}${'school' + .replaceAll(/\s+/g, '') + .toLowerCase()}` + ]?._id || null : '', cluster: row?.cluster - ? externalEntityNameIdMap?.[row.cluster?.replaceAll(/\s+/g, '').toLowerCase()]?._id || null + ? externalEntityNameIdMap?.[ + `${row.cluster?.replaceAll(/\s+/g, '').toLowerCase()}${'cluster' + .replaceAll(/\s+/g, '') + .toLowerCase()}` + ]?._id || null : '', district: row?.district - ? externalEntityNameIdMap?.[row.district?.replaceAll(/\s+/g, '').toLowerCase()]?._id || null + ? externalEntityNameIdMap?.[ + `${row.district?.replaceAll(/\s+/g, '').toLowerCase()}${'district' + .replaceAll(/\s+/g, '') + .toLowerCase()}` + ]?._id || null : '', professional_role: row?.professional_role - ? externalEntityNameIdMap?.[row.professional_role?.replaceAll(/\s+/g, '').toLowerCase()] - ?._id || '' + ? externalEntityNameIdMap?.[ + `${row.professional_role?.replaceAll(/\s+/g, '').toLowerCase()}${'professional_role' + .replaceAll(/\s+/g, '') + .toLowerCase()}` + ]?._id || '' : '', professional_subroles: row?.professional_subroles ? row.professional_subroles .split(',') .map( (prof_subRole) => - externalEntityNameIdMap[prof_subRole?.replaceAll(/\s+/g, '').toLowerCase()] - ?._id + externalEntityNameIdMap[ + `${prof_subRole + ?.replaceAll(/\s+/g, '') + .toLowerCase()}${'professional_subroles' + .replaceAll(/\s+/g, '') + .toLowerCase()}` + ]?._id ) || [] : [], } From bd62e19928b6e4e3c17936ca506d3b9cef9201ab Mon Sep 17 00:00:00 2001 From: Nevil Mathew Date: Wed, 24 Sep 2025 14:16:08 +0530 Subject: [PATCH 02/11] fix: improve validation messages and add query parameter checks for user roles --- src/validators/v1/user-role.js | 92 +++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 36 deletions(-) diff --git a/src/validators/v1/user-role.js b/src/validators/v1/user-role.js index 776435ac..627ad485 100644 --- a/src/validators/v1/user-role.js +++ b/src/validators/v1/user-role.js @@ -1,5 +1,6 @@ const filterRequestBody = require('../common') const { userRole } = require('@constants/blacklistConfig') +const common = require('@constants/common') const validateList = (req, allowedVariables) => { allowedVariables.forEach((variable) => { req.checkQuery(variable) @@ -12,88 +13,107 @@ const validateList = (req, allowedVariables) => { module.exports = { create: (req) => { req.body = filterRequestBody(req.body, userRole.create) + req.checkBody('title') .trim() .notEmpty() - .withMessage('title field is empty') + .withMessage('title is required') .matches(/^[a-z_]+$/) - .withMessage('title is invalid, must not contain spaces') + .withMessage('title must contain only lowercase letters (a–z) and underscores') req.checkBody('user_type') .trim() .notEmpty() - .withMessage('userType field is empty') - .matches(/^[0-9]+$/) - .withMessage('userType is invalid, must not contain spaces') + .withMessage('user_type is required') + .isIn(['0', '1']) + .withMessage('user_type must be 0 (non-admin) or 1 (admin)') req.checkBody('visibility') .trim() .notEmpty() - .withMessage('visibility field is empty') - .matches(/^[A-Z_]+$/) - .withMessage('visibility is invalid, must not contain spaces') + .withMessage('visibility is required') + .isIn(['PUBLIC']) + .withMessage('visibility must be PUBLIC') req.checkBody('label') .trim() .notEmpty() - .withMessage('label field is empty') + .withMessage('label is required') .matches(/^[A-Z][a-zA-Z\s]*$/) - .withMessage('label is invalid, first letter must be capital') + .withMessage('label must start with an uppercase letter and contain only letters and spaces') req.checkBody('status') - .trim() - .matches(/^[A-Za-z]*$/) - .withMessage('status is invalid, must not contain spaces') .optional({ checkFalsy: true }) - .notEmpty() - .withMessage('status field must be a non-empty string when provided') + .trim() + .isIn([common.ACTIVE_STATUS, common.INACTIVE_STATUS]) + .withMessage(`status must be either ${common.ACTIVE_STATUS} or ${common.INACTIVE_STATUS} when provided`) }, update: (req) => { req.body = filterRequestBody(req.body, userRole.update) - req.checkParams('id').notEmpty().withMessage('id param is empty') + + req.checkParams('id').notEmpty().withMessage('id param is required') req.checkBody('title') .trim() .notEmpty() - .withMessage('title field is empty') + .withMessage('title is required') .matches(/^[a-z_]+$/) - .withMessage('title is invalid, must not contain spaces') + .withMessage('title must contain only lowercase letters (a–z) and underscores') req.checkBody('user_type') .trim() .notEmpty() - .withMessage('userType field is empty') - .matches(/^[0-9]+$/) - .withMessage('userType is invalid, must not contain spaces') + .withMessage('user_type is required') + .isIn(['0', '1']) + .withMessage('user_type must be 0 (non-admin) or 1 (admin)') req.checkBody('visibility') .trim() .notEmpty() - .withMessage('visibility field is empty') - .matches(/^[A-Z_]+$/) - .withMessage('visibility is invalid, must not contain spaces') + .withMessage('visibility is required') + .isIn(['PUBLIC']) + .withMessage('visibility must be PUBLIC') req.checkBody('status') - .trim() - .matches(/^[A-Za-z]*$/) - .withMessage('status is invalid, must not contain spaces') .optional({ checkFalsy: true }) - .notEmpty() - .withMessage('status field must be a non-empty string when provided') + .trim() + .isIn([common.ACTIVE_STATUS, common.INACTIVE_STATUS]) + .withMessage(`status must be either ${common.ACTIVE_STATUS} or ${common.INACTIVE_STATUS} when provided`) }, - delete: (req) => { req.checkParams('id').notEmpty().withMessage('id param is empty') }, list: (req) => { - const allowedVariables = ['title', 'user_type', 'visibility', 'organization_id', 'status'] - validateList(req, allowedVariables) - }, + req.checkQuery('title') + .optional() + .trim() + .matches(/^[a-z_]+$/) + .withMessage('title is invalid. Use only lowercase letters a–z and underscores') + + req.checkQuery('user_type') + .optional() + .trim() + .isIn(['0', '1']) + .withMessage('user_type is invalid. Allowed values: 0 (non-admin) or 1 (admin)') + + req.checkQuery('visibility') + .optional() + .trim() + .isIn(['PUBLIC']) + .withMessage('visibility is invalid. Allowed value: PUBLIC') + + req.checkQuery('status') + .optional() + .trim() + .isIn([common.ACTIVE_STATUS, common.INACTIVE_STATUS]) + .withMessage(`status must be either ${common.ACTIVE_STATUS} or ${common.INACTIVE_STATUS} when provided`) - default: (req) => { - const allowedVariables = ['title', 'user_type', 'visibility', 'organization_id', 'status'] - validateList(req, allowedVariables) + req.checkQuery('organization_id') + .optional() + .trim() + .matches(/^[0-9]+$/) + .withMessage('organization_id is invalid. Must be numeric') }, } From 4531441a5db1a6faaef03203304c50afe880f204 Mon Sep 17 00:00:00 2001 From: Nevil Mathew Date: Wed, 24 Sep 2025 15:01:50 +0530 Subject: [PATCH 03/11] feat: add profileById validation for user queries --- src/locales/en.json | 3 ++- src/validators/v1/user.js | 56 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/locales/en.json b/src/locales/en.json index 21726f8a..250a8982 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -178,5 +178,6 @@ "ORG_UNIQUE_CONSTRAIN_ERROR": "Organization Creation / Updation Failed. code / registration_code not unique.", "REG_CODE_ERROR": "registration_code is not valid or unique.", "INVALID_REG_CODE_ERROR": "registration_codes {{errorMessage}}. Invalid Code(s) : {{errorValues}}", - "UNIQUE_CONSTRAINT_ERROR": "{{fields}} is Invalid." + "UNIQUE_CONSTRAINT_ERROR": "{{fields}} is Invalid.", + "USER_PROFILE_FETCHED_SUCCESSFULLY": "User profile fetched successfully!" } diff --git a/src/validators/v1/user.js b/src/validators/v1/user.js index 2dd1ea68..0e257526 100644 --- a/src/validators/v1/user.js +++ b/src/validators/v1/user.js @@ -39,4 +39,60 @@ module.exports = { .isString() .withMessage('preferred_language must be string') }, + profileById: (req) => { + // id (numeric only) + req.checkParams('id') + .optional() + .trim() + .matches(/^[0-9]+$/) + .withMessage('id is invalid. Must be numeric') + + // email + req.checkQuery('email') + .optional() + .trim() + .isEmail() + .withMessage('email is invalid. Must be a valid email format') + // username + req.checkQuery('username') + .optional() + .trim() + .matches(/^(?:[a-z0-9_-]{3,40}|[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,})$/) //accept random string (min 3 max 40) of smaller case letters _ - and numbers OR email in lowercase as username + .withMessage('username is invalid') + // phone + req.checkQuery('phone') + .optional() + .trim() + .matches(/^[0-9]{7,15}$/) + .withMessage('phone is invalid. Must be digits only, length 7–15') + + // phone_code + req.checkQuery('phone_code') + .optional() + .trim() + .matches(/^\+[0-9]{1,4}$/) + .withMessage('phone_code is invalid. Must start with + and contain 1–4 digits') + + // tenant_code + req.checkQuery('tenant_code') + .trim() + .matches(/^[A-Za-z0-9_-]+$/) + .withMessage('tenant_code is invalid. Only letters, numbers, underscore, and hyphen allowed') + + if (!req.params.id) { + req.checkQuery(['email', 'username', 'phone', 'phone_code']).custom(() => { + const { email, username, phone, phone_code } = req.query + + if (!email && !username && !phone) { + throw new Error('At least one of id, email, username, or phone must be provided') + } + + if (phone && !phone_code) { + throw new Error('phone_code is required when phone is provided') + } + + return true + }) + } + }, } From 6643cfdab2b91efb5d696b18fb19374f30ddf382 Mon Sep 17 00:00:00 2001 From: adithya_dinesh Date: Wed, 24 Sep 2025 15:21:08 +0530 Subject: [PATCH 04/11] bulk upload changed from name to external id --- src/generics/utils.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/generics/utils.js b/src/generics/utils.js index 576325c6..022e5983 100644 --- a/src/generics/utils.js +++ b/src/generics/utils.js @@ -792,12 +792,6 @@ async function fetchAndMapAllExternalEntities(entities, service, endPoint, tenan const url = constructUrl(externalBaseUrl, endPoint) const projection = ['_id', 'metaInformation.name', 'metaInformation.externalId', 'entityType'] let data = [] - let query = { - 'metaInformation.name': { - $in: entities, // Dynamically pass the array here - }, - tenantId: tenantCode, - } await axios({ method: 'post', @@ -807,7 +801,12 @@ async function fetchAndMapAllExternalEntities(entities, service, endPoint, tenan 'internal-access-token': process.env.INTERNAL_ACCESS_TOKEN, }, data: { - query, + query: { + 'metaInformation.externalId': { + $in: entities, // Dynamically pass the array here + }, + tenantId: tenantCode, + }, projection, }, }) @@ -819,9 +818,11 @@ async function fetchAndMapAllExternalEntities(entities, service, endPoint, tenan }) responseBody = data.reduce((acc, { _id, entityType, metaInformation }) => { - const key = `${metaInformation?.name?.replaceAll(/\s+/g, '').toLowerCase()}${entityType - .replaceAll(/\s+/g, '') - .toLowerCase()}` + const normalize = (s) => (s ?? '').toString().replace(/\s+/g, '').toLowerCase() + const namePart = normalize(metaInformation?.externalId) + const typePart = normalize(entityType) + if (!namePart || !typePart) return acc + const key = `${namePart}${typePart}` if (key) { acc[key] = { _id, name: metaInformation?.name, entityType, externalId: metaInformation.externalId } } From 6ad81de35b8167604bc0e9627c08b0d3a3c0894f Mon Sep 17 00:00:00 2001 From: Nevil Mathew Date: Thu, 25 Sep 2025 17:59:22 +0530 Subject: [PATCH 05/11] add: create migration for unique user roles index with soft-delete handling --- ...20250925122507-update-unique-user-roles.js | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/database/migrations/20250925122507-update-unique-user-roles.js diff --git a/src/database/migrations/20250925122507-update-unique-user-roles.js b/src/database/migrations/20250925122507-update-unique-user-roles.js new file mode 100644 index 00000000..84b78264 --- /dev/null +++ b/src/database/migrations/20250925122507-update-unique-user-roles.js @@ -0,0 +1,32 @@ +// 20250925120000-update-unique-user-roles.js +module.exports = { + up: async (queryInterface, Sequelize) => { + const table = 'user_roles' + + // drop old constraint if it exists + await queryInterface.sequelize.query( + `ALTER TABLE "${table}" DROP CONSTRAINT IF EXISTS unique_title_org_id_tenant_code;` + ) + + // create partial unique index ignoring soft-deleted rows + await queryInterface.sequelize.query( + `CREATE UNIQUE INDEX IF NOT EXISTS unique_title_org_id_tenant_code + ON "${table}" (title, organization_id, tenant_code) + WHERE deleted_at IS NULL;` + ) + }, + + down: async (queryInterface, Sequelize) => { + const table = 'user_roles' + + // drop the partial index + await queryInterface.sequelize.query(`DROP INDEX IF EXISTS unique_title_org_id_tenant_code;`) + + // restore original constraint (no WHERE clause) + await queryInterface.addConstraint(table, { + fields: ['title', 'organization_id', 'tenant_code'], + type: 'unique', + name: 'unique_title_org_id_tenant_code', + }) + }, +} From 62ba5f5b7661243c613dddc3e3244a152caecb6d Mon Sep 17 00:00:00 2001 From: adithya_dinesh Date: Fri, 26 Sep 2025 11:45:45 +0530 Subject: [PATCH 06/11] sample csv fix --- src/sample.csv | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sample.csv b/src/sample.csv index 609628f0..57d13301 100644 --- a/src/sample.csv +++ b/src/sample.csv @@ -1,4 +1,4 @@ "name","email","phone_code","phone","username","password","roles","state","district","block","cluster","school","professional_role","professional_subroles" -"SarahB","sarahB@tunerlabs.com","+91","7012345499","sarahB_tunerlabs","Password@123","mentee","Karnataka","BENGALURU RURAL","NELAMANGALA","BASAVANAHALLI","SHARADA HPS","Teacher","Teacher (Class 1-5)" -"JohnB","johnB@tunerlabs.com","+91","7012345599","johnB_honai","Password@123","mentor","Karnataka","BENGALURU RURAL","NELAMANGALA","BASAVANAHALLI","SHARADA HPS","Teacher","Teacher (Class 1-5)" -"PradeepB","pradeepB@tunerlabs.com","+91","7012345699","pradeepB_tl","Password@123","mentor,session_manager","Karnataka","BENGALURU RURAL","NELAMANGALA","BASAVANAHALLI","SHARADA HPS","Teacher","Teacher (Class 1-5)" +"SarahB","sarahB@tunerlabs.com","+91","7012345499","sarahB_tunerlabs","Password@123","mentee",16,1603,160301,1603010007,16030100406,student,"student-preschool-class-2,student-class-1-5" +"JohnB","johnB@tunerlabs.com","+91","7012345599","johnB_honai","Password@123","mentor",16,1603,160301,1603010007,16030100406,student,"student-preschool-class-2,student-class-1-5" +"PradeepB","pradeepB@tunerlabs.com","+91","7012345699","pradeepB_tl","Password@123","mentor,session_manager",16,1603,160301,1603010007,16030100406,student,"student-preschool-class-2,student-class-1-5" From bc01cc7e69d3db88fd14d8395538daf9d98f37bd Mon Sep 17 00:00:00 2001 From: Nevil Mathew Date: Fri, 26 Sep 2025 15:50:06 +0530 Subject: [PATCH 07/11] feat: add organization deactivation validation and error messages --- src/locales/en.json | 3 +++ src/services/admin.js | 22 +++++++++++++++++++++- src/validators/v1/admin.js | 14 ++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/locales/en.json b/src/locales/en.json index 21726f8a..8d1c5ffa 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -85,6 +85,9 @@ "ORG_ROLE_REQ_UPDATED": "Organisation request updated", "FILE_UPLOAD_MODIFY_ERROR": "File update failed", "STATUS_UPDATE_FAILED": "Failed to deactivate user", + "ORG_DEACTIVATION_FAILED": "Failed to deactivate organisation.", + "ORG_NOT_FOUND": "Organisation not found.", + "ORG_ALREADY_INACTIVE": "Organisation is already inactive.", "USER_DEACTIVATED": "User deactivated Successfully", "USER_CSV_UPLOADED_FAILED": "Failed to Uploaded User Invites CSV", "ORG_DEACTIVATED": "Organization deactivated Successfully", diff --git a/src/services/admin.js b/src/services/admin.js index 21a44139..1a796b96 100644 --- a/src/services/admin.js +++ b/src/services/admin.js @@ -651,6 +651,26 @@ module.exports = class AdminHelper { static async deactivateOrg(organizationCode, tenantCode, loggedInUserId) { try { + const org = await organizationQueries.findOne({ code: organizationCode, tenant_code: tenantCode }) + + if (!org) { + return responses.failureResponse({ + message: 'ORG_NOT_FOUND', + statusCode: httpStatusCode.not_found, + responseCode: 'CLIENT_ERROR', + }) + } + + if (org.status === common.INACTIVE_STATUS) { + return responses.failureResponse({ + message: 'ORG_ALREADY_INACTIVE', + statusCode: httpStatusCode.bad_request, + responseCode: 'CLIENT_ERROR', + }) + } + + // proceed with update + // 1. Deactivate org const orgRowsAffected = await organizationQueries.update( { @@ -665,7 +685,7 @@ module.exports = class AdminHelper { if (orgRowsAffected === 0) { return responses.failureResponse({ - message: 'STATUS_UPDATE_FAILED', + message: 'ORG_DEACTIVATION_FAILED', statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR', }) diff --git a/src/validators/v1/admin.js b/src/validators/v1/admin.js index 32173eac..7538047f 100644 --- a/src/validators/v1/admin.js +++ b/src/validators/v1/admin.js @@ -163,4 +163,18 @@ module.exports = { req.checkBody(field).isArray().notEmpty().withMessage(` ${field} must be an array and should not be empty.`) } }, + + deactivateOrg: (req) => { + req.checkParams('id') + .notEmpty() + .withMessage('id is required and should not be empty.') + .matches(/^[a-z0-9_]+$/) + .withMessage('id must be lowercase alphanumeric with underscores.') + + req.checkHeaders('tenant-id') + .notEmpty() + .withMessage('tenant-id is required and should not be empty.') + .matches(/^[a-z0-9_]+$/) + .withMessage('tenant-id must be lowercase alphanumeric with underscores.') + }, } From 049ac722fdddb04cce4286eb4c01a45e2fc61389 Mon Sep 17 00:00:00 2001 From: Nevil Mathew Date: Fri, 26 Sep 2025 19:16:27 +0530 Subject: [PATCH 08/11] fix: simplify organization deactivation check in deactivateOrg method --- src/services/admin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/admin.js b/src/services/admin.js index 1a796b96..1bb0b0ea 100644 --- a/src/services/admin.js +++ b/src/services/admin.js @@ -683,7 +683,7 @@ module.exports = class AdminHelper { } ) - if (orgRowsAffected === 0) { + if (!orgRowsAffected) { return responses.failureResponse({ message: 'ORG_DEACTIVATION_FAILED', statusCode: httpStatusCode.bad_request, From 3ee4c02b92fa48c1dac3de34aa328a68667892dc Mon Sep 17 00:00:00 2001 From: adithya_dinesh Date: Mon, 29 Sep 2025 13:39:06 +0530 Subject: [PATCH 09/11] @coderabbitai --- src/validators/v1/organization.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/validators/v1/organization.js b/src/validators/v1/organization.js index f81a6d42..0068779a 100644 --- a/src/validators/v1/organization.js +++ b/src/validators/v1/organization.js @@ -27,7 +27,7 @@ module.exports = { .trim() .notEmpty() .withMessage('name field is empty') - .matches(/^[A-Za-z ]+$/) + .matches(/^[A-Za-z0-9 ]+$/) .withMessage('name is invalid') req.checkBody('description') @@ -47,7 +47,7 @@ module.exports = { .trim() .notEmpty() .withMessage('name field is empty') - .matches(/^[A-Za-z ]+$/) + .matches(/^[A-Za-z0-9 ]+$/) .withMessage('name is invalid') req.checkBody('description') From e8b0e94e017d11e65f642ab06a7317fccf84533e Mon Sep 17 00:00:00 2001 From: Nevil Mathew Date: Mon, 29 Sep 2025 13:44:10 +0530 Subject: [PATCH 10/11] fix: enhance label validation and status checks in user role validation --- src/validators/v1/user-role.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/validators/v1/user-role.js b/src/validators/v1/user-role.js index 627ad485..3feb6e6a 100644 --- a/src/validators/v1/user-role.js +++ b/src/validators/v1/user-role.js @@ -41,6 +41,8 @@ module.exports = { .withMessage('label is required') .matches(/^[A-Z][a-zA-Z\s]*$/) .withMessage('label must start with an uppercase letter and contain only letters and spaces') + .isLength({ max: 50 }) + .withMessage('label must be at most 50 characters') req.checkBody('status') .optional({ checkFalsy: true }) @@ -76,10 +78,18 @@ module.exports = { .withMessage('visibility must be PUBLIC') req.checkBody('status') - .optional({ checkFalsy: true }) .trim() + .optional({ checkFalsy: true }) .isIn([common.ACTIVE_STATUS, common.INACTIVE_STATUS]) .withMessage(`status must be either ${common.ACTIVE_STATUS} or ${common.INACTIVE_STATUS} when provided`) + + req.checkBody('label') + .trim() + .optional() + .matches(/^[A-Z][a-zA-Z\s]*$/) + .withMessage('label must start with an uppercase letter and contain only letters and spaces') + .isLength({ max: 50 }) + .withMessage('label must be at most 50 characters') }, delete: (req) => { req.checkParams('id').notEmpty().withMessage('id param is empty') From bddc326842618dd66838295fc8a5b1852a071993 Mon Sep 17 00:00:00 2001 From: Nevil Mathew Date: Mon, 29 Sep 2025 14:56:54 +0530 Subject: [PATCH 11/11] fix: update status validation to ensure optional check is applied correctly --- src/validators/v1/user-role.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/validators/v1/user-role.js b/src/validators/v1/user-role.js index 3feb6e6a..32c20983 100644 --- a/src/validators/v1/user-role.js +++ b/src/validators/v1/user-role.js @@ -45,8 +45,8 @@ module.exports = { .withMessage('label must be at most 50 characters') req.checkBody('status') - .optional({ checkFalsy: true }) .trim() + .optional({ checkFalsy: true }) .isIn([common.ACTIVE_STATUS, common.INACTIVE_STATUS]) .withMessage(`status must be either ${common.ACTIVE_STATUS} or ${common.INACTIVE_STATUS} when provided`) },