Skip to content

Entity cache optmisation#1501

Open
rakeshSgr wants to merge 5 commits intomentorReadOptimisationfrom
entityCacheOptmisation
Open

Entity cache optmisation#1501
rakeshSgr wants to merge 5 commits intomentorReadOptimisationfrom
entityCacheOptmisation

Conversation

@rakeshSgr
Copy link
Copy Markdown
Collaborator

@rakeshSgr rakeshSgr commented Dec 15, 2025

Release Notes: Entity Cache Optimization

Key Features & Improvements

  • New Form Versions Cache Namespace: Added formVersions cache configuration with 24-hour TTL for organization-aware form version caching
  • Dual-Path Cache Eviction: Introduced dispatcher pattern in cache helper supporting both Redis and in-memory (InternalCache) eviction with configurable useInternal parameter
  • Form Version Caching System: Implemented cached retrieval of readAllFormsVersion() with automatic default organization data filtering via removeDefaultOrgData() utility
  • Organization Code Propagation: Extended organization context throughout service layer, replacing hardcoded default organization references with actual org codes
  • Kafka User Sync Enhancement: Added pushUncachedUsersToKafka() to push newly cached user details for cross-service consistency
  • Centralized Entity Type Cache Management: Consolidated cache invalidation via clearUserCachesForEntityType() helper for consistent mentor, mentee, session, and display properties cache handling
  • Parallel Kafka User Processing: Refactored readUser consumer for parallel user processing with per-user success tracking and comprehensive error reporting
  • Cache-First User Retrieval: Replaced direct database queries with getUserDetailedListUsingCache() across connections, notifications, and session services
  • Entity Service Query Optimization: Eliminated N+1 query patterns by fetching entity type details in list operations; streamlined cache invalidation
  • Organization-Contextual Entity Types: Updated entity type filtering to use actual organization context instead of default organization code in mentee, mentor, request session, and session services

Files Modified (21 files)

File Purpose
src/constants/common.js Added formVersions cache namespace configuration
src/controllers/v1/entity.js Pass organization_code to service layer
src/controllers/v1/form.js Updated form methods with org code parameter
src/database/queries/entity.js Extended attribute selection with tenant_code
src/database/queries/form.js Added tenant_code and organization_code to form queries
src/generics/cacheHelper.js Refactored cache eviction dispatcher, added formVersions module, added session/mentor/mentee eviction utilities
src/generics/kafka-communication.js Added pushUncachedUsersToKafka() function
src/generics/kafka/consumers/readUser.js Parallel user processing with result aggregation
src/generics/utils.js Added removeDefaultOrgData() utility function
src/helpers/entityTypeCache.js Added clearUserCachesForEntityType() helper function
src/requests/user.js Integrated Kafka push for uncached users
src/services/connections.js Switched to cache-enabled user detail retrieval
src/services/entity-type.js Consolidated cache invalidation, removed legacy helpers
src/services/entity.js Added org parameter, eliminated N+1 caching, centralized invalidation
src/services/form.js Added form version cache invalidation and cached retrieval
src/services/mentees.js Updated entity type filtering with mentee org context
src/services/mentors.js Updated entity type filtering with org code parameter
src/services/notifications.js Switched to cache-enabled mentor detail retrieval
src/services/requestSessions.js Updated entity type filtering with actual org context
src/services/sessions.js Replaced defaults with org code, integrated cache-enabled retrieval
src/services/users.js Added mentor/mentee cache invalidation on user operations

@rakeshSgr
Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Dec 15, 2025

✅ Actions performed

Full review triggered.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Dec 15, 2025

Walkthrough

This PR introduces organization-scoped caching and data filtering, centralizes cache invalidation logic, adds Kafka-based user caching, and refactors entity type handling. Changes thread organization_code through controllers, services, and database queries, introduce a new formVersions cache module, restructure cache eviction with internal/Redis support, and optimize user data retrieval with cached lookups.

Changes

Cohort / File(s) Summary
Configuration
src/constants/common.js
Adds formVersions cache namespace with 24-hour TTL and external cache backend.
Controllers
src/controllers/v1/entity.js, src/controllers/v1/form.js
Passes organization_code from decoded token to service layer; entity list now includes org context; form readAllFormsVersion now accepts both tenant_code and organization_code.
Database Queries
src/database/queries/entity.js, src/database/queries/form.js
Entity query extended to include tenant_code in attributes; form query updated to return organization_code alongside existing fields.
Cache Infrastructure
src/generics/cacheHelper.js
Major refactor: renames scanAndDeleteRedis, adds scanAndDelete dispatcher supporting internal (InternalCache) and Redis-based eviction via useInternal flag; introduces formVersions module (get/set/delete); extends eviction utilities across namespaces (sessions, entityTypes, forms, mentor, mentee, permissions, apiPermissions) with internal-cache support; removes mentorOrganizationId dependency in getEntityTypesWithMentorOrg.
Kafka Communication
src/generics/kafka-communication.js
Adds pushUncachedUsersToKafka helper to construct and publish user read events to Kafka.
Kafka Consumer
src/generics/kafka/consumers/readUser.js
Replaces legacy batch processing with parallel Promise.all flow: dispatches to MentorsHelper.read or MenteesHelper.read based on user.is_mentor flag; aggregates results and logs summary.
Utilities
src/generics/utils.js
Adds removeDefaultOrgData utility to filter out default-organization entries when tenant-specific overrides exist.
Cache Helpers
src/helpers/entityTypeCache.js
Introduces clearUserCachesForEntityType(organizationCode, tenantCode, modelName, entityValue) to centralize user cache invalidation across displayProperties, mentee, mentor, and session caches.
Request/Service Layer
src/requests/user.js, src/services/notifications.js, src/services/connections.js, src/services/users.js
getUserDetailedListUsingCache calls added after cache misses to push uncached users to Kafka; user cache invalidation added after user creation/update.
Entity Type Services
src/services/entity-type.js
Replaces internal cache-clear logic with entityTypeCache.clearUserCachesForEntityType; removes _clearUserCachesForEntityTypeChange, _getGroupedEntityTypesFromCache, clearAndRebuildCache static methods; uses explicit orgCode instead of defaults.orgCode.
Entity Service
src/services/entity.js
Centralizes cache invalidation via entityTypeCache; list method signature adds organization_code parameter; removes per-model cache deletion; optional chaining applied to entityTypeDetails access.
Form Service
src/services/form.js
readAllFormsVersion refactored: attempts formVersions cache fetch, falls back to DB query with default-org merge, normalizes via removeDefaultOrgData, caches result; cache invalidation added on form create/update.
Mentee/Mentor Services
src/services/mentees.js, src/services/mentors.js, src/services/sessions.js
Organization_code-based filtering replaces defaults.orgCode in removeDefaultOrgEntityTypes calls; getUserDetailedListUsingCache substitutes remote calls; session entity type logic references organization_code from accessor details.
Request Sessions
src/services/requestSessions.js
Uses explicit organizationCode and userDetails.organization_code instead of defaults.orgCode for removeDefaultOrgEntityTypes filtering.

