From cc5ff4869dd98b07c07625e064d4f8ef278d1db5 Mon Sep 17 00:00:00 2001 From: Weiko Date: Tue, 28 Jul 2026 09:53:42 +0200 Subject: [PATCH] perf: use cache for role validation (#23383) ## Context Role assignment validation queried PostgreSQL only to check whether a role exists and whether `canBeAssignedToUsers` is enabled. Both values already exist in `flatRoleMaps`. This validation runs when inviting users and assigning a role to a user workspace. ## What changed - Replace the role repository lookup with `flatRoleMaps` - Resolve the role through the existing keyed flat-map helper - Preserve the existing role-not-found and role-not-assignable errors - Replace unused TypeORM module wiring with the flat entity cache module ## Expected impact On a warm workspace cache, role assignment validation uses an O(1) map lookup and avoids a PostgreSQL round trip. Cold caches retain the normal workspace cache recomputation behavior. ## Validation - Typecheck reports no errors in the changed files - Existing validation outcomes and exception codes are preserved Review in cubic --- .../role-validation/role-validation.module.ts | 12 +++------- .../services/role-validation.service.ts | 22 +++++++++++-------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/packages/twenty-server/src/engine/metadata-modules/role-validation/role-validation.module.ts b/packages/twenty-server/src/engine/metadata-modules/role-validation/role-validation.module.ts index 2b83e2054c..2943b84a4f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role-validation/role-validation.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role-validation/role-validation.module.ts @@ -1,16 +1,10 @@ import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; +import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module'; import { RoleValidationService } from 'src/engine/metadata-modules/role-validation/services/role-validation.service'; -import { provideWorkspaceScopedRepository } from 'src/engine/twenty-orm/workspace-scoped-repository/provide-workspace-scoped-repository'; @Module({ - imports: [TypeOrmModule.forFeature([RoleEntity])], - providers: [ - RoleValidationService, - provideWorkspaceScopedRepository(RoleEntity), - ], + imports: [WorkspaceManyOrAllFlatEntityMapsCacheModule], + providers: [RoleValidationService], exports: [RoleValidationService], }) export class RoleValidationModule {} diff --git a/packages/twenty-server/src/engine/metadata-modules/role-validation/services/role-validation.service.ts b/packages/twenty-server/src/engine/metadata-modules/role-validation/services/role-validation.service.ts index e32cf4bfd6..c8721197b1 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role-validation/services/role-validation.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role-validation/services/role-validation.service.ts @@ -5,25 +5,29 @@ import { PermissionsExceptionCode, PermissionsExceptionMessage, } from 'src/engine/metadata-modules/permissions/permissions.exception'; -import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; -import { InjectWorkspaceScopedRepository } from 'src/engine/twenty-orm/workspace-scoped-repository/inject-workspace-scoped-repository.decorator'; -import { WorkspaceScopedRepository } from 'src/engine/twenty-orm/workspace-scoped-repository/workspace-scoped-repository'; +import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; +import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; @Injectable() export class RoleValidationService { constructor( - @InjectWorkspaceScopedRepository(RoleEntity) - private readonly roleRepository: WorkspaceScopedRepository, + private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, ) {} async validateRoleAssignableToUsersOrThrow( roleId: string, workspaceId: string, ): Promise { - const role = await this.roleRepository.findOne(workspaceId, { - where: { - id: roleId, - }, + const { flatRoleMaps } = + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: ['flatRoleMaps'], + }, + ); + const role = findFlatEntityByIdInFlatEntityMaps({ + flatEntityId: roleId, + flatEntityMaps: flatRoleMaps, }); if (!role) {