diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-22/1-22-instance-command-fast-1775749486425-auto-generated.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-22/1-22-instance-command-fast-1775749486425-auto-generated.ts new file mode 100644 index 0000000000..e5e2ba0edc --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-22/1-22-instance-command-fast-1775749486425-auto-generated.ts @@ -0,0 +1,17 @@ +import { QueryRunner } from 'typeorm'; + +import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator'; +import { FastInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/fast-instance-command.interface'; + +@RegisteredInstanceCommand('1.22.0', 1775749486425) +export class AutoGeneratedFastInstanceCommand implements FastInstanceCommand { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'CREATE INDEX "IDX_PERMISSION_FLAG_ROLE_ID" ON "core"."permissionFlag" ("roleId") ', + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query('DROP INDEX "core"."IDX_PERMISSION_FLAG_ROLE_ID"'); + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts index 2ef452acfd..38d11c310d 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts @@ -3,9 +3,11 @@ import { AddViewFieldGroupIdIndexOnViewFieldFastInstanceCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-instance-command-fast-1775129420309-add-view-field-group-id-index-on-view-field'; import { MigrateMessagingCalendarToCoreFastInstanceCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-instance-command-fast-1775165049548-migrate-messaging-calendar-to-core'; import { AddEmailThreadWidgetTypeFastInstanceCommand } from 'src/database/commands/upgrade-version-command/1-21/1-21-instance-command-fast-1775200000000-add-email-thread-widget-type'; +import { AutoGeneratedFastInstanceCommand } from 'src/database/commands/upgrade-version-command/1-22/1-22-instance-command-fast-1775749486425-auto-generated'; export const INSTANCE_COMMANDS = [ AddViewFieldGroupIdIndexOnViewFieldFastInstanceCommand, MigrateMessagingCalendarToCoreFastInstanceCommand, AddEmailThreadWidgetTypeFastInstanceCommand, + AutoGeneratedFastInstanceCommand, ]; diff --git a/packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.entity.ts b/packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.entity.ts index b1003527a9..b77fce9c1a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permission-flag/permission-flag.entity.ts @@ -3,6 +3,7 @@ import { Column, CreateDateColumn, Entity, + Index, JoinColumn, ManyToOne, PrimaryGeneratedColumn, @@ -16,6 +17,7 @@ import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-enti @Entity('permissionFlag') @Unique('IDX_PERMISSION_FLAG_FLAG_ROLE_ID_UNIQUE', ['flag', 'roleId']) +@Index('IDX_PERMISSION_FLAG_ROLE_ID', ['roleId']) export class PermissionFlagEntity extends SyncableEntity { @PrimaryGeneratedColumn('uuid') id: string; diff --git a/packages/twenty-server/src/engine/metadata-modules/role/services/workspace-roles-permissions-cache.service.ts b/packages/twenty-server/src/engine/metadata-modules/role/services/workspace-roles-permissions-cache.service.ts index c9f05cb3d9..a0e42b2d57 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/services/workspace-roles-permissions-cache.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/services/workspace-roles-permissions-cache.service.ts @@ -2,20 +2,26 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { PermissionFlagType } from 'twenty-shared/constants'; +import { STANDARD_OBJECTS } from 'twenty-shared/metadata'; import { type ObjectsPermissions, type ObjectsPermissionsByRoleId, type RestrictedFieldsPermissions, } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; -import { Repository } from 'typeorm'; -import { STANDARD_OBJECTS } from 'twenty-shared/metadata'; +import { IsNull, Repository } from 'typeorm'; import { WorkspaceCacheProvider } from 'src/engine/workspace-cache/interfaces/workspace-cache-provider.service'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { FieldPermissionEntity } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.entity'; +import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permission/object-permission.entity'; +import { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-flag/permission-flag.entity'; import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; +import { RowLevelPermissionPredicateGroupEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate-group.entity'; +import { RowLevelPermissionPredicateEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate.entity'; import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator'; +import { regroupEntitiesByRelatedEntityId } from 'src/engine/workspace-cache/utils/regroup-entities-by-related-entity-id'; const WORKFLOW_STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS = [ STANDARD_OBJECTS.workflow.universalIdentifier, @@ -31,6 +37,16 @@ export class WorkspaceRolesPermissionsCacheService extends WorkspaceCacheProvide private readonly objectMetadataRepository: Repository, @InjectRepository(RoleEntity) private readonly roleRepository: Repository, + @InjectRepository(ObjectPermissionEntity) + private readonly objectPermissionRepository: Repository, + @InjectRepository(PermissionFlagEntity) + private readonly permissionFlagRepository: Repository, + @InjectRepository(FieldPermissionEntity) + private readonly fieldPermissionRepository: Repository, + @InjectRepository(RowLevelPermissionPredicateEntity) + private readonly rowLevelPermissionPredicateRepository: Repository, + @InjectRepository(RowLevelPermissionPredicateGroupEntity) + private readonly rowLevelPermissionPredicateGroupRepository: Repository, ) { super(); } @@ -38,25 +54,74 @@ export class WorkspaceRolesPermissionsCacheService extends WorkspaceCacheProvide async computeForCache( workspaceId: string, ): Promise { - const roles = await this.roleRepository.find({ - where: { - workspaceId, - }, - relations: [ - 'objectPermissions', - 'permissionFlags', - 'fieldPermissions', - 'rowLevelPermissionPredicates', - 'rowLevelPermissionPredicateGroups', - ], - }); + const [ + roles, + objectPermissions, + permissionFlags, + fieldPermissions, + rowLevelPermissionPredicates, + rowLevelPermissionPredicateGroups, + workspaceObjectMetadataCollection, + ] = await Promise.all([ + this.roleRepository.find({ + where: { workspaceId }, + }), + this.objectPermissionRepository.find({ + where: { workspaceId }, + }), + this.permissionFlagRepository.find({ + where: { workspaceId }, + }), + this.fieldPermissionRepository.find({ + where: { workspaceId }, + }), + this.rowLevelPermissionPredicateRepository.find({ + where: { workspaceId, deletedAt: IsNull() }, + }), + this.rowLevelPermissionPredicateGroupRepository.find({ + where: { workspaceId, deletedAt: IsNull() }, + }), + this.getWorkspaceObjectMetadataCollection(workspaceId), + ]); - const workspaceObjectMetadataCollection = - await this.getWorkspaceObjectMetadataCollection(workspaceId); + const objectPermissionsByRoleId = + regroupEntitiesByRelatedEntityId<'objectPermission'>({ + entities: objectPermissions, + foreignKey: 'roleId', + }); + const permissionFlagsByRoleId = + regroupEntitiesByRelatedEntityId<'permissionFlag'>({ + entities: permissionFlags, + foreignKey: 'roleId', + }); + const fieldPermissionsByRoleId = + regroupEntitiesByRelatedEntityId<'fieldPermission'>({ + entities: fieldPermissions, + foreignKey: 'roleId', + }); + const rowLevelPermissionPredicatesByRoleId = + regroupEntitiesByRelatedEntityId<'rowLevelPermissionPredicate'>({ + entities: rowLevelPermissionPredicates, + foreignKey: 'roleId', + }); + const rowLevelPermissionPredicateGroupsByRoleId = + regroupEntitiesByRelatedEntityId<'rowLevelPermissionPredicateGroup'>({ + entities: rowLevelPermissionPredicateGroups, + foreignKey: 'roleId', + }); const permissionsByRoleId: ObjectsPermissionsByRoleId = {}; for (const role of roles) { + const roleObjectPermissions = + objectPermissionsByRoleId.get(role.id) ?? []; + const rolePermissionFlags = permissionFlagsByRoleId.get(role.id) ?? []; + const roleFieldPermissions = fieldPermissionsByRoleId.get(role.id) ?? []; + const roleRowLevelPermissionPredicates = + rowLevelPermissionPredicatesByRoleId.get(role.id) ?? []; + const roleRowLevelPermissionPredicateGroups = + rowLevelPermissionPredicateGroupsByRoleId.get(role.id) ?? []; + const objectRecordsPermissions: ObjectsPermissions = {}; for (const objectMetadata of workspaceObjectMetadataCollection) { @@ -77,14 +142,17 @@ export class WorkspaceRolesPermissionsCacheService extends WorkspaceCacheProvide universalIdentifier as (typeof WORKFLOW_STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS)[number], ) ) { - const hasWorkflowsPermissions = this.hasWorkflowsPermissions(role); + const hasWorkflowsPermissions = this.hasWorkflowsPermissions( + role, + rolePermissionFlags, + ); canRead = hasWorkflowsPermissions; canUpdate = hasWorkflowsPermissions; canSoftDelete = hasWorkflowsPermissions; canDestroy = hasWorkflowsPermissions; } else { - const objectRecordPermissionsOverride = role.objectPermissions.find( + const objectRecordPermissionsOverride = roleObjectPermissions.find( (objectPermission) => objectPermission.objectMetadataId === objectMetadataId, ); @@ -111,12 +179,12 @@ export class WorkspaceRolesPermissionsCacheService extends WorkspaceCacheProvide canDestroy, ); - const fieldPermissions = role.fieldPermissions.filter( + const fieldPermissionsForObject = roleFieldPermissions.filter( (fieldPermission) => fieldPermission.objectMetadataId === objectMetadataId, ); - for (const fieldPermission of fieldPermissions) { + for (const fieldPermission of fieldPermissionsForObject) { const isFieldLabelIdentifier = fieldPermission.fieldMetadataId === objectMetadata.labelIdentifierFieldMetadataId; @@ -141,14 +209,12 @@ export class WorkspaceRolesPermissionsCacheService extends WorkspaceCacheProvide canSoftDeleteObjectRecords: canSoftDelete, canDestroyObjectRecords: canDestroy, restrictedFields, - rowLevelPermissionPredicates: - role.rowLevelPermissionPredicates.filter( - (rowLevelPermissionPredicate) => - rowLevelPermissionPredicate.objectMetadataId === - objectMetadataId, - ), + rowLevelPermissionPredicates: roleRowLevelPermissionPredicates.filter( + (rowLevelPermissionPredicate) => + rowLevelPermissionPredicate.objectMetadataId === objectMetadataId, + ), rowLevelPermissionPredicateGroups: - role.rowLevelPermissionPredicateGroups.filter( + roleRowLevelPermissionPredicateGroups.filter( (rowLevelPermissionPredicateGroup) => rowLevelPermissionPredicateGroup.objectMetadataId === objectMetadataId, @@ -180,10 +246,13 @@ export class WorkspaceRolesPermissionsCacheService extends WorkspaceCacheProvide return workspaceObjectMetadata; } - private hasWorkflowsPermissions(role: RoleEntity): boolean { + private hasWorkflowsPermissions( + role: RoleEntity, + permissionFlags: PermissionFlagEntity[], + ): boolean { const hasWorkflowsPermissionFromRole = role.canUpdateAllSettings; const hasWorkflowsPermissionsFromSettingPermissions = isDefined( - role.permissionFlags.find( + permissionFlags.find( (permissionFlag) => permissionFlag.flag === PermissionFlagType.WORKFLOWS, ), diff --git a/packages/twenty-server/src/engine/workspace-cache/utils/regroup-entities-by-related-entity-id.ts b/packages/twenty-server/src/engine/workspace-cache/utils/regroup-entities-by-related-entity-id.ts index 3c75700ec2..6e547314aa 100644 --- a/packages/twenty-server/src/engine/workspace-cache/utils/regroup-entities-by-related-entity-id.ts +++ b/packages/twenty-server/src/engine/workspace-cache/utils/regroup-entities-by-related-entity-id.ts @@ -17,7 +17,7 @@ export const regroupEntitiesByRelatedEntityId = ({ entities, foreignKey, }: RegroupEntitiesByRelatedEntityIdArgs) => { - const entitiesByRelatedEntityId = new Map(); + const entitiesByRelatedEntityId = new Map[]>(); for (const entity of entities) { const currentRelatedEntityId = entity[ @@ -32,10 +32,7 @@ export const regroupEntitiesByRelatedEntityId = ({ entitiesByRelatedEntityId.set(currentRelatedEntityId, []); } - entitiesByRelatedEntityId.get(currentRelatedEntityId)!.push({ - id: entity.id, - universalIdentifier: entity.universalIdentifier, - }); + entitiesByRelatedEntityId.get(currentRelatedEntityId)!.push(entity); } return entitiesByRelatedEntityId;