Skip to content

Conversation

@nevil-mathew
Copy link
Collaborator

@nevil-mathew nevil-mathew commented Sep 24, 2025

Summary by CodeRabbit

  • Bug Fixes
    • More consistent, clearer validation messages for create, update, and list of user roles.
    • Stricter input rules:
      • Title: required on create; lowercase letters and underscores.
      • User type: only 0 or 1.
      • Visibility: only PUBLIC.
      • Status: Active/Inactive values only.
      • Label: required on create, optional on update; must start uppercase, letters/spaces, max length.
    • Update requires a valid ID; list queries now validated per-field (including numeric organization_id).

@coderabbitai
Copy link

coderabbitai bot commented Sep 24, 2025

Walkthrough

Replaced generic validators in src/validators/v1/user-role.js with explicit per-field rules for create, update, and list: tightened patterns, introduced @constants/common status checks, required update id, added numeric organization_id validation, and updated error messages.

Changes

Cohort / File(s) Summary of Changes
User Role v1 Validation Updates
src/validators/v1/user-role.js
Replaced generic validations with explicit per-field validators. Create: stricter title (lowercase/underscores), user_type (isIn(['0','1'])), visibility (isIn(['PUBLIC'])), label (start uppercase, letters/spaces, max length), optional status using common constants. Update: require id param, align field rules with create, label optional. List: per-query checks for title, user_type, visibility, status (common constants), optional numeric organization_id. Added import of @constants/common and updated error messages.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Client
  participant R as Route (v1 user-role)
  participant V as Validator (per-field rules)
  participant S as Service
  note over V: Explicit validators: patterns, isIn, numeric, common constants

  C->>R: POST /user-role (body)
  R->>V: validateCreate(fields)
  alt valid
    V-->>R: next()
    R->>S: createUserRole()
    S-->>R: result
    R-->>C: 200 OK
  else invalid
    V-->>R: field-specific errors
    R-->>C: 400 Bad Request
  end

  C->>R: PUT /user-role/:id (body)
  R->>V: validateUpdate(id, fields)
  alt valid
    V-->>R: next()
    R->>S: updateUserRole()
    S-->>R: result
    R-->>C: 200 OK
  else invalid
    V-->>R: field-specific errors
    R-->>C: 400 Bad Request
  end

  C->>R: GET /user-role (query)
  R->>V: validateList(query)
  alt valid
    V-->>R: next()
    R->>S: listUserRoles()
    S-->>R: results
    R-->>C: 200 OK
  else invalid
    V-->>R: query-specific errors
    R-->>C: 400 Bad Request
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

I twitch my whiskers at patterns tight,
Lowercase titles and labels polite,
Constants shine, IDs stand in line—
Queries counted, errors decline.
A rabbit hops: validations right. 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title “fix[validation]: improve validation messages for user role apis (BUG-3835)” succinctly describes the core change by specifying that validation messages for the user role APIs are being enhanced and includes the associated bug reference without extraneous details.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch user-role-validation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (7)
src/validators/v1/user-role.js (7)

17-23: Title: confirm whether digits are allowed; if yes, expand regex

Current regex forbids digits. If titles like "role_1" are valid, allow digits.

Apply if digits should be allowed:

-      .matches(/^[a-z_]+$/)
-      .withMessage('title must contain only lowercase letters (a–z) and underscores')
+      .matches(/^[a-z0-9_]+$/)
+      .withMessage('title must contain only lowercase letters (a–z), digits (0–9), and underscores')

24-30: user_type: consider normalizing to int and validating numerically

If user_type is stored/handled as a number, normalize and validate as int; otherwise, keep strings as-is.

-      .isIn(['0', '1'])
-      .withMessage('user_type must be 0 (non-admin) or 1 (admin)')
+      .toInt()
+      .isIn([0, 1])
+      .withMessage('user_type must be 0 (non-admin) or 1 (admin)')

38-44: Label: i18n and length guard (optional)

Current regex excludes non‑ASCII letters and hyphens. If i18n labels are required, switch to Unicode properties. Also consider a max length to bound payload size.

Apply if i18n needed:

-      .matches(/^[A-Z][a-zA-Z\s]*$/)
-      .withMessage('label must start with an uppercase letter and contain only letters and spaces')
+      .matches(/^[\p{Lu}][\p{L}\s]*$/u)
+      .withMessage('label must start with an uppercase letter and contain only letters and spaces (i18n supported)')

Optionally add a max length:

+      .isLength({ max: 100 })
+      .withMessage('label must be at most 100 characters')

45-50: status: trim before optional to treat whitespace‑only as “missing”

Reorder to avoid rejecting values like " " when you intend to ignore them.

-    req.checkBody('status')
-      .optional({ checkFalsy: true })
-      .trim()
+    req.checkBody('status')
+      .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`)

55-55: Strengthen id param validation

Validate format, not just presence. Pick the appropriate constraint for your ID type.

Option A (UUID v4):

-    req.checkParams('id').notEmpty().withMessage('id param is required')
+    req.checkParams('id').isUUID('4').withMessage('id param must be a valid UUID')

Option B (numeric):

-    req.checkParams('id').notEmpty().withMessage('id param is required')
+    req.checkParams('id').isInt().withMessage('id param must be a valid integer')

89-118: List validators: trim before optional + stronger numeric check for organization_id

  • Place trim() before optional() so whitespace-only inputs are ignored.
  • Prefer .toInt().isInt() for organization_id.
  • Consider normalizing user_type as int for consistency with create/update.
-    req.checkQuery('title')
-      .optional()
-      .trim()
+    req.checkQuery('title')
+      .trim()
+      .optional()
       .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('user_type')
+      .trim()
+      .optional()
+      .toInt()
+      .isIn([0, 1])
+      .withMessage('user_type is invalid. Allowed values: 0 (non-admin) or 1 (admin)')

-    req.checkQuery('visibility')
-      .optional()
-      .trim()
+    req.checkQuery('visibility')
+      .trim()
+      .optional()
       .isIn(['PUBLIC'])
       .withMessage('visibility is invalid. Allowed value: PUBLIC')

-    req.checkQuery('status')
-      .optional()
-      .trim()
+    req.checkQuery('status')
+      .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.checkQuery('organization_id')
-      .optional()
-      .trim()
-      .matches(/^[0-9]+$/)
-      .withMessage('organization_id is invalid. Must be numeric')
+    req.checkQuery('organization_id')
+      .trim()
+      .optional()
+      .toInt()
+      .isInt()
+      .withMessage('organization_id is invalid. Must be an integer')

4-11: Remove unused validateList from src/validators/v1/user-role.js

Defined but not exported or referenced anywhere in the repository — remove it or export/use it if intended.

Location: src/validators/v1/user-role.js (lines 4–11)

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e5b1348 and bd62e19.

📒 Files selected for processing (1)
  • src/validators/v1/user-role.js (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/validators/**

⚙️ CodeRabbit configuration file

Validate all incoming data thoroughly. Check for missing or incomplete validation rules.

Files:

  • src/validators/v1/user-role.js
🧬 Code graph analysis (1)
src/validators/v1/user-role.js (1)
src/constants/blacklistConfig.js (1)
  • userRole (332-335)
🔇 Additional comments (5)
src/validators/v1/user-role.js (5)

3-3: Confirm status constants exist and match API/domain values

Verify common.ACTIVE_STATUS and common.INACTIVE_STATUS are defined and exactly match the values persisted/used downstream.


31-37: Visibility restricted to PUBLIC only — confirm product intent

Is PUBLIC the only valid value for create/update? If other visibilities (e.g., PRIVATE/RESTRICTED) are supported now or in the near term, widen isIn.


57-63: Same as create/title

See comment on Lines 17–23 regarding digits and length.


64-70: Same as create/user_type

See comment on Lines 24–30 about normalizing to int vs strings.


71-77: Same as create/visibility

See comment on Lines 31–37 (PUBLIC-only).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/validators/v1/user-role.js (2)

117-124: Align list() optional validators with sanitized values

In list(), the query validators call .optional() before trimming and don’t use checkFalsy, so a parameter like status=%20%20 is treated as provided and fails isIn(). Trim first and enable checkFalsy so blank query params are skipped; please apply the same pattern to the other optional query fields here.

		req.checkQuery('status')
-			.optional()
-			.trim()
+			.trim()
+			.optional({ checkFalsy: true })
			.isIn([common.ACTIVE_STATUS, common.INACTIVE_STATUS])

4-11: Remove unused validateList helper

validateList is no longer invoked after inlining the per-field checks; keeping unused helpers invites drift. Please drop this function (and its call sites, if any remain elsewhere).

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bd62e19 and e8b0e94.

📒 Files selected for processing (1)
  • src/validators/v1/user-role.js (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/validators/**

⚙️ CodeRabbit configuration file

Validate all incoming data thoroughly. Check for missing or incomplete validation rules.

Files:

  • src/validators/v1/user-role.js
🧬 Code graph analysis (1)
src/validators/v1/user-role.js (1)
src/constants/blacklistConfig.js (1)
  • userRole (332-335)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e8b0e94 and bddc326.

📒 Files selected for processing (1)
  • src/validators/v1/user-role.js (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/validators/**

⚙️ CodeRabbit configuration file

Validate all incoming data thoroughly. Check for missing or incomplete validation rules.

Files:

  • src/validators/v1/user-role.js
🧬 Code graph analysis (1)
src/validators/v1/user-role.js (1)
src/constants/blacklistConfig.js (1)
  • userRole (332-335)

Comment on lines +99 to +127
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')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Trim before marking list query params optional

In list(), every query-chain calls optional() before trim(). When a client sends "status= " (or other fields with only whitespace), the value is still truthy when .optional() runs, so the chain keeps going and .isIn()/.matches() reject it. We just fixed this exact bug for status in create/update; the GET flow should behave the same way. Reorder the calls so you trim first and only then mark the field optional (using checkFalsy to drop empty strings). (github.com)

-		req.checkQuery('title')
-			.optional()
-			.trim()
+		req.checkQuery('title')
+			.trim()
+			.optional({ checkFalsy: true })
 			.matches(/^[a-z_]+$/)
 			.withMessage('title is invalid. Use only lowercase letters a–z and underscores')

-		req.checkQuery('user_type')
-			.optional()
-			.trim()
+		req.checkQuery('user_type')
+			.trim()
+			.optional({ checkFalsy: true })
 			.isIn(['0', '1'])
 			.withMessage('user_type is invalid. Allowed values: 0 (non-admin) or 1 (admin)')

-		req.checkQuery('visibility')
-			.optional()
-			.trim()
+		req.checkQuery('visibility')
+			.trim()
+			.optional({ checkFalsy: true })
 			.isIn(['PUBLIC'])
 			.withMessage('visibility is invalid. Allowed value: PUBLIC')

-		req.checkQuery('status')
-			.optional()
-			.trim()
+		req.checkQuery('status')
+			.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.checkQuery('organization_id')
-			.optional()
-			.trim()
+		req.checkQuery('organization_id')
+			.trim()
+			.optional({ checkFalsy: true })
 			.matches(/^[0-9]+$/)
 			.withMessage('organization_id is invalid. Must be numeric')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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')
req.checkQuery('title')
.trim()
.optional({ checkFalsy: true })
.matches(/^[a-z_]+$/)
.withMessage('title is invalid. Use only lowercase letters a–z and underscores')
req.checkQuery('user_type')
.trim()
.optional({ checkFalsy: true })
.isIn(['0', '1'])
.withMessage('user_type is invalid. Allowed values: 0 (non-admin) or 1 (admin)')
req.checkQuery('visibility')
.trim()
.optional({ checkFalsy: true })
.isIn(['PUBLIC'])
.withMessage('visibility is invalid. Allowed value: PUBLIC')
req.checkQuery('status')
.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.checkQuery('organization_id')
.trim()
.optional({ checkFalsy: true })
.matches(/^[0-9]+$/)
.withMessage('organization_id is invalid. Must be numeric')
🤖 Prompt for AI Agents
In src/validators/v1/user-role.js around lines 99 to 127, each query validation
chain currently calls .optional() before .trim(), causing whitespace-only values
to be treated as present and then rejected; reorder the calls to trim first and
then make the field optional using the falsy-check so empty strings are dropped
(e.g., call .trim() then .optional({ checkFalsy: true }) before the following
validators like .matches()/.isIn()), and apply this change to title, user_type,
visibility, status and organization_id chains.

@aks30 aks30 merged commit 2dc3075 into develop Sep 29, 2025
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants