Skip to content

Issue #251045 feat: Hierarchical Categories Implementation#668

Open
vaivk369 wants to merge 10 commits intoELEVATE-Project:developfrom
vaivk369:new-dev
Open

Issue #251045 feat: Hierarchical Categories Implementation#668
vaivk369 wants to merge 10 commits intoELEVATE-Project:developfrom
vaivk369:new-dev

Conversation

@vaivk369
Copy link

@vaivk369 vaivk369 commented Jan 8, 2026

Description

These recommendations are intended to promote code quality and team communication during software development. They cover a variety of topics, including ensuring that pull requests are submitted to the correct branch, documenting new methods, preserving consistency across many services, and avoiding typical blunders like accessing APIs or DB queries within loops. Sensitive data should not be uploaded, and changes to environment variables or database models should be executed consistently. Teams may work more effectively and develop higher-quality software by adhering to these standards.

Type of change

Please choose appropriate options.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Enhancement (additive changes to improve performance)
  • This change requires a documentation update

Checklist

  • It's critical to avoid making needless file modifications in contributions, such as adding new lines, console logs, or additional spaces, to guarantee cleaner and more efficient code. Furthermore, eliminating unnecessary imports from a file might enhance code readability and efficiency.
  • Ensure that the pull request is assigned to the right base branch and that the development branch name contains the JIRA Task Id. Furthermore, each commit message should include the JIRA Task Id in the manner "ED-100: message".
  • Only update packages if it is mentioned and authorized in the design document, and make sure that you have the required permissions.
  • Avoid making API and database queries inside a loop as it can lead to performance issues and slow down the system.
  • When calling another function inside a given function, add comments explaining the purpose and meaning of the passed arguments and expected return values.
  • If adding a blank argument in a function, add a comment explaining the reason for the blank argument.
  • Before submitting a pull request, do a self-review of your code to ensure there are no conflicts with the base branch and all comments have been addressed.
  • Before merging a pull request, it's important to have other team members review it to catch any potential errors or issues
  • To maintain code integrity, it's important to remove all related changes when removing code during a code review.
  • If new constants, endpoints, or utility functions are introduced, it is important to check if they already exist in the service to avoid any duplication.
  • Whenever a new environment variable is added to a service, it's important to ensure that the necessary changes are made to related files such as ".env.sample" and "envVariables.js" to maintain consistency and avoid errors. Additionally, the new environment variable should be added to the devops repository to ensure that it is properly documented and accessible to the team.
  • When adding a new function to a service, it is important to document it with relevant information such as the name, parameters, and return value in a consistent format across all services. Additionally, if there are any changes to the API response, ensure that the documentation in the controllers is updated accordingly.
  • Write a clear and concise commit message that describes the changes made.
  • Maintain consistent function signature and code across all services when adding a function to multiple services. Implement changes to database models in all services that use the same model.
  • Use only let and const. Do not use var.
  • Make common functions for repetitive code blocks.
  • Avoid uploading sensitive information such as secret tokens or passwords in pull requests to ensure data security.
  • Maintain consistent indentation and spacing throughout the code.

Summary by CodeRabbit

  • New Features

    • Delete and details endpoints for library categories (delete category, fetch category with optional children)
    • Hierarchical categories with parent/child relationships, depth handling, and sequence ordering
    • Project templates: filter by category IDs, group results by category, and optionally include task details
    • Category metadata: description, keywords, sequenceNumber, hasChildCategories
  • Other

    • New user-facing messages for successful/failed category deletion

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 8, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

📝 Walkthrough

Walkthrough

Adds hierarchical project library categories (parent/child, sequence, keywords), new delete/details endpoints, validation and helper logic for hierarchy management, schema/index updates, API response messages, and extends project templates listing to support category filtering/grouping and task-detail enrichment.

Changes

Cohort / File(s) Summary
Routes
constants/interface-routes/elevate-project/configs.json
Inserted two new routes: DELETE /project/v1/library/categories/delete/:id and GET /project/v1/library/categories/details/:id positioned after the categories list route.
Controllers
controllers/v1/library/categories.js, controllers/v1/project/templates.js
Added delete and details controller methods for library categories; extended templates controller to pass categoryIds, groupByCategory, taskDetails from query to helper; normalized API docs/headers.
Helpers / Business Logic
module/library/categories/helper.js, module/project/templates/helper.js
Major additions: parent validation (validateParentId), cycle detection (isDescendant), depth calc, delete and details helper methods, create/update/list enhancements for sequence and child handling; project templates list accepts categoryIds, grouping, and optional task detail enrichment.
Schema / Models
models/project-categories.js
Added fields: description, keywords, parentId, hasChildCategories, sequenceNumber, metaInformation; added compound indexes for parent/tenant/sequence and tenant/hasChildCategories.
Validation
module/library/categories/validator/v1.js
Added/updated validators: optional description, keywords array, parent_id (MongoId), forbid hasChildCategories, optional sequenceNumber.
Constants / Responses
generics/constants/api-responses.js
Added PROJECT_CATEGORIES_DELETED and PROJECT_CATEGORIES_NOT_DELETED messages.

Sequence Diagrams

sequenceDiagram
    participant Client
    participant Controller as LibraryCategoriesController
    participant Helper as LibraryCategoriesHelper
    participant DB as Database

    Client->>Controller: DELETE /project/v1/library/categories/delete/:id
    Controller->>Helper: delete({ _id: id, tenantId }, userDetails)
    Helper->>DB: Find category by id & tenant
    DB-->>Helper: category
    alt not found
        Helper-->>Controller: error (not found)
        Controller-->>Client: 404
    else found
        Helper->>DB: Count child categories
        DB-->>Helper: childCount
        alt childCount > 0
            Helper-->>Controller: error (has children)
            Controller-->>Client: 400
        else no children
            Helper->>DB: Count associated projects
            DB-->>Helper: projectCount
            alt projectCount > 0
                Helper-->>Controller: error (has projects)
                Controller-->>Client: 400
            else safe to delete
                Helper->>DB: Soft-delete category (isDeleted)
                DB-->>Helper: updatedCategory
                Helper->>DB: Update parent hasChildCategories if needed
                DB-->>Helper: parentUpdated
                Helper-->>Controller: success(result)
                Controller-->>Client: 200
            end
        end
    end
Loading
sequenceDiagram
    participant Client
    participant Controller as ProjectTemplatesController
    participant Helper as ProjectTemplatesHelper
    participant DB as Database

    Client->>Controller: GET /project/v1/project-templates?categoryIds=...&groupByCategory=true
    Controller->>Helper: list(pageNo, pageSize, searchText, currentOrgOnly, userDetails, categoryIds, groupByCategory, taskDetails)
    Helper->>DB: Query templates with optional category filter
    DB-->>Helper: paginatedTemplates
    alt groupByCategory == true
        Helper->>Helper: Group templates by categoryId
        alt taskDetails == true
            Helper->>Helper: Enrich templates with tasks and subtasks
        end
        Helper-->>Controller: groupedResult
    else
        alt taskDetails == true
            Helper->>Helper: Enrich templates with tasks
        end
        Helper-->>Controller: paginatedResult
    end
    Controller-->>Client: 200 result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • VISHNUDAS-tunerlabs
  • praveenKDass

Poem

"I hop through trees of categories new and bright,
Parents and children in orderly sight,
Delete with care, no stray sprout remains,
Templates grouped tidy — I polish the lanes,
A rabbit cheers code that takes flight! 🐇"

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is a generic template with unchecked checkboxes rather than an actual description of the changes made. It provides no specific information about the hierarchical categories implementation, changes made, or rationale. Replace the template with a concrete description explaining: (1) what hierarchical categories feature does, (2) which files were changed and why, (3) new schema fields and API endpoints added, and (4) testing performed. Check relevant type-of-change and checklist items.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and specifically describes the main change: implementing hierarchical categories functionality, which aligns with the substantial changes across models, helpers, validators, and controllers.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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.

@vaivk369
Copy link
Author

vaivk369 commented Jan 8, 2026

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Jan 8, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
controllers/v1/project/templates.js (2)

815-828: Ensure query params passed to projectTemplatesHelper.list are normalized to booleans

currentOrgOnly, categoryIds (string), and groupByCategory are passed straight from req.query. The helper already normalizes currentOrgOnly, but groupByCategory is consumed as‑is, so values like ?groupByCategory=false will still be treated as truthy.

If you don’t normalize inside the controller, make sure the helper does (see suggested change in list), so that:

  • "true" / "false" / true / false all behave as expected.
  • Omitting the param defaults to false.

Given the existing pattern in this file (booleans normalized in helpers), fixing this centrally in ProjectTemplatesHelper.list is preferable to duplicating parsing logic here.


903-913: Normalize isExternalProgram before calling createChildProjectTemplate

req.query.isExternalProgram arrives as a string and is passed directly without type normalization. While the downstream solutionsUtils.createSolution does call UTILS.convertStringToBoolean(isExternalProgram) internally, normalizing at the controller entry point is cleaner and aligns with patterns elsewhere in the codebase (e.g., controllers/v1/userProjects.js).

Convert it in the controller:

const isExternalProgram =
  req.query.isExternalProgram !== undefined
    ? UTILS.convertStringToBoolean(req.query.isExternalProgram)
    : true

This ensures the boolean type is correct from the outset and keeps query parameter handling consistent across the API.

module/project/templates/helper.js (1)

611-735: Fix critical bugs in createChildProjectTemplate: wrong response property and severely misaligned cleanup parameters

Three high-impact correctness issues:

  1. Use createdSolution.data instead of createdSolution.result (lines 673, 677)
    The function handleDuplicateTemplateTask correctly accesses the same helper's response as newSolution.data._id. Using .result here causes undefined values, breaking cleanup logic.

  2. Fix deletedResourceDetails call with correct parameter alignment (lines 726–731)
    The signature expects (resourceId, resourceType, isAPrivateProgram, tenantId, orgId, deletedBy), but the call passes (resourceId, resourceType, tenantId, tenantAndOrgInfo[0], userId). This misalignment breaks the cleanup logic entirely:

    • tenantId placed in position 3 (expects boolean)
    • tenantAndOrgInfo[0] is undefined (array indexing on object)
    • userId placed where orgId belongs
    • deletedBy not passed

    Use the established pattern from admin.js: pass tenantAndOrgInfo.orgId[0] for the org parameter, and userId as the sixth argument.

  3. Normalize isExternalProgram from string to boolean
    Query parameters are strings. Convert using UTILS.convertStringToBoolean() (see admin.js:171 pattern) or ensure the helper handles string inputs safely.

Corrected parameter order for deletedResourceDetails
- await adminHelper.deletedResourceDetails(
-   _id,
-   CONSTANTS.common.SOLUTION,
-   userDetails.tenantAndOrgInfo.tenantId,
-   userDetails.tenantAndOrgInfo[0],
-   userDetails.userInformation.userId
- )
+ await adminHelper.deletedResourceDetails(
+   _id,
+   CONSTANTS.common.SOLUTION,
+   false,  // isAPrivateProgram
+   userDetails.tenantAndOrgInfo.tenantId,
+   userDetails.tenantAndOrgInfo.orgId[0],
+   userDetails.userInformation.userId  // deletedBy
+ )
module/library/categories/validator/v1.js (1)

32-34: Add validation for updatable fields in the update method.

The update method only validates the _id parameter but doesn't validate body fields. If fields like description, keywords, parent_id, or sequenceNumber can be updated, they should have the same validation rules as in the create method.

♻️ Suggested enhancement
 update: function () {
   req.checkParams('_id').exists().withMessage('required category id')
+  req.checkBody('description').optional().isString().withMessage('description must be a string')
+  req.checkBody('keywords').optional().isArray().withMessage('keywords must be an array')
+  if (req.body.keywords) {
+    req.body.keywords.forEach((keyword, index) => {
+      req.checkBody(`keywords[${index}]`)
+        .isString()
+        .withMessage(`keyword at index ${index} must be a string`)
+    })
+  }
+  req.checkBody('parent_id').optional().isMongoId().withMessage('parent_id must be a valid ObjectId')
+  req.checkBody('hasChildCategories')
+    .not()
+    .exists()
+    .withMessage('hasChildCategories cannot be set in request body')
+  req.checkBody('sequenceNumber')
+    .optional()
+    .isInt({ min: 0 })
+    .withMessage('sequenceNumber must be a non-negative integer')
 },

Note: Consider extracting common validation logic into a shared function to avoid duplication.

🤖 Fix all issues with AI agents
In @models/project-categories.js:
- Around line 69-94: The compound index keys use parent_id but the schema
defines parentId, so update the compound index definition in
models/project-categories.js to use parentId (and keep tenantId and
sequenceNumber unchanged), and then audit and fix all usages in
LibraryCategoriesHelper (create/update/list/delete) to consistently reference
parentId (not parent_id) and the existing hasChildCategories and sequenceNumber
fields so queries and updates use the same field names as the schema.

In @module/library/categories/helper.js:
- Around line 1114-1215: In the static delete method, the check for associated
templates uses categoryData.noOfProjects but treats it like an array
(associatedTemplates.length), which always fails; change the logic to treat
noOfProjects as a number (e.g., const associatedTemplates =
categoryData.noOfProjects || 0 and then if (associatedTemplates > 0) throw the
bad_request error) so deletion is blocked when noOfProjects is greater than
zero; update the check around categoryData.noOfProjects and ensure the existing
error payload is thrown when the numeric condition is met.
- Around line 910-951: create currently mixes categoryData.parentId and
categoryData.parent_id which causes validateParentId, sequenceNumber calculation
and DB queries (projectCategoriesQueries.categoryDocuments) to operate on
different fields leading to incorrect sibling lookups and inconsistent stored
documents; normalize to a single canonical field (use parentId): derive const
parentId = categoryData.parentId || categoryData.parent_id || null, set
categoryData.parentId = parentId and delete categoryData.parent_id, call
this.validateParentId(parentId, ...) and use parentId for all sequenceNumber
logic and for the query filters passed to
projectCategoriesQueries.categoryDocuments, ensuring sequenceNumber assignment
(sequenceNumber variable) uses the normalized parentId for both sibling and root
queries and store only parentId in the created document.

In @module/project/templates/helper.js:
- Around line 1985-1993: In the static list(...) method normalize
groupByCategory exactly like currentOrgOnly (convert req.query string
"true"/"false" to a boolean) right after the currentOrgOnly conversion so
"false" doesn’t evaluate truthy; and when grouping results (the loop around
categories in list), precompute a Set of requested categoryIds
(parsed/normalized to the same _id type as categories) and only add a template
to groups for categories whose _id exists in that Set so templates aren’t placed
into categories that weren’t requested.
🧹 Nitpick comments (5)
module/library/categories/helper.js (3)

37-94: Hierarchy helpers are solid, but consider guarding isDescendant against cycles

The new validateParentId, isDescendant, and getHierarchyDepth helpers add good safeguards (self-parenting and descendant checks, tenant scoping).

One minor robustness concern:

  • isDescendant walks parentId up the tree with a while (currentId) loop and no depth/visited limit.
  • If historical data already contains a cycle, isDescendant can loop indefinitely, repeatedly querying the same nodes.

Given getHierarchyDepth uses depth < 10 as a safety cap, consider applying a similar depth or visited-set guard in isDescendant as a defensive measure. This isn’t a blocker but will make the hierarchy routines more resilient to bad data.

Also applies to: 105-130


655-685: Parent update logic: good overall, but keep parentId handling consistent

The update flow now:

  • Validates updateData.parentId via validateParentId.
  • Adjusts hasChildCategories on the old parent if the category moves.
  • Sets hasChildCategories: true on the new parent when applicable.

This correctly maintains the parent’s leaf/branch status.

However:

  • validateParentId returns parentCategory, but the local parentCategory variable is never used; you can drop it to avoid confusion.
  • Ensure that everywhere else in the codebase you rely on the same field name (parentId, not parent_id) for parent relations; some parts of create and the model still refer to parent_id, which will bypass this logic.

Not a blocker, but cleaning up these minor inconsistencies will avoid subtle bugs later.

Also applies to: 721-750


1123-1222: Async Promise executor lint warnings can likely be ignored for consistency

The new delete and details methods follow the existing pattern in this service:

static delete(filterQuery, userDetails) {
  return new Promise(async (resolve, reject) => {
    // ...
  })
}

Biome flags noAsyncPromiseExecutor, but this pattern is used widely across the codebase for helpers/controllers and is an intentional architectural choice. Changing just these two methods would introduce inconsistency without real benefit.

Unless you’re planning a broader refactor away from this pattern, it’s reasonable to keep the current style and adjust linter configuration instead.

Also applies to: 1233-1293

controllers/v1/library/categories.js (1)

181-230: Updated list documentation still references old response shape

The @api example under /library/categories/list still shows the older flat response structure (name, type, projectsCount, url). The helper now returns richer category objects (including description, keywords, parentId, hasChildCategories, sequenceNumber, etc.).

Not strictly required for functionality, but updating the example to reflect the current shape will make the API docs less confusing for consumers.

module/library/categories/validator/v1.js (1)

14-21: Consider additional validation for keywords array.

The current validation checks if keywords is an array and if each item is a string. Consider adding:

  • Minimum/maximum array length to prevent abuse
  • Non-empty string validation for each keyword
  • Duplicate detection within the array
  • Optional: trim whitespace from keywords
♻️ Enhanced validation example
req.checkBody('keywords')
  .optional()
  .custom((value) => {
    if (!Array.isArray(value)) {
      throw new Error('keywords must be an array')
    }
    if (value.length > 50) {
      throw new Error('keywords array cannot exceed 50 items')
    }
    if (!value.every(item => typeof item === 'string' && item.trim().length > 0)) {
      throw new Error('All keywords must be non-empty strings')
    }
    const uniqueKeywords = new Set(value.map(k => k.toLowerCase().trim()))
    if (uniqueKeywords.size !== value.length) {
      throw new Error('keywords must be unique')
    }
    return true
  })
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 920b3f3 and e9cf645.

📒 Files selected for processing (8)
  • constants/interface-routes/elevate-project/configs.json
  • controllers/v1/library/categories.js
  • controllers/v1/project/templates.js
  • generics/constants/api-responses.js
  • models/project-categories.js
  • module/library/categories/helper.js
  • module/library/categories/validator/v1.js
  • module/project/templates/helper.js
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-09-08T11:43:01.267Z
Learnt from: Prajwal17Tunerlabs
Repo: ELEVATE-Project/project-service PR: 565
File: constants/interface-routes/elevate-project/configs.json:4050-4104
Timestamp: 2025-09-08T11:43:01.267Z
Learning: For route configurations in constants/interface-routes/elevate-project/configs.json, suggestions about controller/handler implementation mismatches may not be applicable as this file only declares route mappings and the actual implementation details are handled at the application level.

Applied to files:

  • constants/interface-routes/elevate-project/configs.json
📚 Learning: 2025-09-08T11:40:02.328Z
Learnt from: Prajwal17Tunerlabs
Repo: ELEVATE-Project/project-service PR: 565
File: constants/interface-routes/elevate-project/configs.json:4036-4048
Timestamp: 2025-09-08T11:40:02.328Z
Learning: For route configurations in constants/interface-routes/elevate-project/configs.json, security suggestions like adding rate limiting or requiresCustomHandling may not be applicable or may be handled differently in the system architecture.

Applied to files:

  • constants/interface-routes/elevate-project/configs.json
📚 Learning: 2025-09-09T03:37:46.179Z
Learnt from: Prajwal17Tunerlabs
Repo: ELEVATE-Project/project-service PR: 565
File: constants/interface-routes/elevate-project/configs.json:4036-4048
Timestamp: 2025-09-09T03:37:46.179Z
Learning: The requiresCustomHandling key is not needed for route configurations in constants/interface-routes/elevate-project/configs.json.

Applied to files:

  • constants/interface-routes/elevate-project/configs.json
📚 Learning: 2025-08-01T12:05:55.953Z
Learnt from: MallanagoudaB
Repo: ELEVATE-Project/project-service PR: 542
File: module/admin/helper.js:386-540
Timestamp: 2025-08-01T12:05:55.953Z
Learning: The deleteAssociatedResources method in module/admin/helper.js uses the async Promise executor pattern "return new Promise(async (resolve, reject) => {" intentionally for architectural uniformity across the ELEVATE-Project/project-service codebase, consistent with the established pattern used throughout the service.

Applied to files:

  • controllers/v1/library/categories.js
📚 Learning: 2025-08-11T11:22:52.598Z
Learnt from: Prajwal17Tunerlabs
Repo: ELEVATE-Project/project-service PR: 550
File: api-doc/api-doc.yaml:1683-1685
Timestamp: 2025-08-11T11:22:52.598Z
Learning: In the ELEVATE-Project/project-service codebase, for the api-doc.yaml file, it is acceptable to have query parameters with descriptions mentioning "in the path". This is an intentional design choice and should not be flagged as a mismatch.

Applied to files:

  • controllers/v1/project/templates.js
🧬 Code graph analysis (2)
module/project/templates/helper.js (2)
module/scp/helper.js (1)
  • template (133-152)
module/library/categories/helper.js (2)
  • category (113-120)
  • category (146-153)
module/library/categories/helper.js (4)
generics/middleware/authenticator.js (1)
  • tenantId (665-665)
module/project/templates/helper.js (1)
  • projectCategoriesQueries (25-25)
module/userProjects/helper.js (1)
  • projectCategoriesQueries (13-13)
generics/helpers/utils.js (1)
  • query (113-113)
🪛 Biome (2.1.2)
controllers/v1/library/categories.js

[error] 267-284: Promise executor functions should not be async.

(lint/suspicious/noAsyncPromiseExecutor)

module/library/categories/helper.js

[error] 1123-1222: Promise executor functions should not be async.

(lint/suspicious/noAsyncPromiseExecutor)


[error] 1234-1293: Promise executor functions should not be async.

(lint/suspicious/noAsyncPromiseExecutor)

🔇 Additional comments (7)
generics/constants/api-responses.js (1)

79-84: New delete response constants are consistent and ready to use

PROJECT_CATEGORIES_DELETED and PROJECT_CATEGORIES_NOT_DELETED follow the existing naming/message conventions and are already referenced from the new delete helper logic, so there’s nothing to change here.

module/library/categories/helper.js (1)

1011-1077: New list filters are useful; just ensure search and keyword filters compose as intended

The extended list implementation adds:

  • Stronger base filters: status: ACTIVE_STATUS, isDeleted: false.
  • Optional parentId handling (including 'null' for root).
  • Optional keyword filtering via keywords: { $in: [...] }.
  • Case-insensitive search on name, description, and externalId via $or.

The shape of the resulting Mongo query (AND between keywords and $or search clauses) looks correct for “must match keywords and search term”. No code change is strictly required here; mainly:

  • Be aware that providing both keywords and searchText will narrow results (not union them).
  • If you want “either matches keywords OR search”, you’d need to wrap them in a higher-level $or.

As implemented, this is a reasonable, predictable default.

Also applies to: 1078-1090

controllers/v1/library/categories.js (1)

266-285: delete controller method is wired correctly to the helper

The new delete controller:

  • Builds a simple filter { _id: req.params._id }.
  • Delegates to libraryCategoriesHelper.delete.
  • Maps data to result in the standard controller pattern and returns structured errors.

This aligns with existing create/update/list patterns and matches the new DELETE route configuration. No changes needed here.

constants/interface-routes/elevate-project/configs.json (1)

895-921: New DELETE route for library categories is consistent with existing routes

The route:

{
  "sourceRoute": "/project/v1/library/categories/delete/:id",
  "type": "DELETE",
  "priority": "MUST_HAVE",
  "inSequence": false,
  "orchestrated": false,
  "targetPackages": [{ "basePackageName": "project", "packageName": "elevate-project" }],
  "service": "project"
}

is consistent with the existing library category routes (create, update, list) and correctly targets the project service. Assuming the router maps this path to LibraryCategories.delete, no further changes are required.

module/library/categories/validator/v1.js (3)

13-13: LGTM! Clean validation rules.

The validation for description, hasChildCategories, and sequenceNumber is well-implemented:

  • description as optional string is straightforward and correct
  • hasChildCategories is properly forbidden in the request body (likely a computed field)
  • sequenceNumber correctly validates as a non-negative integer for ordering

Also applies to: 23-30


22-22: Validation is properly separated: format in validator, business logic in helper.

The parent_id validator correctly checks only the MongoDB ObjectId format. The helper layer (module/library/categories/helper.js) handles all business logic validation:

  • Circular reference detection via isDescendant() check (line 83)
  • Parent existence validation via validateParentId() (called during create and update operations)
  • Hierarchy depth tracking via getHierarchyDepth() method

All edge cases are properly covered.


14-21: The forEach pattern with dynamic req.checkBody() calls is working correctly and is used elsewhere in the codebase. The validator middleware (generics/middleware/validator/index.js) calls the validator function during middleware execution (before next()), so all req.checkBody() calls register in time for validation. This same pattern is used successfully in module/userExtension/validator/v1.js with even more complex nesting.

No changes needed.

Likely an incorrect or invalid review comment.

@vaivk369
Copy link
Author

vaivk369 commented Jan 8, 2026

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Jan 8, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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: 4

🤖 Fix all issues with AI agents
In @controllers/v1/library/categories.js:
- Around line 258-265: Update the JSDoc block above the delete method so the
@name tag correctly reflects the method name (replace `@name list` with `@name
delete`) and adjust the description if needed to match "delete a library
category"; locate the JSDoc that documents the delete function (the comment
starting "delete a library category") and ensure tags like @method, @name,
@param, and @returns accurately describe the `delete` method signature.

In @module/library/categories/helper.js:
- Around line 1017-1022: The code in the list(searchText, params, userDetails)
function incorrectly references req.userDetails.userInformation.organizationId
which is out of scope; replace uses of req with the passed-in userDetails object
(e.g., access userDetails.userInformation.organizationId) so the currentOrgOnly
branch builds query['orgId'] = { $in: ['ALL',
userDetails.userInformation.organizationId] } (ensure
UTILS.convertStringToBoolean(params['currentOrgOnly']) stays as-is and handle
the case where userDetails or userDetails.userInformation may be undefined
before reading organizationId).

In @module/project/templates/helper.js:
- Around line 2065-2088: groupedTemplates is referenced but never declared in
the groupByCategory block causing a runtime ReferenceError; declare and
initialize groupedTemplates before use (e.g., const groupedTemplates = {} or let
groupedTemplates = {} placed immediately inside the if (groupByCategory) block,
above the paginatedResults.forEach loop) so that the subsequent assignments to
groupedTemplates[catId].push(template) and the final paginatedResults =
groupedTemplates work correctly; ensure the chosen declaration (const vs let)
matches any later reassignments to paginatedResults.
- Around line 2066-2086: The grouping logic assumes each entry in
template.categories is an ObjectId, but categories are objects with an _id
field; update the loop in the paginatedResults handling (where
template.categories is iterated and catId computed) to extract the real id:
derive the id value as const idVal = categoryId && categoryId._id ?
categoryId._id : categoryId, then call idVal.toString() for the key and use that
string for membership checks against categoryIdArray and for groupedTemplates
keys (i.e., replace categoryId.toString() with the normalized id string); ensure
you handle null/undefined categoryId safely before calling toString().
🧹 Nitpick comments (3)
module/project/templates/helper.js (1)

2051-2063: Performance: N+1 query pattern in taskDetails loop.

When taskDetails is true, this performs a database query for each template in paginatedResults. Consider batching these queries or using aggregation to reduce database round-trips.

module/library/categories/helper.js (2)

105-130: Consider adding a safety limit to prevent infinite loops.

Unlike getHierarchyDepth which has a depth < 10 safety limit, isDescendant could loop indefinitely if there's corrupted data with a circular reference already in the database.

♻️ Suggested improvement
 static async isDescendant(potentialDescendantId, ancestorId, tenantId) {
     let currentId = potentialDescendantId
+    let iterations = 0
+    const MAX_ITERATIONS = 100 // Safety limit

-    while (currentId) {
+    while (currentId && iterations < MAX_ITERATIONS) {
+        iterations++
         if (currentId.toString() === ancestorId.toString()) {
             return true
         }

1243-1304: details method implementation is correct.

The method properly:

  • Fetches category by ID with tenant scoping
  • Optionally retrieves immediate children
  • Returns structured response

Note: The organizationId variable at line 1247 is declared but never used - consider removing it.

♻️ Remove unused variable
 static details(filterQuery, userDetails) {
     return new Promise(async (resolve, reject) => {
         try {
             let tenantId = userDetails.userInformation.tenantId
-            let organizationId = userDetails.userInformation.organizationId
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e9cf645 and 5aa79a8.

📒 Files selected for processing (6)
  • constants/interface-routes/elevate-project/configs.json
  • controllers/v1/library/categories.js
  • controllers/v1/project/templates.js
  • models/project-categories.js
  • module/library/categories/helper.js
  • module/project/templates/helper.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • constants/interface-routes/elevate-project/configs.json
  • controllers/v1/project/templates.js
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-08-01T11:59:41.053Z
Learnt from: MallanagoudaB
Repo: ELEVATE-Project/project-service PR: 542
File: databaseQueries/projectTemplates.js:158-172
Timestamp: 2025-08-01T11:59:41.053Z
Learning: In the ELEVATE-Project/project-service codebase, the async Promise executor pattern "return new Promise(async (resolve, reject) => {" is intentionally used consistently across all database query methods in the databaseQueries directory for architectural uniformity, despite static analysis tools flagging it as an anti-pattern.

Applied to files:

  • controllers/v1/library/categories.js
📚 Learning: 2025-09-08T11:55:24.922Z
Learnt from: Prajwal17Tunerlabs
Repo: ELEVATE-Project/project-service PR: 565
File: databaseQueries/organizationExtension.js:70-84
Timestamp: 2025-09-08T11:55:24.922Z
Learning: Database query helper files in this project use Promise wrapper patterns (with new Promise and async executors) to maintain consistency across all helper functions, even when async/await could be used directly.

Applied to files:

  • controllers/v1/library/categories.js
📚 Learning: 2025-08-06T06:10:37.743Z
Learnt from: MallanagoudaB
Repo: ELEVATE-Project/project-service PR: 542
File: module/admin/helper.js:337-337
Timestamp: 2025-08-06T06:10:37.743Z
Learning: The ELEVATE-Project/project-service codebase has inconsistent MongoDB $in operator usage patterns. While some files correctly use arrays (e.g., `{ $in: [value] }`), others incorrectly pass variables directly (e.g., `{ $in: variable }`). This inconsistency exists particularly between main application files (mostly correct) and migration files (mostly incorrect), and should be standardized to prevent runtime errors.

Applied to files:

  • module/project/templates/helper.js
  • models/project-categories.js
  • module/library/categories/helper.js
🧬 Code graph analysis (2)
controllers/v1/library/categories.js (3)
module/project/templates/helper.js (1)
  • libraryCategoriesHelper (17-17)
module/userProjects/helper.js (1)
  • libraryCategoriesHelper (9-9)
generics/helpers/utils.js (1)
  • filterQuery (965-965)
module/project/templates/helper.js (1)
module/wishlist/helper.js (2)
  • startIndex (251-251)
  • endIndex (252-252)
🪛 Biome (2.1.2)
controllers/v1/library/categories.js

[error] 267-284: Promise executor functions should not be async.

(lint/suspicious/noAsyncPromiseExecutor)

module/library/categories/helper.js

[error] 1133-1232: Promise executor functions should not be async.

(lint/suspicious/noAsyncPromiseExecutor)


[error] 1244-1303: Promise executor functions should not be async.

(lint/suspicious/noAsyncPromiseExecutor)

🔇 Additional comments (8)
models/project-categories.js (2)

69-98: LGTM! Schema fields support hierarchical categories well.

The new fields (parentId, hasChildCategories, sequenceNumber, keywords, description, metaInformation) provide solid support for hierarchical categorization. The indexes on these fields will enable efficient hierarchy queries.

Minor note: description has both required: true and default: 'default' - the default ensures the required constraint is always satisfied, which works but means documents will always have this field populated automatically.


100-111: Compound indexes are well-designed for hierarchy queries.

The indexes on { parentId: 1, tenantId: 1, sequenceNumber: 1 } and { tenantId: 1, hasChildCategories: 1 } will efficiently support:

  • Fetching children of a parent in sequence order
  • Finding leaf/parent categories by tenant
module/library/categories/helper.js (3)

37-94: Well-implemented parent validation with circular reference detection.

The validateParentId method properly:

  • Validates ObjectId format
  • Checks parent exists in same tenant
  • Prevents self-parenting
  • Detects circular references via isDescendant

1133-1232: Async promise executor pattern is intentional.

Static analysis flagged this as an anti-pattern, but based on learnings, this codebase intentionally uses this pattern across database query methods for architectural uniformity.

The delete method implementation is solid:

  • Validates category exists
  • Checks for child categories before deletion
  • Checks for associated projects
  • Performs soft delete
  • Updates parent's hasChildCategories flag

910-951: Sequence number auto-assignment logic is correct.

The implementation properly finds the maximum sequence number among siblings (under the same parent or at root level) and increments by 1. This ensures unique ordering within each hierarchy level.

controllers/v1/library/categories.js (3)

240-256: Updated list method correctly integrates with new helper signature.

The method now passes req.searchText, req.query, and req.userDetails to the helper, and properly maps data to result for API response consistency.


266-285: delete controller method implementation is correct.

The async promise executor pattern is intentional in this codebase per learnings. The implementation properly:

  • Constructs filter query from request params
  • Delegates to helper
  • Maps response for API consistency
  • Handles errors appropriately

295-315: details method correctly handles getChildren query parameter.

The boolean conversion req.query.getChildren === 'true' is appropriate for query string parameters. The implementation follows the established controller patterns.

Copy link
Collaborator

@aks30 aks30 left a comment

Choose a reason for hiding this comment

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

Reviewed and added comments for all except - module/library/categories/helper.js, will do that later.

name: { externalId: 1, tenantId: 1 },
indexType: { unique: true },
},
{
Copy link
Collaborator

Choose a reason for hiding this comment

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

@vaivk369 Not sure what is the use of these compound indexes, can you highlight please

Copy link
Author

Choose a reason for hiding this comment

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

For Query (parentId + tenantId queries) @aks30

type: String,
index: true,
},
name: String,
Copy link
Collaborator

Choose a reason for hiding this comment

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

@vaivk369 Any reason to remove this?

Copy link
Author

Choose a reason for hiding this comment

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

if category name is updated, the name in project-templates become stale and then we would need to write sync job to update the names in project templates and projects. As we are alredy haaving id and external Id of the category, we can safely remove name.

_id: req.params._id,
getChildren: req.query.getChildren === 'true',
}
let projectCategories = await libraryCategoriesHelper.details(filterQuery, req.userDetails)
Copy link
Collaborator

Choose a reason for hiding this comment

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

@vaivk369 why do we need user detail to get category details?

Copy link
Author

@vaivk369 vaivk369 Jan 9, 2026

Choose a reason for hiding this comment

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

@aks30 to return category only if its tenantId = user's tenant Id t

return new Promise(async (resolve, reject) => {
try {
let projectCategories = await libraryCategoriesHelper.list(req)
let projectCategories = await libraryCategoriesHelper.list(req.searchText, req.query, req.userDetails)
Copy link
Collaborator

Choose a reason for hiding this comment

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

@vaivk369 why do we need user detail to get category listing?

Copy link
Author

Choose a reason for hiding this comment

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

we should get list of categories for the tenant user is belonging to right?
user categories model has tenant.
Also we should fetch categories which are having visibleToOrganizations set to orgId of the user

Copy link
Author

Choose a reason for hiding this comment

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

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.

2 participants