Sequence Diagram(s)

sequenceDiagram
    participant KafkaConsumer as Kafka Consumer<br/>(readUser)
    participant Promise as Promise.all
    participant MentorHelper as MentorsHelper
    participant MenteeHelper as MenteesHelper
    participant DB as Database
    participant Log as Logger

    KafkaConsumer->>KafkaConsumer: Receive message with users array
    KafkaConsumer->>Promise: Map users to parallel tasks
    par User Processing
        Promise->>MentorHelper: Read (user_id, org_code, tenant_code)<br/>where is_mentor=true
        MentorHelper->>DB: Fetch mentor details
        DB-->>MentorHelper: Mentor data
        MentorHelper-->>Promise: {userId, is_mentor, success}
    and
        Promise->>MenteeHelper: Read (user_id, org_code, tenant_code)<br/>where is_mentor=false
        MenteeHelper->>DB: Fetch mentee details
        DB-->>MenteeHelper: Mentee data
        MenteeHelper-->>Promise: {userId, is_mentor, success}
    end
    Promise-->>KafkaConsumer: All results
    KafkaConsumer->>KafkaConsumer: Aggregate {results,<br/>successCount, totalCount}
    KafkaConsumer->>Log: "processing of read users<br/>complete: X/Y processed"
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • src/generics/cacheHelper.js: High-density refactor introducing dispatcher pattern, internal cache eviction paths, and reorganization of multiple namespace handlers—requires careful verification of routing logic and backward compatibility.
  • src/services/entity-type.js and src/services/entity.js: Significant consolidation of cache invalidation; removal of multiple static methods; organization-code aware filtering throughout—verify all cache-clear paths are correctly delegated.
  • src/services/form.js: readAllFormsVersion completely redesigned with cache-fallback logic, data normalization via removeDefaultOrgData, and default-org merging—trace end-to-end form retrieval flow and cache persistence.
  • Organization_code threading: Changes span 15+ files with organization_code added to parameters, queries, and filtering logic—audit all call sites to ensure consistent parameter propagation and correct default-org handling.
  • Kafka consumer refactor: Parallel processing replaces sequential batch logic—verify error handling, result aggregation, and logging correctness.

Possibly related PRs

  • Develop tenant changes #1334: Implements same tenant-awareness refactor threading tenant_code/organization_code through controllers, services, and database with tenant-scoped cache helpers.
  • Staging #1463: Modifies form-version retrieval/caching; this PR adds formVersions cache module while related PR removes caching from the same paths.
  • Session details api fix #1481: Modifies cacheHelper.js session cache APIs and key construction similarly to this PR's cache infrastructure refactoring.

Suggested reviewers

  • nevil-mathew

🐰 Through caches we hop and seeds we sow,
Organizations grow, with each org's flow,
Cache helpers unified, Kafka flows fast,
User data saved, no queries to cast!
🎉

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Entity cache optmisation' is relevant to a core aspect of the changeset, which includes significant refactoring of caching logic (formVersions, entityType cache invalidation, etc.), but contains a typo ('optmisation' instead of 'optimization') and is somewhat vague about the specific nature of the optimization. Consider clarifying the title to be more specific about the optimization's scope (e.g., 'Centralize entity type cache invalidation' or 'Optimize entity cache with formVersions support') and fix the typo.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch entityCacheOptmisation

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
Copy Markdown

@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: 6

Caution

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

⚠️ Outside diff range comments (4)
src/database/queries/entity.js (1)

45-55: options.where can override tenant scoping in findAllEntities / updateOneEntity

Because ...options is spread after where, any options.where from callers can overwrite the method’s own where (including tenant_code), which risks bypassing tenant isolation.

To harden this, merge options.where explicitly and always apply tenant_code last, instead of allowing callers to replace where wholesale. For example:

 static async findAllEntities(filter, tenantCode, options = {}) {
   try {
-    if (tenantCode) {
-      filter.tenant_code = tenantCode
-    }
-    return await Entity.findAll({
-      where: filter,
-      ...options,
-      raw: true,
-    })
+    const { where: optionsWhere = {}, ...restOptions } = options || {}
+
+    const where = {
+      ...filter,
+      ...optionsWhere,
+      ...(tenantCode ? { tenant_code: tenantCode } : {}),
+    }
+
+    return await Entity.findAll({
+      where,
+      ...restOptions,
+      raw: true,
+    })
   } catch (error) {
     throw error
   }
 }
 
 static async updateOneEntity(whereClause, tenantCode, update, options = {}) {
   try {
-    // MANDATORY: Include tenant_code in whereClause
-    const where = { ...(whereClause || {}), tenant_code: tenantCode }
-    const sanitized = { ...update }
-    delete sanitized.tenant_code
-    return await Entity.update(sanitized, {
-      where,
-      ...options,
-    })
+    const { where: optionsWhere = {}, ...restOptions } = options || {}
+
+    const where = {
+      ...(whereClause || {}),
+      ...optionsWhere,
+      ...(tenantCode ? { tenant_code: tenantCode } : {}),
+    }
+
+    const sanitized = { ...update }
+    delete sanitized.tenant_code
+
+    return await Entity.update(sanitized, {
+      where,
+      ...restOptions,
+    })
   } catch (error) {
     throw error
   }
 }

This follows the multi‑tenant pattern from earlier work (ensure tenant_code is enforced after merging caller options). Based on learnings, this should be treated as a must‑fix for tenant isolation.

Also applies to: 60-70

src/database/queries/form.js (1)

16-30: Fix unsafe spread of options.where (can throw at runtime)

In findOne, findFormsByFilter, and updateOneForm you destructure options.where without a default:

const { where: optionsWhere, ...otherOptions } = options
...
where: {
  ...optionsWhere,
  ...filterOrWhereClause,
}

If options.where is omitted (or options is {}), optionsWhere is undefined and ...optionsWhere will throw TypeError: Cannot convert undefined or null to object.

Recommend defaulting optionsWhere to an empty object (and guarding options):

- const { where: optionsWhere, ...otherOptions } = options
+ const { where: optionsWhere = {}, ...otherOptions } = options || {}

Apply this in all three methods so the “safe merge” pattern actually remains safe. Based on learnings, tenant scoping remains enforced while allowing caller filters.

Also applies to: 36-53, 59-76

src/services/sessions.js (1)

1713-1757: Use model‑preserving dedupe helper when combining session and accessor entity types

Here you build entityTypes by merging sessionEntityTypes and accessorEntityTypes and then prune with removeDefaultOrgEntityTypes(entityTypes, sessionAccessorDetails.organization_code) before passing into processDbResponse for mentor designation and custom entity text (Lines 1717‑1750). removeDefaultOrgEntityTypes deduplicates purely by value, ignoring model_names, so when the same value exists for multiple models (e.g. Session vs UserExtension), one model’s entity type can be dropped. That defeats the purpose described in _removeDefaultOrgEntityTypesPreservingModels, whose JSDoc explicitly says it exists “to prevent removing UserExtension entity types needed for designation processing when Session model has entity types with same values” (Lines 1815‑1833).

This is likely to surface as missing/incorrect mentor designations for orgs where Session and UserExtension share some entity type values.

Consider switching the combined-path pruning to _removeDefaultOrgEntityTypesPreservingModels while keeping the per‑model call on sessionEntityTypes as‑is. For example:

-			let entityTypes = []
+			let entityTypes = []
...
-				// Combine both sets
-				entityTypes = [...(sessionEntityTypes || []), ...(accessorEntityTypes || [])]
+				// Combine both sets
+				entityTypes = [...(sessionEntityTypes || []), ...(accessorEntityTypes || [])]
...
-			if (mentorExtension?.user_id) {
-				const validationData = removeDefaultOrgEntityTypes(
-					entityTypes,
-					sessionAccessorDetails.organization_code
-				)
+			if (mentorExtension?.user_id) {
+				const validationData = this._removeDefaultOrgEntityTypesPreservingModels(
+					entityTypes,
+					sessionAccessorDetails.organization_code
+				)

This keeps distinct entity types per value + model_names combination and still prefers the current org over defaults.

Also applies to: 1815-1833

src/generics/cacheHelper.js (1)

1108-1156: Pass organizationCode when calling _sendToKafkaBackground for mentor cache warming

mentor._sendToKafkaBackground now expects (userMappingData, tenantCode, organizationCode) and includes organizationCode in the Kafka payload:

const batchMessage = {
  tenantCode: tenantCode,
  organizationCode: organizationCode,
  userBatches: userBatches,
}

But in getMentorKafka you call it as:

const userMappingData = Object.fromEntries(userMentorEntries)
this._sendToKafkaBackground(userMappingData, tenantCode)

So organizationCode is always undefined in the batch message, which may break consumers expecting a valid org code for routing or logging.

Forward the organizationCode argument you already have in getMentorKafka:

-			const userMappingData = Object.fromEntries(userMentorEntries)
-			this._sendToKafkaBackground(userMappingData, tenantCode)
+			const userMappingData = Object.fromEntries(userMentorEntries)
+			this._sendToKafkaBackground(userMappingData, tenantCode, organizationCode)

Also applies to: 1166-1227

🧹 Nitpick comments (10)
src/services/connections.js (1)

267-283: Switch to cached user detail retrieval looks fine; consider minor optimization

Using userRequests.getUserDetailedListUsingCache(userIds, tenantCode) in both pending and connected-users flows keeps the existing .result mapping logic intact while enabling cache hits, so behavior should be preserved as long as the new helper returns the same structure.

Optionally, to reduce payload size and cache/HTTP overhead, you could deduplicate IDs before the call:

- const userIds = connectionsWithDetails.map((item) => item.friend_id)
+ const userIds = [...new Set(connectionsWithDetails.map((item) => item.friend_id))]

and similarly for the connected-users list.

Also applies to: 495-507

src/generics/utils.js (1)

1289-1300: removeDefaultOrgData correctly preserves tenant overrides over defaults

The new helper:

const orgData = new Set(data.filter((f) => f.organization_code !== defaultOrgCode).map((f) => f[key]))
return data.filter((f) => !(f.organization_code === defaultOrgCode && orgData.has(f[key])))

does the intended thing:

  • Keeps all non‑default‑org rows.
  • Drops default‑org rows only when there’s at least one non‑default row with the same key (e.g., type), so tenant/org-specific overrides win.
  • Retains default records when no override exists.

Two minor robustness tweaks you could consider (optional):

  • Guard against non-array input: if (!Array.isArray(data)) return data || [].
  • Document expected shape in a JSDoc comment (required fields: organization_code and the provided key).

Otherwise this utility and its export look solid for the form-versions cache usage.

Also applies to: 1359-1359

src/services/users.js (1)

171-227: Cache invalidation here is redundant with #updateUser and could be centralized

Inside #createOrUpdateUserAndOrg you now have:

const isAMentor = userExtensionData.roles.some((role) => role.title == common.MENTOR_ROLE)

try {
  if (!isNewUser && isAMentor) {
    await cacheHelper.mentor.delete(userDetails.data.result.tenant_code, userId)
  } else if (!isNewUser) {
    await cacheHelper.mentee.delete(userDetails.data.result.tenant_code, userId)
  }
} catch (error) {
  //  not require to clear the chache
}

But for the non‑new‑user path you always call #updateUser, which already:

  • Invalidates both mentee and mentor caches on role change, and
  • Invalidates the appropriate cache after a plain update.

So this block mostly results in a second, redundant delete against the same keys. It’s harmless functionally but adds extra cache calls.

Consider either:

  • Removing this new block and relying solely on #updateUser for user-cache invalidation, or
  • Moving all user cache invalidation into #createOrUpdateUserAndOrg and stripping it from #updateUser, so there’s a single well-defined place handling it.

That would keep behavior the same while simplifying the cache story.

src/requests/user.js (1)

874-885: Kafka publishing errors could break the main request flow.

The pushUncachedUsersToKafka call is awaited in the main execution path without try-catch. If Kafka publishing fails, it will propagate the error and potentially cause the entire getUserDetailedListUsingCache call to fail, even though the user data was successfully retrieved.

Consider wrapping this in a fire-and-forget pattern since caching is an optimization, not a critical path:

 		if (cacheHelper._internal.ENABLE_CACHE) {
 			let unCachedUsers = []
 			for (const userDetail of usersFromDb) {
 				unCachedUsers.push({
 					user_id: userDetail.user_id,
 					is_mentor: userDetail.is_mentor,
 					tenant_code: userDetail.tenant_code,
 					organization_code: userDetail.organization_code,
 				})
 			}
-			await kafkaCommunication.pushUncachedUsersToKafka(unCachedUsers)
+			// Fire-and-forget: Don't block main flow for background cache population
+			kafkaCommunication.pushUncachedUsersToKafka(unCachedUsers).catch((err) => {
+				console.error('Failed to push uncached users to Kafka:', err.message)
+			})
 		}
src/helpers/entityTypeCache.js (1)

490-494: getModelName calls are not wrapped in safeRun and could throw.

The Promise.all for fetching model names (lines 490-494) is outside the safeRun wrapper. If any of these queries fail, the entire function will throw and no cache clearing will happen. Consider wrapping this in try-catch with fallback values if model names are not critical for partial cache clearing.

-		const [menteeModel, mentorModel, sessionModel] = await Promise.all([
-			menteeExtensionQueries.getModelName(),
-			mentorExtensionQueries.getModelName(),
-			sessionQueries.getModelName(),
-		])
+		let menteeModel, mentorModel, sessionModel
+		try {
+			[menteeModel, mentorModel, sessionModel] = await Promise.all([
+				menteeExtensionQueries.getModelName(),
+				mentorExtensionQueries.getModelName(),
+				sessionQueries.getModelName(),
+			])
+		} catch (modelError) {
+			console.error('Failed to get model names for cache clearing:', modelError.message)
+			// Use common constants as fallback
+			menteeModel = common.userExtensionModelName
+			mentorModel = common.mentorExtensionModelName
+			sessionModel = common.sessionModelName
+		}
src/services/entity-type.js (2)

119-127: Pass full model_names array to cache invalidation helper

In update, you call:

await entityTypeCache.clearUserCachesForEntityType(
  orgCode,
  tenantCode,
  updatedEntity.model_names ? updatedEntity.model_names[0] : null,
  updatedEntity.value
)

If an entity type is associated with multiple models (i.e. model_names is an array), this only clears caches for the first model, potentially leaving stale cache entries for the others.

Safer to pass the whole array (letting the helper handle string/array gracefully):

-				updatedEntity.model_names ? updatedEntity.model_names[0] : null,
+				updatedEntity.model_names || null,

293-317: Avoid double‑calling clearUserCachesForEntityType in delete path

In delete, you call entityTypeCache.clearUserCachesForEntityType twice:

  1. Before deletion with entityToDelete.model_names (array).
  2. After deletion with just entityToDelete.model_names[0].

Given the helper already takes model_names and value, the second, narrower call is redundant and may be confusing to future maintainers.

Either drop the post‑delete call or keep a single call (pre‑ or post‑delete) using the full model_names array:

-			// Clear user caches since entity types affect user profiles
-			await entityTypeCache.clearUserCachesForEntityType(
-				organizationCode,
-				tenantCode,
-				entityToDelete.model_names ? entityToDelete.model_names[0] : null,
-				entityToDelete.value
-			)
src/services/entity.js (2)

36-49: Entity–entityType cache invalidation wiring looks consistent, with a small logging nit

Across create, update, and delete you now:

  • Retrieve entityTypeDetails (from createEntityWithValidation or via findEntityTypeById), and
  • Call entityTypeCache.clearUserCachesForEntityType(organization_code, tenantCode, model_names, value).

This is aligned with the centralized invalidation pattern introduced in entity-type service and should keep user-facing caches consistent when entities change.

Only minor nit: the inner catch in update logs "after entity creation" even though it’s in the update path:

console.error(`❌ Failed to invalidate entityType cache after entity creation:`, cacheError)

Consider updating the message to "after entity update" for clarity; behaviour is otherwise fine.

Also applies to: 118-141, 320-341


367-419: Tighten empty-result check in list

list now delegates to entityQueries.getAllEntitiesWithEntityTypeDetails and checks:

if (entities.rows == 0 || entities.count == 0) {
  ...
}

Since rows is an array, the entities.rows == 0 comparison will always be false; only count == 0 actually drives the NO_RESULTS_FOUND response.

For clarity and to avoid confusion, prefer an explicit length check:

-			if (entities.rows == 0 || entities.count == 0) {
+			if (!entities.rows?.length || entities.count === 0) {
src/generics/cacheHelper.js (1)

264-271: scanAndDelete dispatcher behaviour is consistent, but JSDoc is now outdated

You’ve split the old scanAndDelete into:

  • scanAndDeleteRedis(pattern, { batchSize, unlink }), and
  • A new dispatcher scanAndDelete(pattern, { useInternal = false }) that chooses internal vs Redis deletion.

Internal call sites (evictNamespace, evictOrgByPattern, evictTenantByPattern, and various evict* helpers) only pass { useInternal } or nothing, so behaviour is consistent and safe.

However, the JSDoc above still documents scanAndDelete(pattern, opts) with opts.batchSize / opts.unlink, which are no longer accepted by the public scanAndDelete export. If any external callers pass those options, they’ll silently be ignored.

Consider either:

  • Updating the comment to reflect the new dispatcher semantics, or
  • Extending the dispatcher signature to forward batchSize/unlink through to scanAndDeleteRedis when useInternal is false.

Also applies to: 281-311

📜 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 d55b556 and 8500cd2.

⛔ Files ignored due to path filters (1)
  • src/configs/kafka.js is excluded by !src/configs/**
📒 Files selected for processing (21)
  • src/constants/common.js (1 hunks)
  • src/controllers/v1/entity.js (1 hunks)
  • src/controllers/v1/form.js (2 hunks)
  • src/database/queries/entity.js (1 hunks)
  • src/database/queries/form.js (1 hunks)
  • src/generics/cacheHelper.js (12 hunks)
  • src/generics/kafka-communication.js (1 hunks)
  • src/generics/kafka/consumers/readUser.js (1 hunks)
  • src/generics/utils.js (3 hunks)
  • src/helpers/entityTypeCache.js (2 hunks)
  • src/requests/user.js (2 hunks)
  • src/services/connections.js (2 hunks)
  • src/services/entity-type.js (5 hunks)
  • src/services/entity.js (6 hunks)
  • src/services/form.js (3 hunks)
  • src/services/mentees.js (2 hunks)
  • src/services/mentors.js (1 hunks)
  • src/services/notifications.js (1 hunks)
  • src/services/requestSessions.js (2 hunks)
  • src/services/sessions.js (8 hunks)
  • src/services/users.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
src/services/**

⚙️ CodeRabbit configuration file

This is core business logic. Please check for correctness, efficiency, and potential edge cases.

Files:

  • src/services/notifications.js
  • src/services/requestSessions.js
  • src/services/mentees.js
  • src/services/users.js
  • src/services/mentors.js
  • src/services/connections.js
  • src/services/sessions.js
  • src/services/form.js
  • src/services/entity.js
  • src/services/entity-type.js
src/database/queries/**

⚙️ CodeRabbit configuration file

Review database queries for performance. Check for N+1 problems and ensure indexes can be used.

Files:

  • src/database/queries/entity.js
  • src/database/queries/form.js
src/controllers/**

⚙️ CodeRabbit configuration file

These are API controllers. Focus on request/response handling, security (auth middleware usage), and proper status codes.

Files:

  • src/controllers/v1/entity.js
  • src/controllers/v1/form.js
🧠 Learnings (8)
📚 Learning: 2025-11-06T06:25:57.830Z
Learnt from: sumanvpacewisdom
Repo: ELEVATE-Project/mentoring PR: 1426
File: src/database/migrations/20251020081719-add-orgEntity-type.js:10-12
Timestamp: 2025-11-06T06:25:57.830Z
Learning: In the ELEVATE-Project/mentoring repository, entity type migrations (like 20251020081719-add-orgEntity-type.js) run before tenant-specific migrations. Therefore, down migrations for these entity types do not need to filter by organization_code and tenant_code, as multi-tenant data does not exist at the time these migrations execute.

Applied to files:

  • src/generics/utils.js
  • src/services/requestSessions.js
  • src/database/queries/entity.js
  • src/database/queries/form.js
  • src/services/mentees.js
  • src/services/mentors.js
  • src/helpers/entityTypeCache.js
  • src/services/sessions.js
  • src/controllers/v1/entity.js
  • src/generics/cacheHelper.js
  • src/services/entity.js
  • src/services/entity-type.js
📚 Learning: 2025-11-04T10:36:50.155Z
Learnt from: rakeshSgr
Repo: ELEVATE-Project/mentoring PR: 1442
File: src/services/sessions.js:1822-1831
Timestamp: 2025-11-04T10:36:50.155Z
Learning: In the ELEVATE-Project/mentoring codebase, when `mentorId` or `session.mentor_id` exist in the sessions service, the `mentorExtensionQueries.getMentorExtension()` call is guaranteed to return a valid object with a `name` property. Defensive null checks like `mentorDetails?.name` are not necessary when the mentor ID guard condition is present.

Applied to files:

  • src/services/notifications.js
  • src/services/mentees.js
  • src/services/mentors.js
  • src/services/sessions.js
📚 Learning: 2025-09-16T11:03:05.949Z
Learnt from: sumanvpacewisdom
Repo: ELEVATE-Project/mentoring PR: 1334
File: src/controllers/v1/sessions.js:502-524
Timestamp: 2025-09-16T11:03:05.949Z
Learning: The removeAllSessions endpoint in src/controllers/v1/sessions.js is an internal API that intentionally accepts user_id, organization_code, and tenant_code from the request body rather than deriving them from req.decodedToken, as it's designed for service-to-service communication where the calling service specifies the tenant context.

Applied to files:

  • src/services/requestSessions.js
  • src/services/sessions.js
  • src/controllers/v1/entity.js
  • src/generics/cacheHelper.js
📚 Learning: 2025-09-16T11:06:06.180Z
Learnt from: sumanvpacewisdom
Repo: ELEVATE-Project/mentoring PR: 1334
File: src/controllers/v1/entity-type.js:42-44
Timestamp: 2025-09-16T11:06:06.180Z
Learning: The entityTypeService.update method in src/services/entity-type.js has the signature: update(bodyData, id, loggedInUserId, orgCode, tenantCode, roles) - it includes the loggedInUserId parameter between id and orgCode.

Applied to files:

  • src/services/requestSessions.js
  • src/database/queries/entity.js
  • src/services/mentees.js
  • src/services/mentors.js
  • src/helpers/entityTypeCache.js
  • src/services/sessions.js
  • src/controllers/v1/entity.js
  • src/services/entity.js
  • src/services/entity-type.js
  • src/controllers/v1/form.js
📚 Learning: 2025-08-17T08:50:22.819Z
Learnt from: sumanvpacewisdom
Repo: ELEVATE-Project/mentoring PR: 1319
File: src/database/queries/reportTypes.js:13-19
Timestamp: 2025-08-17T08:50:22.819Z
Learning: In multi-tenant database query methods, when accepting an options parameter that gets spread into Sequelize queries, ensure that critical fields like tenant_code are applied after spreading options to prevent bypassing tenant isolation. Use pattern: `const { where: optionsWhere = {}, ...rest } = options || {}; const where = { ...optionsWhere, tenant_code: tenantCode }` to safely merge caller options while enforcing tenant scoping.

Applied to files:

  • src/database/queries/entity.js
  • src/database/queries/form.js
📚 Learning: 2025-12-03T08:35:14.163Z
Learnt from: rakeshSgr
Repo: ELEVATE-Project/mentoring PR: 1481
File: src/database/queries/sessionAttendees.js:304-330
Timestamp: 2025-12-03T08:35:14.163Z
Learning: In Sequelize queries with LEFT JOINs via include, tenant_code filtering on the main table's where clause does NOT automatically apply to the joined table. To enforce tenant isolation across joins, add a where clause with tenant_code to each include block, especially when joining tables that have tenant_code fields. The where clause on an include becomes part of the JOIN's ON condition, properly scoping the join itself.

Applied to files:

  • src/database/queries/entity.js
📚 Learning: 2025-07-31T09:34:29.508Z
Learnt from: sumanvpacewisdom
Repo: ELEVATE-Project/mentoring PR: 1281
File: src/controllers/v1/connections.js:16-17
Timestamp: 2025-07-31T09:34:29.508Z
Learning: In the ELEVATE-Project/mentoring repository, validation for req.decodedToken fields (including tenant_code, organization_id, id, etc.) is not required in controller methods. The project handles token validation at a different layer and does not need additional validation checks in individual controller methods.

Applied to files:

  • src/controllers/v1/entity.js
📚 Learning: 2025-09-16T10:57:49.662Z
Learnt from: sumanvpacewisdom
Repo: ELEVATE-Project/mentoring PR: 1334
File: src/controllers/v1/org-admin.js:117-121
Timestamp: 2025-09-16T10:57:49.662Z
Learning: In the ELEVATE-Project/mentoring repository, internal APIs that are called by other services (like user service) may intentionally accept tenant_code from the request body rather than using req.decodedToken.tenant_code, as this allows the calling service to specify the tenant context for the operation. This is different from external APIs where token-based tenant context is preferred.

Applied to files:

  • src/generics/cacheHelper.js
🧬 Code graph analysis (12)
src/generics/kafka-communication.js (2)
src/services/sessions.js (4)
  • payload (538-556)
  • payload (2214-2231)
  • payload (2361-2376)
  • payload (3605-3655)
src/helpers/getDefaultOrgId.js (1)
  • process (11-11)
src/generics/utils.js (4)
src/services/users.js (1)
  • data (230-237)
src/database/queries/organisationExtension.js (1)
  • data (56-56)
src/scripts/viewsScript.js (1)
  • defaultOrgCode (12-12)
src/services/admin.js (1)
  • orgData (1812-1812)
src/requests/user.js (1)
src/generics/cacheHelper.js (3)
  • kafkaCommunication (17-17)
  • usersFromDb (1201-1201)
  • usersFromDb (1456-1461)
src/services/requestSessions.js (2)
src/generics/utils.js (1)
  • removeDefaultOrgEntityTypes (473-485)
src/controllers/v1/platform.js (1)
  • organizationCode (14-14)
src/generics/kafka/consumers/readUser.js (1)
src/generics/kafka/consumers/readSession.js (5)
  • message (9-9)
  • message (61-61)
  • results (17-17)
  • successCount (52-52)
  • totalCount (53-53)
src/services/mentees.js (2)
src/services/mentors.js (10)
  • validationData (86-95)
  • validationData (464-464)
  • validationData (571-571)
  • validationData (989-989)
  • validationData (1324-1329)
  • entityTypes (454-458)
  • entityTypes (563-567)
  • entityTypes (979-983)
  • mentorExtension (775-780)
  • mentorExtension (914-914)
src/generics/utils.js (4)
  • removeDefaultOrgEntityTypes (473-485)
  • entityTypes (516-516)
  • entityTypes (578-578)
  • entityTypes (1234-1251)
src/services/users.js (2)
src/generics/utils.js (1)
  • isAMentor (125-127)
src/services/feedback.js (1)
  • isAMentor (151-151)
src/services/mentors.js (1)
src/generics/utils.js (4)
  • removeDefaultOrgEntityTypes (473-485)
  • entityTypes (516-516)
  • entityTypes (578-578)
  • entityTypes (1234-1251)
src/helpers/entityTypeCache.js (1)
src/services/entity-type.js (1)
  • menteeExtensionQueries (4-4)
src/services/connections.js (2)
src/requests/user.js (6)
  • userDetails (186-186)
  • userDetails (243-243)
  • userDetails (364-364)
  • userDetails (456-467)
  • userDetails (588-588)
  • userDetails (617-617)
src/services/users.js (3)
  • userDetails (32-32)
  • userDetails (172-172)
  • userRequests (4-4)
src/services/sessions.js (1)
src/generics/utils.js (1)
  • removeDefaultOrgEntityTypes (473-485)
src/controllers/v1/entity.js (1)
src/controllers/v1/admin.js (2)
  • req (177-177)
  • req (217-217)
🪛 Biome (2.1.2)
src/services/form.js

[error] 243-243: Do not shadow the global "Error" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)


[error] 257-257: Do not shadow the global "Error" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

🔇 Additional comments (18)
src/database/queries/entity.js (1)

139-149: Adding tenant_code to selected attributes looks good

Including tenant_code in the projection for getAllEntitiesWithEntityTypeDetails keeps the query scoped as before while exposing tenant context to callers/cache logic. No functional or performance concerns here.

src/database/queries/form.js (1)

88-97: Expanded attributes for form versions look correct

Including tenant_code and organization_code alongside id, type, and version in findAllTypeFormVersion matches the multi-tenant use case (e.g., for downstream removeDefaultOrgData logic) and keeps the filter on tenant_code/organization_code unchanged.

src/services/requestSessions.js (1)

131-140: Using actual org code for entity-type filtering is an improvement

In both create and getInfo, passing the contextual org code to removeDefaultOrgEntityTypes:

  • organizationCode in create
  • userDetails.organization_code in getInfo

ensures that, when both default-org and org-specific entity types exist for the same value, the org-specific variants are preferred, which matches the documented behavior of the helper.

Just make sure all call sites to create and getInfo reliably provide a non-empty organization code; otherwise the behavior degenerates to “first entity wins” with no org preference.

Also applies to: 684-691

src/services/notifications.js (1)

133-165: Cached mentor lookup is consistent with other flows

Replacing getUserDetailedList with getUserDetailedListUsingCache(mentorIds, tenantCode, false, true) aligns this path with the rest of the service layer’s cache usage while keeping the downstream logic (userAccounts.result[0]) unchanged.

If getUserDetailedListUsingCache can ever return a non-success response or a shape without .result, you may want a quick guard before accessing userAccounts.result.length to avoid runtime errors, but the change itself is coherent.

src/controllers/v1/entity.js (1)

103-112: Threading organization_code into entityService.list is appropriate

Passing both req.decodedToken.tenant_code and req.decodedToken.organization_code into entityService.list gives the service full tenant/org context for entity retrieval and caching, with no change to request/response handling in the controller.

src/controllers/v1/form.js (2)

42-51: No behavioral change in update block

The extra blank line after formsService.update(...) has no functional impact; method behavior is unchanged.


65-72: Passing org code to readAllFormsVersion correctly aligns with service expectations

Calling:

formsService.readAllFormsVersion(
  req.decodedToken.tenant_code,
  req.decodedToken.organization_code
)

gives the form service both tenant and organization context for the new form-versions cache and default-org filtering logic, and fits the described updated service signature.

src/services/mentors.js (1)

571-571: LGTM - Consistent with explicit orgCode handling.

The change correctly uses the explicit orgCode parameter instead of defaults.orgCode for filtering entity types during mentor extension update, aligning with the broader PR goal of organization-scoped entity type handling.

src/services/mentees.js (2)

123-123: LGTM - Correctly uses the mentee's organization context for entity type filtering.

Using mentee.organization_code ensures that entity types are filtered based on the profile owner's organization, which is the correct behavior for the read endpoint.


2220-2220: No changes needed. The code is safe.

At line 2148, an early return guard ensures requestedUserExtension is non-null before line 2216 is reached. Therefore, accessing requestedUserExtension.organization_code on line 2216 is safe, and the filtering at line 2220 using mentorExtension.organization_code is consistent with the data flow.

src/constants/common.js (1)

313-318: LGTM - New cache namespace follows established patterns.

The formVersions cache namespace is correctly structured with consistent configuration (enabled, 1-day TTL, not using internal cache) matching the existing namespace definitions.

src/generics/kafka/consumers/readUser.js (1)

7-66: LGTM - Well-structured parallel processing with proper error isolation.

The refactored consumer correctly:

  1. Validates the users array before processing
  2. Uses Promise.all for parallel processing, improving throughput
  3. Isolates errors per-user, preventing one failure from affecting others
  4. Returns comprehensive results with success/total counts for observability

The function signatures for MentorsHelper.read and MenteesHelper.read are correctly called with appropriate default values for optional parameters.

src/helpers/entityTypeCache.js (1)

469-515: All cache eviction methods referenced in the function exist in the cacheHelper implementation: evictMentee (line 1298), evictMentor (line 1054), and evictSessions (line 413) in src/generics/cacheHelper.js.

src/services/entity-type.js (1)

237-264: Pruning user entity types by org code looks correct

readUserEntityTypes now fetches from both default and current org/tenant and then prunes via removeDefaultOrgEntityTypes(entityTypes, orgCode) (Line 237). This matches the intended behaviour of preferring org‑specific entity types over defaults when values collide.

No issues from a correctness or caching perspective.

src/generics/cacheHelper.js (3)

701-703: EntityTypes cache: clearAll + mentor-org lookup changes look sound

  • entityTypes.clearAll now resolves useInternal via nsUseInternal('entityTypes') and passes it into evictNamespace, so entityTypes eviction honours the configured backend (Redis vs Internal) (Lines 701‑703).
  • getEntityTypesWithMentorOrg has been simplified to just call getAllEntityTypesForModel(tenantCode, currentOrgCode, modelName) (Lines 805‑808). Given that getAllEntityTypesForModel already merges current org and defaults, this matches the usage in sessions.details, which now passes sessionAccessorDetails.organization_code as currentOrgCode.

These changes are consistent with the new centralized entityType caching strategy.

Also applies to: 805-808


413-417: New per-tenant eviction helpers for sessions/mentor/mentee are consistent with key patterns

  • sessions.evictSessions(tenantCode) evicts tenant:${tenantCode}:sessions:*, matching the keys built via buildKey({ tenantCode, ns: 'sessions', id }) (Lines 413‑417).
  • mentor.evictMentor(tenantCode) and mentee.evictMentee(tenantCode) evict tenant:${tenantCode}:mentor:* and tenant:${tenantCode}:mentee:* respectively (Lines 1054‑1058, 1298‑1304), consistent with the mentor/mentee key builders.

These helpers provide a clean way to blow away all per-tenant session and profile caches when needed, and correctly respect the namespace-specific useInternal configuration.

Also applies to: 1054-1058, 1298-1304


1895-1944: formVersions cache helper is correctly wired into the public API

The new formVersions helper provides get/set/delete with:

  • Keys scoped as tenant:${tenantCode}:org:${orgCode}:formVersions via buildKey.
  • TTL of 1 day on writes using setScoped.
  • nsUseInternal('formVersions') to respect internal vs Redis configuration on reads and deletes.

Exporting formVersions in module.exports (Line 1979‑1980) makes it available alongside other namespace helpers. No issues spotted in key construction, TTL usage, or eviction behaviour.

Also applies to: 1979-1980

src/services/sessions.js (1)

401-419: and

Comment thread src/generics/kafka-communication.js
Comment thread src/requests/user.js Outdated

const emailEncryption = require('@utils/emailEncryption')
const _ = require('lodash')
const organization = require('@validators/v1/organization')
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if 'organization' validator is used anywhere in the file
rg -n 'organization' src/requests/user.js | grep -v "import\|require\|organizationId\|organizationCode\|organization_"

Repository: ELEVATE-Project/mentoring

Length of output: 3058


🏁 Script executed:

cat -n src/requests/user.js | head -100

Repository: ELEVATE-Project/mentoring

Length of output: 4525


🏁 Script executed:

rg -n 'organization\(' src/requests/user.js

Repository: ELEVATE-Project/mentoring

Length of output: 51


Remove unused import.

The organization validator imported on line 28 is not used anywhere in the file and should be removed.

🤖 Prompt for AI Agents
In src/requests/user.js around line 28, the organization validator is imported
but not used; remove the unused import statement "const organization =
require('@validators/v1/organization')" from the file to clean up unused code
and avoid linter errors.

Comment thread src/services/entity-type.js
Comment thread src/services/form.js
Comment thread src/services/form.js Outdated
Comment thread src/services/form.js Outdated
Comment on lines +479 to +481

clearPromises.push(safeRun(cacheHelper.displayProperties.delete(tenantCode, organizationCode)))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What is this displayProperties ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

displayProperties are driven from entity types


const clearPromises = []

clearPromises.push(safeRun(cacheHelper.displayProperties.delete(tenantCode, organizationCode)))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

What is this displayProperties? Wont this break the flow?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

displayProperties are used in frontend to show or hide the entity types for the mentor details page.
we define below keys in the meta of entityType, based on these data frontend decide what do

                                   key: 'organization',
				label: 'Organization',
				visible: true,
				visibility: 'main',
				sequence: 1,

Comment on lines +484 to +488
clearPromises.push(
safeRun(cacheHelper.entityTypes.delete(tenantCode, organizationCode, model, entityValue))
)
}
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why do we need model names in the entity type cache? Can a same entity type code exist with multiple model names?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

within a org can't be created same entity type

Comment on lines +501 to +509
if (shouldClearProfileCaches) {
clearPromises.push(safeRun(cacheHelper.mentee.evictMentee(tenantCode)))
clearPromises.push(safeRun(cacheHelper.mentor.evictMentor(tenantCode)))
}

// Clear session caches
if (shouldClearSessionCaches) {
clearPromises.push(safeRun(cacheHelper.sessions.evictSessions(tenantCode)))
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Do we need to clear out all the tenants' cache? Wouldn't this be only required when it's the default organisation?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

we are deleting for that tenant, not for all

if (entityValue) {
for (const model of modelNames) {
clearPromises.push(
safeRun(cacheHelper.entityTypes.delete(tenantCode, organizationCode, model, entityValue))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If the default organisation's entity/entity type is getting updated, shouldn't we clear out all other orgs' caches as well?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

No, currently its not done. we have API's to clear , we can use that if any any default organisation entity/ forms/ notifications etc create / updated

Comment thread src/services/entity-type.js Outdated
Comment thread src/services/entity-type.js
@nevil-mathew
Copy link
Copy Markdown
Collaborator

Comments related to cache clearing added in entity type might be applicable to entities and forms, or any place where default org data is shared with other orgs. Please check that as well.

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