[permissions] Improve performances using a cache for userWorkspaces roles (#11587)
In this PR we are
- introducing a cached map `{ userworkspaceId: roleId } `to reduce calls
to get a userWorkspace's role (we were having N+1 around that with
combinedFindMany queries and generally having a lot of avoidable
queries)
- using the roles permissions cache (`{ roleId: { objectNameSingular:
{ canRead: bool, canUpdate: bool, ...} } `) in Permissions V1's
userHasObjectPermission, in order to 1) improve performances to avoid
calls to get roles 2) start using our permissions cache
This commit is contained in:
@ -5,7 +5,7 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat
|
|||||||
import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permission/object-permission.entity';
|
import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permission/object-permission.entity';
|
||||||
import { ObjectPermissionService } from 'src/engine/metadata-modules/object-permission/object-permission.service';
|
import { ObjectPermissionService } from 'src/engine/metadata-modules/object-permission/object-permission.service';
|
||||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||||
import { WorkspaceRolesPermissionsCacheModule } from 'src/engine/metadata-modules/workspace-roles-permissions-cache/workspace-roles-permissions-cache.module';
|
import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -13,7 +13,7 @@ import { WorkspaceRolesPermissionsCacheModule } from 'src/engine/metadata-module
|
|||||||
[ObjectPermissionEntity, RoleEntity, ObjectMetadataEntity],
|
[ObjectPermissionEntity, RoleEntity, ObjectMetadataEntity],
|
||||||
'metadata',
|
'metadata',
|
||||||
),
|
),
|
||||||
WorkspaceRolesPermissionsCacheModule,
|
WorkspacePermissionsCacheModule,
|
||||||
],
|
],
|
||||||
providers: [ObjectPermissionService],
|
providers: [ObjectPermissionService],
|
||||||
exports: [ObjectPermissionService],
|
exports: [ObjectPermissionService],
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import {
|
|||||||
PermissionsExceptionMessage,
|
PermissionsExceptionMessage,
|
||||||
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||||
import { WorkspaceRolesPermissionsCacheService } from 'src/engine/metadata-modules/workspace-roles-permissions-cache/workspace-roles-permissions-cache.service';
|
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
|
||||||
|
|
||||||
export class ObjectPermissionService {
|
export class ObjectPermissionService {
|
||||||
constructor(
|
constructor(
|
||||||
@ -22,7 +22,7 @@ export class ObjectPermissionService {
|
|||||||
private readonly roleRepository: Repository<RoleEntity>,
|
private readonly roleRepository: Repository<RoleEntity>,
|
||||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||||
private readonly workspaceRolesPermissionsCacheService: WorkspaceRolesPermissionsCacheService,
|
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async upsertObjectPermission({
|
public async upsertObjectPermission({
|
||||||
@ -54,9 +54,10 @@ export class ObjectPermissionService {
|
|||||||
throw new Error('Failed to upsert object permission');
|
throw new Error('Failed to upsert object permission');
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.workspaceRolesPermissionsCacheService.recomputeRolesPermissionsCache(
|
await this.workspacePermissionsCacheService.recomputeRolesPermissionsCache(
|
||||||
{
|
{
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
roleIds: [input.roleId],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { PermissionsService } from 'src/engine/metadata-modules/permissions/perm
|
|||||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||||
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
||||||
import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module';
|
import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module';
|
||||||
|
import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -14,6 +15,7 @@ import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.
|
|||||||
FeatureFlagModule,
|
FeatureFlagModule,
|
||||||
TypeOrmModule.forFeature([UserWorkspace], 'core'),
|
TypeOrmModule.forFeature([UserWorkspace], 'core'),
|
||||||
UserRoleModule,
|
UserRoleModule,
|
||||||
|
WorkspacePermissionsCacheModule,
|
||||||
],
|
],
|
||||||
providers: [PermissionsService],
|
providers: [PermissionsService],
|
||||||
exports: [PermissionsService],
|
exports: [PermissionsService],
|
||||||
|
|||||||
@ -7,18 +7,24 @@ import {
|
|||||||
AuthException,
|
AuthException,
|
||||||
AuthExceptionCode,
|
AuthExceptionCode,
|
||||||
} from 'src/engine/core-modules/auth/auth.exception';
|
} from 'src/engine/core-modules/auth/auth.exception';
|
||||||
|
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||||
|
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||||
import {
|
import {
|
||||||
PermissionsException,
|
PermissionsException,
|
||||||
PermissionsExceptionCode,
|
PermissionsExceptionCode,
|
||||||
PermissionsExceptionMessage,
|
PermissionsExceptionMessage,
|
||||||
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
|
||||||
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
||||||
|
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PermissionsService {
|
export class PermissionsService {
|
||||||
constructor(private readonly userRoleService: UserRoleService) {}
|
constructor(
|
||||||
|
private readonly userRoleService: UserRoleService,
|
||||||
|
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
|
||||||
|
private readonly featureFlagService: FeatureFlagService,
|
||||||
|
) {}
|
||||||
|
|
||||||
public async getUserWorkspacePermissions({
|
public async getUserWorkspacePermissions({
|
||||||
userWorkspaceId,
|
userWorkspaceId,
|
||||||
@ -124,11 +130,21 @@ export class PermissionsService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const settingPermissions = roleOfUserWorkspace.settingPermissions ?? [];
|
const isPermissionsV2Enabled =
|
||||||
|
await this.featureFlagService.isFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsPermissionsV2Enabled,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
return settingPermissions.some(
|
if (isPermissionsV2Enabled) {
|
||||||
(settingPermission) => settingPermission.setting === setting,
|
const settingPermissions = roleOfUserWorkspace.settingPermissions ?? [];
|
||||||
);
|
|
||||||
|
return settingPermissions.some(
|
||||||
|
(settingPermission) => settingPermission.setting === setting,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async userHasObjectRecordsPermission({
|
public async userHasObjectRecordsPermission({
|
||||||
@ -142,6 +158,18 @@ export class PermissionsService {
|
|||||||
requiredPermission: PermissionsOnAllObjectRecords;
|
requiredPermission: PermissionsOnAllObjectRecords;
|
||||||
isExecutedByApiKey: boolean;
|
isExecutedByApiKey: boolean;
|
||||||
}): Promise<boolean> {
|
}): Promise<boolean> {
|
||||||
|
const isPermissionsV2Enabled =
|
||||||
|
await this.featureFlagService.isFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsPermissionsV2Enabled,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isPermissionsV2Enabled) {
|
||||||
|
throw new Error(
|
||||||
|
'This should not be called once Permissions V2 is enabled',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (isExecutedByApiKey) {
|
if (isExecutedByApiKey) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -153,31 +181,51 @@ export class PermissionsService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [roleOfUserWorkspace] = await this.userRoleService
|
const roleIdOfUserWorkspace =
|
||||||
.getRolesByUserWorkspaces({
|
await this.userRoleService.getRoleIdForUserWorkspace({
|
||||||
userWorkspaceIds: [userWorkspaceId],
|
userWorkspaceId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
})
|
});
|
||||||
.then((roles) => roles?.get(userWorkspaceId) ?? []);
|
|
||||||
|
|
||||||
const roleColumn =
|
if (!isDefined(roleIdOfUserWorkspace)) {
|
||||||
this.getRoleColumnForRequiredPermission(requiredPermission);
|
throw new PermissionsException(
|
||||||
|
PermissionsExceptionMessage.NO_ROLE_FOUND_FOR_USER_WORKSPACE,
|
||||||
|
PermissionsExceptionCode.NO_ROLE_FOUND_FOR_USER_WORKSPACE,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return roleOfUserWorkspace?.[roleColumn] === true;
|
const { data: rolesPermissions } =
|
||||||
|
await this.workspacePermissionsCacheService.getRolesPermissionsFromCache({
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const rolePermissionsForUserWorkspaceRole =
|
||||||
|
rolesPermissions[roleIdOfUserWorkspace];
|
||||||
|
|
||||||
|
const objectPermissionKey =
|
||||||
|
this.getObjectPermissionKeyForRequiredPermission(requiredPermission);
|
||||||
|
|
||||||
|
// until permissions V2 is enabled all objects have the same permission values deriving from role, ex role.canReadAllObjectRecords
|
||||||
|
const objectPermissionValue =
|
||||||
|
rolePermissionsForUserWorkspaceRole[
|
||||||
|
Object.keys(rolePermissionsForUserWorkspaceRole)[0]
|
||||||
|
]?.[objectPermissionKey];
|
||||||
|
|
||||||
|
return objectPermissionValue === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getRoleColumnForRequiredPermission(
|
private getObjectPermissionKeyForRequiredPermission(
|
||||||
requiredPermission: PermissionsOnAllObjectRecords,
|
requiredPermission: PermissionsOnAllObjectRecords,
|
||||||
): keyof RoleEntity {
|
) {
|
||||||
switch (requiredPermission) {
|
switch (requiredPermission) {
|
||||||
case PermissionsOnAllObjectRecords.READ_ALL_OBJECT_RECORDS:
|
case PermissionsOnAllObjectRecords.READ_ALL_OBJECT_RECORDS:
|
||||||
return 'canReadAllObjectRecords';
|
return 'canRead';
|
||||||
case PermissionsOnAllObjectRecords.UPDATE_ALL_OBJECT_RECORDS:
|
case PermissionsOnAllObjectRecords.UPDATE_ALL_OBJECT_RECORDS:
|
||||||
return 'canUpdateAllObjectRecords';
|
return 'canUpdate';
|
||||||
case PermissionsOnAllObjectRecords.SOFT_DELETE_ALL_OBJECT_RECORDS:
|
case PermissionsOnAllObjectRecords.SOFT_DELETE_ALL_OBJECT_RECORDS:
|
||||||
return 'canSoftDeleteAllObjectRecords';
|
return 'canSoftDelete';
|
||||||
case PermissionsOnAllObjectRecords.DESTROY_ALL_OBJECT_RECORDS:
|
case PermissionsOnAllObjectRecords.DESTROY_ALL_OBJECT_RECORDS:
|
||||||
return 'canDestroyAllObjectRecords';
|
return 'canDestroy';
|
||||||
default:
|
default:
|
||||||
throw new PermissionsException(
|
throw new PermissionsException(
|
||||||
PermissionsExceptionMessage.UNKNOWN_REQUIRED_PERMISSION,
|
PermissionsExceptionMessage.UNKNOWN_REQUIRED_PERMISSION,
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import { RoleService } from 'src/engine/metadata-modules/role/role.service';
|
|||||||
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
||||||
import { SettingPermissionModule } from 'src/engine/metadata-modules/setting-permission/setting-permission.module';
|
import { SettingPermissionModule } from 'src/engine/metadata-modules/setting-permission/setting-permission.module';
|
||||||
import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module';
|
import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module';
|
||||||
import { WorkspaceRolesPermissionsCacheModule } from 'src/engine/metadata-modules/workspace-roles-permissions-cache/workspace-roles-permissions-cache.module';
|
import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -25,7 +25,7 @@ import { WorkspaceRolesPermissionsCacheModule } from 'src/engine/metadata-module
|
|||||||
FeatureFlagModule,
|
FeatureFlagModule,
|
||||||
ObjectPermissionModule,
|
ObjectPermissionModule,
|
||||||
SettingPermissionModule,
|
SettingPermissionModule,
|
||||||
WorkspaceRolesPermissionsCacheModule,
|
WorkspacePermissionsCacheModule,
|
||||||
],
|
],
|
||||||
providers: [RoleService, RoleResolver],
|
providers: [RoleService, RoleResolver],
|
||||||
exports: [RoleService],
|
exports: [RoleService],
|
||||||
|
|||||||
@ -20,7 +20,7 @@ import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
|||||||
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
||||||
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
||||||
import { isArgDefinedIfProvidedOrThrow } from 'src/engine/metadata-modules/utils/is-arg-defined-if-provided-or-throw.util';
|
import { isArgDefinedIfProvidedOrThrow } from 'src/engine/metadata-modules/utils/is-arg-defined-if-provided-or-throw.util';
|
||||||
import { WorkspaceRolesPermissionsCacheService } from 'src/engine/metadata-modules/workspace-roles-permissions-cache/workspace-roles-permissions-cache.service';
|
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
|
||||||
|
|
||||||
export class RoleService {
|
export class RoleService {
|
||||||
constructor(
|
constructor(
|
||||||
@ -31,7 +31,7 @@ export class RoleService {
|
|||||||
@InjectRepository(UserWorkspaceRoleEntity, 'metadata')
|
@InjectRepository(UserWorkspaceRoleEntity, 'metadata')
|
||||||
private readonly userWorkspaceRoleRepository: Repository<UserWorkspaceRoleEntity>,
|
private readonly userWorkspaceRoleRepository: Repository<UserWorkspaceRoleEntity>,
|
||||||
private readonly userRoleService: UserRoleService,
|
private readonly userRoleService: UserRoleService,
|
||||||
private readonly workspaceRolesPermissionsCacheService: WorkspaceRolesPermissionsCacheService,
|
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async getWorkspaceRoles(workspaceId: string): Promise<RoleEntity[]> {
|
public async getWorkspaceRoles(workspaceId: string): Promise<RoleEntity[]> {
|
||||||
@ -69,7 +69,7 @@ export class RoleService {
|
|||||||
}): Promise<RoleEntity> {
|
}): Promise<RoleEntity> {
|
||||||
await this.validateRoleInput({ input, workspaceId });
|
await this.validateRoleInput({ input, workspaceId });
|
||||||
|
|
||||||
const role = this.roleRepository.save({
|
const role = await this.roleRepository.save({
|
||||||
label: input.label,
|
label: input.label,
|
||||||
description: input.description,
|
description: input.description,
|
||||||
icon: input.icon,
|
icon: input.icon,
|
||||||
@ -82,11 +82,10 @@ export class RoleService {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.workspaceRolesPermissionsCacheService.recomputeRolesPermissionsCache(
|
await this.workspacePermissionsCacheService.recomputeRolesPermissionsCache({
|
||||||
{
|
workspaceId,
|
||||||
workspaceId,
|
roleIds: [role.id],
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
return role;
|
return role;
|
||||||
}
|
}
|
||||||
@ -128,11 +127,10 @@ export class RoleService {
|
|||||||
...input.update,
|
...input.update,
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.workspaceRolesPermissionsCacheService.recomputeRolesPermissionsCache(
|
await this.workspacePermissionsCacheService.recomputeRolesPermissionsCache({
|
||||||
{
|
workspaceId,
|
||||||
workspaceId,
|
roleIds: [input.id],
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
return { ...existingRole, ...updatedRole };
|
return { ...existingRole, ...updatedRole };
|
||||||
}
|
}
|
||||||
@ -196,11 +194,9 @@ export class RoleService {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.workspaceRolesPermissionsCacheService.recomputeRolesPermissionsCache(
|
await this.workspacePermissionsCacheService.recomputeRolesPermissionsCache({
|
||||||
{
|
workspaceId,
|
||||||
workspaceId,
|
});
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return roleId;
|
return roleId;
|
||||||
}
|
}
|
||||||
@ -317,10 +313,11 @@ export class RoleService {
|
|||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
defaultRoleId: string;
|
defaultRoleId: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const userWorkspaceIds = await this.getUserWorkspaceIdsForRole(
|
const userWorkspaceIds =
|
||||||
roleId,
|
await this.userRoleService.getUserWorkspaceIdsAssignedToRole(
|
||||||
workspaceId,
|
roleId,
|
||||||
);
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
userWorkspaceIds.map((userWorkspaceId) =>
|
userWorkspaceIds.map((userWorkspaceId) =>
|
||||||
@ -333,24 +330,6 @@ export class RoleService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getUserWorkspaceIdsForRole(
|
|
||||||
roleId: string,
|
|
||||||
workspaceId: string,
|
|
||||||
): Promise<string[]> {
|
|
||||||
return this.userWorkspaceRoleRepository
|
|
||||||
.find({
|
|
||||||
where: {
|
|
||||||
roleId: roleId,
|
|
||||||
workspaceId,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((userWorkspaceRoles) =>
|
|
||||||
userWorkspaceRoles.map(
|
|
||||||
(userWorkspaceRole) => userWorkspaceRole.userWorkspaceId,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getRole(
|
private async getRole(
|
||||||
roleId: string,
|
roleId: string,
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
|
|||||||
@ -5,11 +5,13 @@ import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-works
|
|||||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||||
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
||||||
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
||||||
|
import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forFeature([RoleEntity, UserWorkspaceRoleEntity], 'metadata'),
|
TypeOrmModule.forFeature([RoleEntity, UserWorkspaceRoleEntity], 'metadata'),
|
||||||
TypeOrmModule.forFeature([UserWorkspace], 'core'),
|
TypeOrmModule.forFeature([UserWorkspace], 'core'),
|
||||||
|
WorkspacePermissionsCacheModule,
|
||||||
],
|
],
|
||||||
providers: [UserRoleService],
|
providers: [UserRoleService],
|
||||||
exports: [UserRoleService],
|
exports: [UserRoleService],
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import {
|
|||||||
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||||
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
||||||
|
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||||
|
|
||||||
@ -24,6 +25,7 @@ export class UserRoleService {
|
|||||||
@InjectRepository(UserWorkspace, 'core')
|
@InjectRepository(UserWorkspace, 'core')
|
||||||
private readonly userWorkspaceRepository: Repository<UserWorkspace>,
|
private readonly userWorkspaceRepository: Repository<UserWorkspace>,
|
||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
|
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async assignRoleToUserWorkspace({
|
public async assignRoleToUserWorkspace({
|
||||||
@ -56,6 +58,12 @@ export class UserRoleService {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
id: Not(newUserWorkspaceRole.id),
|
id: Not(newUserWorkspaceRole.id),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await this.workspacePermissionsCacheService.recomputeUserWorkspaceRoleMapCache(
|
||||||
|
{
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getRoleIdForUserWorkspace({
|
public async getRoleIdForUserWorkspace({
|
||||||
@ -69,11 +77,14 @@ export class UserRoleService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const userWorkspaceRole = await this.userWorkspaceRoleRepository.findOne({
|
const userWorkspaceRoleMap =
|
||||||
where: { userWorkspaceId, workspaceId },
|
await this.workspacePermissionsCacheService.getUserWorkspaceRoleMapFromCache(
|
||||||
});
|
{
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
return userWorkspaceRole?.roleId;
|
return userWorkspaceRoleMap.data[userWorkspaceId];
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getRolesByUserWorkspaces({
|
public async getRolesByUserWorkspaces({
|
||||||
@ -125,21 +136,13 @@ export class UserRoleService {
|
|||||||
roleId: string,
|
roleId: string,
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
): Promise<WorkspaceMemberWorkspaceEntity[]> {
|
): Promise<WorkspaceMemberWorkspaceEntity[]> {
|
||||||
const userWorkspaceRoles = await this.userWorkspaceRoleRepository.find({
|
const userWorkspaceIdsWithRole =
|
||||||
where: {
|
await this.getUserWorkspaceIdsAssignedToRole(roleId, workspaceId);
|
||||||
roleId,
|
|
||||||
workspaceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const userIds = await this.userWorkspaceRepository
|
const userIds = await this.userWorkspaceRepository
|
||||||
.find({
|
.find({
|
||||||
where: {
|
where: {
|
||||||
id: In(
|
id: In(userWorkspaceIdsWithRole),
|
||||||
userWorkspaceRoles.map(
|
|
||||||
(userWorkspaceRole) => userWorkspaceRole.userWorkspaceId,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((userWorkspaces) =>
|
.then((userWorkspaces) =>
|
||||||
@ -161,6 +164,22 @@ export class UserRoleService {
|
|||||||
return workspaceMembers;
|
return workspaceMembers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getUserWorkspaceIdsAssignedToRole(
|
||||||
|
roleId: string,
|
||||||
|
workspaceId: string,
|
||||||
|
): Promise<string[]> {
|
||||||
|
const userWorkspaceRoleMap =
|
||||||
|
await this.workspacePermissionsCacheService.getUserWorkspaceRoleMapFromCache(
|
||||||
|
{
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return Object.entries(userWorkspaceRoleMap.data)
|
||||||
|
.filter(([_, roleIdFromMap]) => roleIdFromMap === roleId)
|
||||||
|
.map(([userWorkspaceId]) => userWorkspaceId);
|
||||||
|
}
|
||||||
|
|
||||||
public async validateUserWorkspaceIsNotUniqueAdminOrThrow({
|
public async validateUserWorkspaceIsNotUniqueAdminOrThrow({
|
||||||
userWorkspaceId,
|
userWorkspaceId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
|||||||
@ -10,6 +10,8 @@ import { TwentyORMExceptionCode } from 'src/engine/twenty-orm/exceptions/twenty-
|
|||||||
import { getFromCacheWithRecompute } from 'src/engine/utils/get-data-from-cache-with-recompute.util';
|
import { getFromCacheWithRecompute } from 'src/engine/utils/get-data-from-cache-with-recompute.util';
|
||||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||||
|
|
||||||
|
const FEATURE_FLAG_MAP = 'Feature flag map';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WorkspaceFeatureFlagsMapCacheService {
|
export class WorkspaceFeatureFlagsMapCacheService {
|
||||||
logger = new Logger(WorkspaceFeatureFlagsMapCacheService.name);
|
logger = new Logger(WorkspaceFeatureFlagsMapCacheService.name);
|
||||||
@ -45,7 +47,7 @@ export class WorkspaceFeatureFlagsMapCacheService {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
),
|
),
|
||||||
recomputeCache: (params) => this.recomputeFeatureFlagsMapCache(params),
|
recomputeCache: (params) => this.recomputeFeatureFlagsMapCache(params),
|
||||||
cachedEntityName: 'Feature flag map',
|
cachedEntityName: FEATURE_FLAG_MAP,
|
||||||
exceptionCode: TwentyORMExceptionCode.FEATURE_FLAG_MAP_VERSION_NOT_FOUND,
|
exceptionCode: TwentyORMExceptionCode.FEATURE_FLAG_MAP_VERSION_NOT_FOUND,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
export type UserWorkspaceRoleMap = Record<string, string>;
|
||||||
@ -0,0 +1,156 @@
|
|||||||
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { ObjectRecordsPermissionsByRoleId } from 'twenty-shared/types';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator';
|
||||||
|
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
|
||||||
|
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
|
||||||
|
import { UserWorkspaceRoleMap } from 'src/engine/metadata-modules/workspace-permissions-cache/types/user-workspace-role-map.type';
|
||||||
|
import { WorkspaceCacheKeys } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||||
|
|
||||||
|
const TTL_INFINITE = 0;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class WorkspacePermissionsCacheStorageService {
|
||||||
|
logger = new Logger(WorkspacePermissionsCacheStorageService.name);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@InjectCacheStorage(CacheStorageNamespace.EngineWorkspace)
|
||||||
|
private readonly cacheStorageService: CacheStorageService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async setRolesPermissionsVersion(workspaceId: string): Promise<string> {
|
||||||
|
const rolesPermissionsVersion = v4();
|
||||||
|
|
||||||
|
await this.cacheStorageService.set<string>(
|
||||||
|
`${WorkspaceCacheKeys.MetadataPermissionsRolesPermissionsVersion}:${workspaceId}`,
|
||||||
|
rolesPermissionsVersion,
|
||||||
|
TTL_INFINITE,
|
||||||
|
);
|
||||||
|
|
||||||
|
return rolesPermissionsVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setRolesPermissions(
|
||||||
|
workspaceId: string,
|
||||||
|
permissions: ObjectRecordsPermissionsByRoleId,
|
||||||
|
): Promise<{
|
||||||
|
newRolesPermissionsVersion: string;
|
||||||
|
}> {
|
||||||
|
const [, newRolesPermissionsVersion] = await Promise.all([
|
||||||
|
this.cacheStorageService.set<ObjectRecordsPermissionsByRoleId>(
|
||||||
|
`${WorkspaceCacheKeys.MetadataPermissionsRolesPermissions}:${workspaceId}`,
|
||||||
|
permissions,
|
||||||
|
TTL_INFINITE,
|
||||||
|
),
|
||||||
|
this.setRolesPermissionsVersion(workspaceId),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return { newRolesPermissionsVersion };
|
||||||
|
}
|
||||||
|
|
||||||
|
getRolesPermissions(
|
||||||
|
workspaceId: string,
|
||||||
|
): Promise<ObjectRecordsPermissionsByRoleId | undefined> {
|
||||||
|
return this.cacheStorageService.get<ObjectRecordsPermissionsByRoleId>(
|
||||||
|
`${WorkspaceCacheKeys.MetadataPermissionsRolesPermissions}:${workspaceId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getRolesPermissionsVersion(workspaceId: string): Promise<string | undefined> {
|
||||||
|
return this.cacheStorageService.get<string>(
|
||||||
|
`${WorkspaceCacheKeys.MetadataPermissionsRolesPermissionsVersion}:${workspaceId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
addRolesPermissionsOngoingCachingLock(workspaceId: string) {
|
||||||
|
return this.cacheStorageService.set<boolean>(
|
||||||
|
`${WorkspaceCacheKeys.MetadataPermissionsRolesPermissionsOngoingCachingLock}:${workspaceId}`,
|
||||||
|
true,
|
||||||
|
1_000 * 60, // 1 minute
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeRolesPermissionsOngoingCachingLock(workspaceId: string) {
|
||||||
|
return this.cacheStorageService.del(
|
||||||
|
`${WorkspaceCacheKeys.MetadataPermissionsRolesPermissionsOngoingCachingLock}:${workspaceId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getRolesPermissionsOngoingCachingLock(
|
||||||
|
workspaceId: string,
|
||||||
|
): Promise<boolean | undefined> {
|
||||||
|
return this.cacheStorageService.get<boolean>(
|
||||||
|
`${WorkspaceCacheKeys.MetadataPermissionsRolesPermissionsOngoingCachingLock}:${workspaceId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async setUserWorkspaceRoleMap(
|
||||||
|
workspaceId: string,
|
||||||
|
userWorkspaceRoleMap: UserWorkspaceRoleMap,
|
||||||
|
): Promise<{
|
||||||
|
newUserWorkspaceRoleMapVersion: string;
|
||||||
|
}> {
|
||||||
|
const [, newUserWorkspaceRoleMapVersion] = await Promise.all([
|
||||||
|
this.cacheStorageService.set<UserWorkspaceRoleMap>(
|
||||||
|
`${WorkspaceCacheKeys.MetadataPermissionsUserWorkspaceRoleMap}:${workspaceId}`,
|
||||||
|
userWorkspaceRoleMap,
|
||||||
|
TTL_INFINITE,
|
||||||
|
),
|
||||||
|
this.setUserWorkspaceRoleMapVersion(workspaceId),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return { newUserWorkspaceRoleMapVersion };
|
||||||
|
}
|
||||||
|
|
||||||
|
async setUserWorkspaceRoleMapVersion(workspaceId: string) {
|
||||||
|
const userWorkspaceRoleMapVersion = v4();
|
||||||
|
|
||||||
|
await this.cacheStorageService.set<string>(
|
||||||
|
`${WorkspaceCacheKeys.MetadataPermissionsUserWorkspaceRoleMapVersion}:${workspaceId}`,
|
||||||
|
userWorkspaceRoleMapVersion,
|
||||||
|
TTL_INFINITE,
|
||||||
|
);
|
||||||
|
|
||||||
|
return userWorkspaceRoleMapVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
getUserWorkspaceRoleMap(
|
||||||
|
workspaceId: string,
|
||||||
|
): Promise<Record<string, string> | undefined> {
|
||||||
|
return this.cacheStorageService.get<Record<string, string>>(
|
||||||
|
`${WorkspaceCacheKeys.MetadataPermissionsUserWorkspaceRoleMap}:${workspaceId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getUserWorkspaceRoleMapVersion(
|
||||||
|
workspaceId: string,
|
||||||
|
): Promise<string | undefined> {
|
||||||
|
return this.cacheStorageService.get<string>(
|
||||||
|
`${WorkspaceCacheKeys.MetadataPermissionsUserWorkspaceRoleMapVersion}:${workspaceId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
addUserWorkspaceRoleMapOngoingCachingLock(workspaceId: string) {
|
||||||
|
return this.cacheStorageService.set<boolean>(
|
||||||
|
`${WorkspaceCacheKeys.MetadataPermissionsUserWorkspaceRoleMapOngoingCachingLock}:${workspaceId}`,
|
||||||
|
true,
|
||||||
|
1_000 * 60, // 1 minute
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeUserWorkspaceRoleMapOngoingCachingLock(workspaceId: string) {
|
||||||
|
return this.cacheStorageService.del(
|
||||||
|
`${WorkspaceCacheKeys.MetadataPermissionsUserWorkspaceRoleMapOngoingCachingLock}:${workspaceId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getUserWorkspaceRoleMapOngoingCachingLock(
|
||||||
|
workspaceId: string,
|
||||||
|
): Promise<boolean | undefined> {
|
||||||
|
return this.cacheStorageService.get<boolean>(
|
||||||
|
`${WorkspaceCacheKeys.MetadataPermissionsUserWorkspaceRoleMapOngoingCachingLock}:${workspaceId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
|
||||||
|
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||||
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
|
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||||
|
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
||||||
|
import { WorkspacePermissionsCacheStorageService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache-storage.service';
|
||||||
|
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
||||||
|
|
||||||
|
import { WorkspacePermissionsCacheService } from './workspace-permissions-cache.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
TypeOrmModule.forFeature([Workspace], 'core'),
|
||||||
|
TypeOrmModule.forFeature(
|
||||||
|
[ObjectMetadataEntity, RoleEntity, UserWorkspaceRoleEntity],
|
||||||
|
'metadata',
|
||||||
|
),
|
||||||
|
WorkspaceCacheStorageModule,
|
||||||
|
FeatureFlagModule,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
WorkspacePermissionsCacheService,
|
||||||
|
WorkspacePermissionsCacheStorageService,
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
WorkspacePermissionsCacheService,
|
||||||
|
WorkspacePermissionsCacheStorageService,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class WorkspacePermissionsCacheModule {}
|
||||||
@ -0,0 +1,318 @@
|
|||||||
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ObjectRecordsPermissions,
|
||||||
|
ObjectRecordsPermissionsByRoleId,
|
||||||
|
} from 'twenty-shared/types';
|
||||||
|
import { In, Repository } from 'typeorm';
|
||||||
|
|
||||||
|
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||||
|
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||||
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
|
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||||
|
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
||||||
|
import { UserWorkspaceRoleMap } from 'src/engine/metadata-modules/workspace-permissions-cache/types/user-workspace-role-map.type';
|
||||||
|
import { WorkspacePermissionsCacheStorageService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache-storage.service';
|
||||||
|
import { TwentyORMExceptionCode } from 'src/engine/twenty-orm/exceptions/twenty-orm.exception';
|
||||||
|
import { getFromCacheWithRecompute } from 'src/engine/utils/get-data-from-cache-with-recompute.util';
|
||||||
|
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||||
|
|
||||||
|
type CacheResult<T, U> = {
|
||||||
|
version: T;
|
||||||
|
data: U;
|
||||||
|
};
|
||||||
|
|
||||||
|
const USER_WORKSPACE_ROLE_MAP = 'User workspace role map';
|
||||||
|
const ROLES_PERMISSIONS = 'Roles permissions';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class WorkspacePermissionsCacheService {
|
||||||
|
logger = new Logger(WorkspacePermissionsCacheService.name);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||||
|
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||||
|
@InjectRepository(RoleEntity, 'metadata')
|
||||||
|
private readonly roleRepository: Repository<RoleEntity>,
|
||||||
|
@InjectRepository(UserWorkspaceRoleEntity, 'metadata')
|
||||||
|
private readonly userWorkspaceRoleRepository: Repository<UserWorkspaceRoleEntity>,
|
||||||
|
private readonly workspacePermissionsCacheStorageService: WorkspacePermissionsCacheStorageService,
|
||||||
|
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
||||||
|
private readonly featureFlagService: FeatureFlagService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async recomputeRolesPermissionsCache({
|
||||||
|
workspaceId,
|
||||||
|
ignoreLock = false,
|
||||||
|
roleIds,
|
||||||
|
}: {
|
||||||
|
workspaceId: string;
|
||||||
|
ignoreLock?: boolean;
|
||||||
|
roleIds?: string[];
|
||||||
|
}): Promise<void> {
|
||||||
|
const isPermissionsV2Enabled =
|
||||||
|
await this.featureFlagService.isFeatureEnabled(
|
||||||
|
FeatureFlagKey.IsPermissionsV2Enabled,
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isAlreadyCaching =
|
||||||
|
await this.workspacePermissionsCacheStorageService.getRolesPermissionsOngoingCachingLock(
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!ignoreLock && isAlreadyCaching) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.workspacePermissionsCacheStorageService.addRolesPermissionsOngoingCachingLock(
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
let currentRolesPermissions: ObjectRecordsPermissionsByRoleId | undefined =
|
||||||
|
undefined;
|
||||||
|
|
||||||
|
if (roleIds) {
|
||||||
|
currentRolesPermissions =
|
||||||
|
await this.workspacePermissionsCacheStorageService.getRolesPermissions(
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const recomputedRolesPermissions =
|
||||||
|
await this.getObjectRecordPermissionsForRoles({
|
||||||
|
workspaceId,
|
||||||
|
isPermissionsV2Enabled,
|
||||||
|
roleIds,
|
||||||
|
});
|
||||||
|
|
||||||
|
const freshObjectRecordsPermissionsByRoleId = roleIds
|
||||||
|
? { ...currentRolesPermissions, ...recomputedRolesPermissions }
|
||||||
|
: recomputedRolesPermissions;
|
||||||
|
|
||||||
|
await this.workspacePermissionsCacheStorageService.setRolesPermissions(
|
||||||
|
workspaceId,
|
||||||
|
freshObjectRecordsPermissionsByRoleId,
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.workspacePermissionsCacheStorageService.removeRolesPermissionsOngoingCachingLock(
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async recomputeUserWorkspaceRoleMapCache({
|
||||||
|
workspaceId,
|
||||||
|
ignoreLock = false,
|
||||||
|
}: {
|
||||||
|
workspaceId: string;
|
||||||
|
ignoreLock?: boolean;
|
||||||
|
}): Promise<void> {
|
||||||
|
const isAlreadyCaching =
|
||||||
|
await this.workspacePermissionsCacheStorageService.getUserWorkspaceRoleMapOngoingCachingLock(
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!ignoreLock && isAlreadyCaching) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.workspacePermissionsCacheStorageService.addUserWorkspaceRoleMapOngoingCachingLock(
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const freshUserWorkspaceRoleMap =
|
||||||
|
await this.getUserWorkspaceRoleMapFromDatabase({
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.workspacePermissionsCacheStorageService.setUserWorkspaceRoleMap(
|
||||||
|
workspaceId,
|
||||||
|
freshUserWorkspaceRoleMap,
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.workspacePermissionsCacheStorageService.removeUserWorkspaceRoleMapOngoingCachingLock(
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRolesPermissionsFromCache({
|
||||||
|
workspaceId,
|
||||||
|
}: {
|
||||||
|
workspaceId: string;
|
||||||
|
}): Promise<CacheResult<string, ObjectRecordsPermissionsByRoleId>> {
|
||||||
|
return getFromCacheWithRecompute<string, ObjectRecordsPermissionsByRoleId>({
|
||||||
|
workspaceId,
|
||||||
|
getCacheData: () =>
|
||||||
|
this.workspacePermissionsCacheStorageService.getRolesPermissions(
|
||||||
|
workspaceId,
|
||||||
|
),
|
||||||
|
getCacheVersion: () =>
|
||||||
|
this.workspacePermissionsCacheStorageService.getRolesPermissionsVersion(
|
||||||
|
workspaceId,
|
||||||
|
),
|
||||||
|
recomputeCache: (params) => this.recomputeRolesPermissionsCache(params),
|
||||||
|
cachedEntityName: ROLES_PERMISSIONS,
|
||||||
|
exceptionCode: TwentyORMExceptionCode.ROLES_PERMISSIONS_VERSION_NOT_FOUND,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUserWorkspaceRoleMapFromCache({
|
||||||
|
workspaceId,
|
||||||
|
}: {
|
||||||
|
workspaceId: string;
|
||||||
|
}): Promise<CacheResult<string, UserWorkspaceRoleMap>> {
|
||||||
|
return getFromCacheWithRecompute<string, UserWorkspaceRoleMap>({
|
||||||
|
workspaceId,
|
||||||
|
getCacheData: () =>
|
||||||
|
this.workspacePermissionsCacheStorageService.getUserWorkspaceRoleMap(
|
||||||
|
workspaceId,
|
||||||
|
),
|
||||||
|
getCacheVersion: () =>
|
||||||
|
this.workspacePermissionsCacheStorageService.getUserWorkspaceRoleMapVersion(
|
||||||
|
workspaceId,
|
||||||
|
),
|
||||||
|
recomputeCache: (params) =>
|
||||||
|
this.recomputeUserWorkspaceRoleMapCache(params),
|
||||||
|
cachedEntityName: USER_WORKSPACE_ROLE_MAP,
|
||||||
|
exceptionCode:
|
||||||
|
TwentyORMExceptionCode.USER_WORKSPACE_ROLE_MAP_VERSION_NOT_FOUND,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getObjectRecordPermissionsForRoles({
|
||||||
|
workspaceId,
|
||||||
|
isPermissionsV2Enabled,
|
||||||
|
roleIds,
|
||||||
|
}: {
|
||||||
|
workspaceId: string;
|
||||||
|
isPermissionsV2Enabled: boolean;
|
||||||
|
roleIds?: string[];
|
||||||
|
}): Promise<ObjectRecordsPermissionsByRoleId> {
|
||||||
|
let roles: RoleEntity[] = [];
|
||||||
|
|
||||||
|
roles = await this.roleRepository.find({
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
...(roleIds ? { id: In(roleIds) } : {}),
|
||||||
|
},
|
||||||
|
relations: ['objectPermissions'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const workspaceObjectMetadataNameIdMap =
|
||||||
|
await this.getWorkspaceObjectMetadataNameIdMap(workspaceId);
|
||||||
|
|
||||||
|
const permissionsByRoleId: ObjectRecordsPermissionsByRoleId = {};
|
||||||
|
|
||||||
|
for (const role of roles) {
|
||||||
|
const objectRecordsPermissions: ObjectRecordsPermissions = {};
|
||||||
|
|
||||||
|
for (const objectMetadataNameSingular of Object.keys(
|
||||||
|
workspaceObjectMetadataNameIdMap,
|
||||||
|
)) {
|
||||||
|
let canRead = role.canReadAllObjectRecords;
|
||||||
|
let canUpdate = role.canUpdateAllObjectRecords;
|
||||||
|
let canSoftDelete = role.canSoftDeleteAllObjectRecords;
|
||||||
|
let canDestroy = role.canDestroyAllObjectRecords;
|
||||||
|
|
||||||
|
if (isPermissionsV2Enabled) {
|
||||||
|
const objectRecordPermissionsOverride = role.objectPermissions.find(
|
||||||
|
(objectPermission) =>
|
||||||
|
objectPermission.objectMetadataId ===
|
||||||
|
workspaceObjectMetadataNameIdMap[objectMetadataNameSingular],
|
||||||
|
);
|
||||||
|
|
||||||
|
canRead =
|
||||||
|
objectRecordPermissionsOverride?.canReadObjectRecords ?? canRead;
|
||||||
|
canUpdate =
|
||||||
|
objectRecordPermissionsOverride?.canUpdateObjectRecords ??
|
||||||
|
canUpdate;
|
||||||
|
canSoftDelete =
|
||||||
|
objectRecordPermissionsOverride?.canSoftDeleteObjectRecords ??
|
||||||
|
canSoftDelete;
|
||||||
|
canDestroy =
|
||||||
|
objectRecordPermissionsOverride?.canDestroyObjectRecords ??
|
||||||
|
canDestroy;
|
||||||
|
}
|
||||||
|
|
||||||
|
objectRecordsPermissions[objectMetadataNameSingular] = {
|
||||||
|
canRead,
|
||||||
|
canUpdate,
|
||||||
|
canSoftDelete,
|
||||||
|
canDestroy,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
permissionsByRoleId[role.id] = objectRecordsPermissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
return permissionsByRoleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getWorkspaceObjectMetadataNameIdMap(
|
||||||
|
workspaceId: string,
|
||||||
|
): Promise<Record<string, string>> {
|
||||||
|
let workspaceObjectMetadataMap: Record<string, string> = {};
|
||||||
|
const metadataVersion =
|
||||||
|
await this.workspaceCacheStorageService.getMetadataVersion(workspaceId);
|
||||||
|
|
||||||
|
if (metadataVersion) {
|
||||||
|
const objectMetadataMaps =
|
||||||
|
await this.workspaceCacheStorageService.getObjectMetadataMaps(
|
||||||
|
workspaceId,
|
||||||
|
metadataVersion,
|
||||||
|
);
|
||||||
|
|
||||||
|
workspaceObjectMetadataMap = Object.values(
|
||||||
|
objectMetadataMaps?.byId ?? {},
|
||||||
|
).reduce(
|
||||||
|
(acc, objectMetadata) => {
|
||||||
|
acc[objectMetadata.nameSingular] = objectMetadata.id;
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<string, string>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!metadataVersion ||
|
||||||
|
Object.keys(workspaceObjectMetadataMap).length === 0
|
||||||
|
) {
|
||||||
|
const workspaceObjectMetadata = await this.objectMetadataRepository.find({
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
workspaceObjectMetadataMap = workspaceObjectMetadata.reduce(
|
||||||
|
(acc, objectMetadata) => {
|
||||||
|
acc[objectMetadata.nameSingular] = objectMetadata.id;
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<string, string>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return workspaceObjectMetadataMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getUserWorkspaceRoleMapFromDatabase({
|
||||||
|
workspaceId,
|
||||||
|
}: {
|
||||||
|
workspaceId: string;
|
||||||
|
}): Promise<UserWorkspaceRoleMap> {
|
||||||
|
const userWorkspaceRoleMap = await this.userWorkspaceRoleRepository.find({
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return userWorkspaceRoleMap.reduce((acc, userWorkspaceRole) => {
|
||||||
|
acc[userWorkspaceRole.userWorkspaceId] = userWorkspaceRole.roleId;
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {} as UserWorkspaceRoleMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,20 +0,0 @@
|
|||||||
import { Module } from '@nestjs/common';
|
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
|
||||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
|
||||||
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
|
||||||
|
|
||||||
import { WorkspaceRolesPermissionsCacheService } from './workspace-roles-permissions-cache.service';
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
imports: [
|
|
||||||
TypeOrmModule.forFeature([Workspace], 'core'),
|
|
||||||
TypeOrmModule.forFeature([ObjectMetadataEntity, RoleEntity], 'metadata'),
|
|
||||||
WorkspaceCacheStorageModule,
|
|
||||||
],
|
|
||||||
providers: [WorkspaceRolesPermissionsCacheService],
|
|
||||||
exports: [WorkspaceRolesPermissionsCacheService],
|
|
||||||
})
|
|
||||||
export class WorkspaceRolesPermissionsCacheModule {}
|
|
||||||
@ -1,128 +0,0 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ObjectRecordsPermissions,
|
|
||||||
ObjectRecordsPermissionsByRoleId,
|
|
||||||
} from 'twenty-shared/types';
|
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
|
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
|
||||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
|
||||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class WorkspaceRolesPermissionsCacheService {
|
|
||||||
logger = new Logger(WorkspaceRolesPermissionsCacheService.name);
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
|
||||||
@InjectRepository(RoleEntity, 'metadata')
|
|
||||||
private readonly roleRepository: Repository<RoleEntity>,
|
|
||||||
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async recomputeRolesPermissionsCache({
|
|
||||||
workspaceId,
|
|
||||||
ignoreLock = false,
|
|
||||||
}: {
|
|
||||||
workspaceId: string;
|
|
||||||
ignoreLock?: boolean;
|
|
||||||
}): Promise<void> {
|
|
||||||
const isAlreadyCaching =
|
|
||||||
await this.workspaceCacheStorageService.getRolesPermissionsOngoingCachingLock(
|
|
||||||
workspaceId,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!ignoreLock && isAlreadyCaching) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.workspaceCacheStorageService.addRolesPermissionsOngoingCachingLock(
|
|
||||||
workspaceId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const freshObjectRecordsPermissionsByRoleId =
|
|
||||||
await this.getObjectRecordPermissionsForRoles({
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.workspaceCacheStorageService.setRolesPermissions(
|
|
||||||
workspaceId,
|
|
||||||
freshObjectRecordsPermissionsByRoleId,
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.workspaceCacheStorageService.removeRolesPermissionsOngoingCachingLock(
|
|
||||||
workspaceId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getObjectRecordPermissionsForRoles({
|
|
||||||
workspaceId,
|
|
||||||
}: {
|
|
||||||
workspaceId: string;
|
|
||||||
}): Promise<ObjectRecordsPermissionsByRoleId> {
|
|
||||||
const roles = await this.roleRepository.find({
|
|
||||||
where: {
|
|
||||||
workspaceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const workspaceObjectMetadataNames =
|
|
||||||
await this.getWorkspaceObjectMetadataNames(workspaceId);
|
|
||||||
|
|
||||||
const permissionsByRoleId: ObjectRecordsPermissionsByRoleId = {};
|
|
||||||
|
|
||||||
for (const role of roles) {
|
|
||||||
const objectRecordsPermissions: ObjectRecordsPermissions = {};
|
|
||||||
|
|
||||||
for (const objectMetadataNameSingular of workspaceObjectMetadataNames) {
|
|
||||||
objectRecordsPermissions[objectMetadataNameSingular] = {
|
|
||||||
canRead: role.canReadAllObjectRecords,
|
|
||||||
canUpdate: role.canUpdateAllObjectRecords,
|
|
||||||
canSoftDelete: role.canSoftDeleteAllObjectRecords,
|
|
||||||
canDestroy: role.canDestroyAllObjectRecords,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
permissionsByRoleId[role.id] = objectRecordsPermissions;
|
|
||||||
}
|
|
||||||
|
|
||||||
return permissionsByRoleId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getWorkspaceObjectMetadataNames(
|
|
||||||
workspaceId: string,
|
|
||||||
): Promise<string[]> {
|
|
||||||
let workspaceObjectMetadataNames: string[] = [];
|
|
||||||
const metadataVersion =
|
|
||||||
await this.workspaceCacheStorageService.getMetadataVersion(workspaceId);
|
|
||||||
|
|
||||||
if (metadataVersion) {
|
|
||||||
const objectMetadataMaps =
|
|
||||||
await this.workspaceCacheStorageService.getObjectMetadataMaps(
|
|
||||||
workspaceId,
|
|
||||||
metadataVersion,
|
|
||||||
);
|
|
||||||
|
|
||||||
workspaceObjectMetadataNames = Object.values(
|
|
||||||
objectMetadataMaps?.byId ?? {},
|
|
||||||
).map((objectMetadata) => objectMetadata.nameSingular);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!metadataVersion || workspaceObjectMetadataNames.length === 0) {
|
|
||||||
const workspaceObjectMetadata = await this.objectMetadataRepository.find({
|
|
||||||
where: {
|
|
||||||
workspaceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
workspaceObjectMetadataNames = workspaceObjectMetadata.map(
|
|
||||||
(objectMetadata) => objectMetadata.nameSingular,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return workspaceObjectMetadataNames;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -18,16 +18,16 @@ export class WorkspaceDataSource extends DataSource {
|
|||||||
readonly manager: WorkspaceEntityManager;
|
readonly manager: WorkspaceEntityManager;
|
||||||
featureFlagMapVersion: string;
|
featureFlagMapVersion: string;
|
||||||
featureFlagMap: FeatureFlagMap;
|
featureFlagMap: FeatureFlagMap;
|
||||||
rolesPermissionsVersion?: string;
|
rolesPermissionsVersion: string;
|
||||||
permissionsPerRoleId?: ObjectRecordsPermissionsByRoleId;
|
permissionsPerRoleId: ObjectRecordsPermissionsByRoleId;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
internalContext: WorkspaceInternalContext,
|
internalContext: WorkspaceInternalContext,
|
||||||
options: DataSourceOptions,
|
options: DataSourceOptions,
|
||||||
featureFlagMapVersion: string,
|
featureFlagMapVersion: string,
|
||||||
featureFlagMap: FeatureFlagMap,
|
featureFlagMap: FeatureFlagMap,
|
||||||
rolesPermissionsVersion?: string,
|
rolesPermissionsVersion: string,
|
||||||
permissionsPerRoleId?: ObjectRecordsPermissionsByRoleId,
|
permissionsPerRoleId: ObjectRecordsPermissionsByRoleId,
|
||||||
) {
|
) {
|
||||||
super(options);
|
super(options);
|
||||||
this.internalContext = internalContext;
|
this.internalContext = internalContext;
|
||||||
|
|||||||
@ -13,4 +13,5 @@ export enum TwentyORMExceptionCode {
|
|||||||
WORKSPACE_SCHEMA_NOT_FOUND = 'WORKSPACE_SCHEMA_NOT_FOUND',
|
WORKSPACE_SCHEMA_NOT_FOUND = 'WORKSPACE_SCHEMA_NOT_FOUND',
|
||||||
ROLES_PERMISSIONS_VERSION_NOT_FOUND = 'ROLES_PERMISSIONS_VERSION_NOT_FOUND',
|
ROLES_PERMISSIONS_VERSION_NOT_FOUND = 'ROLES_PERMISSIONS_VERSION_NOT_FOUND',
|
||||||
FEATURE_FLAG_MAP_VERSION_NOT_FOUND = 'FEATURE_FLAG_MAP_VERSION_NOT_FOUND',
|
FEATURE_FLAG_MAP_VERSION_NOT_FOUND = 'FEATURE_FLAG_MAP_VERSION_NOT_FOUND',
|
||||||
|
USER_WORKSPACE_ROLE_MAP_VERSION_NOT_FOUND = 'USER_WORKSPACE_ROLE_MAP_VERSION_NOT_FOUND',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,12 +7,12 @@ import { EntitySchema } from 'typeorm';
|
|||||||
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||||
import { NodeEnvironment } from 'src/engine/core-modules/twenty-config/interfaces/node-environment.interface';
|
import { NodeEnvironment } from 'src/engine/core-modules/twenty-config/interfaces/node-environment.interface';
|
||||||
|
|
||||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
|
||||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||||
import { WorkspaceFeatureFlagsMapCacheService } from 'src/engine/metadata-modules/workspace-feature-flags-map-cache/workspace-feature-flags-map-cache.service';
|
import { WorkspaceFeatureFlagsMapCacheService } from 'src/engine/metadata-modules/workspace-feature-flags-map-cache/workspace-feature-flags-map-cache.service';
|
||||||
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
|
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
|
||||||
import { WorkspaceRolesPermissionsCacheService } from 'src/engine/metadata-modules/workspace-roles-permissions-cache/workspace-roles-permissions-cache.service';
|
import { WorkspacePermissionsCacheStorageService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache-storage.service';
|
||||||
|
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
|
||||||
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
|
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
|
||||||
import {
|
import {
|
||||||
TwentyORMException,
|
TwentyORMException,
|
||||||
@ -40,7 +40,8 @@ export class WorkspaceDatasourceFactory {
|
|||||||
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
||||||
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
|
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
|
||||||
private readonly entitySchemaFactory: EntitySchemaFactory,
|
private readonly entitySchemaFactory: EntitySchemaFactory,
|
||||||
private readonly workspaceRolesPermissionsCacheService: WorkspaceRolesPermissionsCacheService,
|
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
|
||||||
|
private readonly workspacePermissionsCacheStorageService: WorkspacePermissionsCacheStorageService,
|
||||||
private readonly workspaceFeatureFlagsMapCacheService: WorkspaceFeatureFlagsMapCacheService,
|
private readonly workspaceFeatureFlagsMapCacheService: WorkspaceFeatureFlagsMapCacheService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -60,15 +61,11 @@ export class WorkspaceDatasourceFactory {
|
|||||||
{ workspaceId },
|
{ workspaceId },
|
||||||
);
|
);
|
||||||
|
|
||||||
const isPermissionsV2Enabled =
|
|
||||||
cachedFeatureFlagMap[FeatureFlagKey.IsPermissionsV2Enabled];
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: cachedRolesPermissions,
|
data: cachedRolesPermissions,
|
||||||
version: cachedRolesPermissionsVersion,
|
version: cachedRolesPermissionsVersion,
|
||||||
} = await this.getRolesPermissionsFromCache({
|
} = await this.getRolesPermissionsFromCache({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
isPermissionsV2Enabled,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -196,13 +193,11 @@ export class WorkspaceDatasourceFactory {
|
|||||||
throw new Error(`Failed to create WorkspaceDataSource for ${cacheKey}`);
|
throw new Error(`Failed to create WorkspaceDataSource for ${cacheKey}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPermissionsV2Enabled) {
|
await this.updateWorkspaceDataSourceRolesPermissionsIfNeeded({
|
||||||
await this.updateWorkspaceDataSourceRolesPermissionsIfNeeded({
|
workspaceDataSource,
|
||||||
workspaceDataSource,
|
cachedRolesPermissionsVersion,
|
||||||
cachedRolesPermissionsVersion,
|
cachedRolesPermissions,
|
||||||
cachedRolesPermissions,
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.updateWorkspaceDataSourceFeatureFlagsMapIfNeeded({
|
await this.updateWorkspaceDataSourceFeatureFlagsMapIfNeeded({
|
||||||
workspaceDataSource,
|
workspaceDataSource,
|
||||||
@ -215,33 +210,21 @@ export class WorkspaceDatasourceFactory {
|
|||||||
|
|
||||||
private async getRolesPermissionsFromCache({
|
private async getRolesPermissionsFromCache({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
isPermissionsV2Enabled,
|
|
||||||
}: {
|
}: {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
isPermissionsV2Enabled?: boolean;
|
}): Promise<CacheResult<string, ObjectRecordsPermissionsByRoleId>> {
|
||||||
}): Promise<
|
return getFromCacheWithRecompute<string, ObjectRecordsPermissionsByRoleId>({
|
||||||
CacheResult<
|
|
||||||
string | undefined,
|
|
||||||
ObjectRecordsPermissionsByRoleId | undefined
|
|
||||||
>
|
|
||||||
> {
|
|
||||||
if (!isPermissionsV2Enabled) {
|
|
||||||
return { version: undefined, data: undefined };
|
|
||||||
}
|
|
||||||
|
|
||||||
return getFromCacheWithRecompute<
|
|
||||||
string | undefined,
|
|
||||||
ObjectRecordsPermissionsByRoleId | undefined
|
|
||||||
>({
|
|
||||||
workspaceId,
|
workspaceId,
|
||||||
getCacheData: () =>
|
getCacheData: () =>
|
||||||
this.workspaceCacheStorageService.getRolesPermissions(workspaceId),
|
this.workspacePermissionsCacheStorageService.getRolesPermissions(
|
||||||
|
workspaceId,
|
||||||
|
),
|
||||||
getCacheVersion: () =>
|
getCacheVersion: () =>
|
||||||
this.workspaceCacheStorageService.getRolesPermissionsVersionFromCache(
|
this.workspacePermissionsCacheStorageService.getRolesPermissionsVersion(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
),
|
),
|
||||||
recomputeCache: (params) =>
|
recomputeCache: (params) =>
|
||||||
this.workspaceRolesPermissionsCacheService.recomputeRolesPermissionsCache(
|
this.workspacePermissionsCacheService.recomputeRolesPermissionsCache(
|
||||||
params,
|
params,
|
||||||
),
|
),
|
||||||
cachedEntityName: 'Roles permissions',
|
cachedEntityName: 'Roles permissions',
|
||||||
@ -281,8 +264,8 @@ export class WorkspaceDatasourceFactory {
|
|||||||
cachedRolesPermissions,
|
cachedRolesPermissions,
|
||||||
}: {
|
}: {
|
||||||
workspaceDataSource: WorkspaceDataSource;
|
workspaceDataSource: WorkspaceDataSource;
|
||||||
cachedRolesPermissionsVersion: string | undefined;
|
cachedRolesPermissionsVersion: string;
|
||||||
cachedRolesPermissions: ObjectRecordsPermissionsByRoleId | undefined;
|
cachedRolesPermissions: ObjectRecordsPermissionsByRoleId;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
this.updateWorkspaceDataSourceIfNeeded({
|
this.updateWorkspaceDataSourceIfNeeded({
|
||||||
workspaceDataSource,
|
workspaceDataSource,
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permi
|
|||||||
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
||||||
import { WorkspaceFeatureFlagsMapCacheModule } from 'src/engine/metadata-modules/workspace-feature-flags-map-cache/workspace-feature-flags-map-cache.module';
|
import { WorkspaceFeatureFlagsMapCacheModule } from 'src/engine/metadata-modules/workspace-feature-flags-map-cache/workspace-feature-flags-map-cache.module';
|
||||||
import { WorkspaceMetadataCacheModule } from 'src/engine/metadata-modules/workspace-metadata-cache/workspace-metadata-cache.module';
|
import { WorkspaceMetadataCacheModule } from 'src/engine/metadata-modules/workspace-metadata-cache/workspace-metadata-cache.module';
|
||||||
import { WorkspaceRolesPermissionsCacheModule } from 'src/engine/metadata-modules/workspace-roles-permissions-cache/workspace-roles-permissions-cache.module';
|
import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module';
|
||||||
import { entitySchemaFactories } from 'src/engine/twenty-orm/factories';
|
import { entitySchemaFactories } from 'src/engine/twenty-orm/factories';
|
||||||
import { EntitySchemaFactory } from 'src/engine/twenty-orm/factories/entity-schema.factory';
|
import { EntitySchemaFactory } from 'src/engine/twenty-orm/factories/entity-schema.factory';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
@ -26,8 +26,8 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/
|
|||||||
WorkspaceCacheStorageModule,
|
WorkspaceCacheStorageModule,
|
||||||
WorkspaceMetadataCacheModule,
|
WorkspaceMetadataCacheModule,
|
||||||
PermissionsModule,
|
PermissionsModule,
|
||||||
WorkspaceRolesPermissionsCacheModule,
|
|
||||||
WorkspaceFeatureFlagsMapCacheModule,
|
WorkspaceFeatureFlagsMapCacheModule,
|
||||||
|
WorkspacePermissionsCacheModule,
|
||||||
FeatureFlagModule,
|
FeatureFlagModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
|
|||||||
@ -2,9 +2,7 @@ import { Injectable } from '@nestjs/common';
|
|||||||
|
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
|
|
||||||
import { ObjectRecordsPermissionsByRoleId } from 'twenty-shared/types';
|
|
||||||
import { EntitySchemaOptions } from 'typeorm';
|
import { EntitySchemaOptions } from 'typeorm';
|
||||||
import { v4 } from 'uuid';
|
|
||||||
|
|
||||||
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||||
|
|
||||||
@ -23,12 +21,15 @@ export enum WorkspaceCacheKeys {
|
|||||||
MetadataObjectMetadataMaps = 'metadata:object-metadata-maps',
|
MetadataObjectMetadataMaps = 'metadata:object-metadata-maps',
|
||||||
MetadataObjectMetadataOngoingCachingLock = 'metadata:object-metadata-ongoing-caching-lock',
|
MetadataObjectMetadataOngoingCachingLock = 'metadata:object-metadata-ongoing-caching-lock',
|
||||||
MetadataVersion = 'metadata:workspace-metadata-version',
|
MetadataVersion = 'metadata:workspace-metadata-version',
|
||||||
MetadataRolesPermissions = 'metadata:roles-permissions',
|
FeatureFlagMap = 'feature-flag:feature-flag-map',
|
||||||
MetadataRolesPermissionsVersion = 'metadata:roles-permissions-version',
|
FeatureFlagMapVersion = 'feature-flag:feature-flag-map-version',
|
||||||
MetadataRolesPermissionsOngoingCachingLock = 'metadata:roles-permissions-ongoing-caching-lock',
|
|
||||||
FeatureFlagMap = 'feature-flag-map',
|
|
||||||
FeatureFlagMapVersion = 'feature-flag-map-version',
|
|
||||||
FeatureFlagMapOngoingCachingLock = 'feature-flag-map-ongoing-caching-lock',
|
FeatureFlagMapOngoingCachingLock = 'feature-flag-map-ongoing-caching-lock',
|
||||||
|
MetadataPermissionsRolesPermissions = 'metadata:permissions:roles-permissions',
|
||||||
|
MetadataPermissionsRolesPermissionsVersion = 'metadata:permissions:roles-permissions-version',
|
||||||
|
MetadataPermissionsRolesPermissionsOngoingCachingLock = 'metadata:permissions:roles-permissions-ongoing-caching-lock',
|
||||||
|
MetadataPermissionsUserWorkspaceRoleMap = 'metadata:permissions:user-workspace-role-map',
|
||||||
|
MetadataPermissionsUserWorkspaceRoleMapVersion = 'metadata:permissions:user-workspace-role-map-version',
|
||||||
|
MetadataPermissionsUserWorkspaceRoleMapOngoingCachingLock = 'metadata:permissions:user-workspace-role-map-ongoing-caching-lock',
|
||||||
}
|
}
|
||||||
|
|
||||||
const TTL_INFINITE = 0;
|
const TTL_INFINITE = 0;
|
||||||
@ -186,74 +187,6 @@ export class WorkspaceCacheStorageService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getRolesPermissionsVersionFromCache(
|
|
||||||
workspaceId: string,
|
|
||||||
): Promise<string | undefined> {
|
|
||||||
return this.cacheStorageService.get<string>(
|
|
||||||
`${WorkspaceCacheKeys.MetadataRolesPermissionsVersion}:${workspaceId}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async setRolesPermissionsVersion(workspaceId: string): Promise<string> {
|
|
||||||
const rolesPermissionsVersion = v4();
|
|
||||||
|
|
||||||
await this.cacheStorageService.set<string>(
|
|
||||||
`${WorkspaceCacheKeys.MetadataRolesPermissionsVersion}:${workspaceId}`,
|
|
||||||
rolesPermissionsVersion,
|
|
||||||
TTL_INFINITE,
|
|
||||||
);
|
|
||||||
|
|
||||||
return rolesPermissionsVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
async setRolesPermissions(
|
|
||||||
workspaceId: string,
|
|
||||||
permissions: ObjectRecordsPermissionsByRoleId,
|
|
||||||
): Promise<{
|
|
||||||
newRolesPermissionsVersion: string;
|
|
||||||
}> {
|
|
||||||
const [, newRolesPermissionsVersion] = await Promise.all([
|
|
||||||
this.cacheStorageService.set<ObjectRecordsPermissionsByRoleId>(
|
|
||||||
`${WorkspaceCacheKeys.MetadataRolesPermissions}:${workspaceId}`,
|
|
||||||
permissions,
|
|
||||||
TTL_INFINITE,
|
|
||||||
),
|
|
||||||
this.setRolesPermissionsVersion(workspaceId),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return { newRolesPermissionsVersion };
|
|
||||||
}
|
|
||||||
|
|
||||||
getRolesPermissions(
|
|
||||||
workspaceId: string,
|
|
||||||
): Promise<ObjectRecordsPermissionsByRoleId | undefined> {
|
|
||||||
return this.cacheStorageService.get<ObjectRecordsPermissionsByRoleId>(
|
|
||||||
`${WorkspaceCacheKeys.MetadataRolesPermissions}:${workspaceId}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
addRolesPermissionsOngoingCachingLock(workspaceId: string) {
|
|
||||||
return this.cacheStorageService.set<boolean>(
|
|
||||||
`${WorkspaceCacheKeys.MetadataRolesPermissionsOngoingCachingLock}:${workspaceId}`,
|
|
||||||
true,
|
|
||||||
1_000 * 60, // 1 minute
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
removeRolesPermissionsOngoingCachingLock(workspaceId: string) {
|
|
||||||
return this.cacheStorageService.del(
|
|
||||||
`${WorkspaceCacheKeys.MetadataRolesPermissionsOngoingCachingLock}:${workspaceId}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getRolesPermissionsOngoingCachingLock(
|
|
||||||
workspaceId: string,
|
|
||||||
): Promise<boolean | undefined> {
|
|
||||||
return this.cacheStorageService.get<boolean>(
|
|
||||||
`${WorkspaceCacheKeys.MetadataRolesPermissionsOngoingCachingLock}:${workspaceId}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getFeatureFlagsMapVersionFromCache(
|
getFeatureFlagsMapVersionFromCache(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
): Promise<string | undefined> {
|
): Promise<string | undefined> {
|
||||||
@ -341,15 +274,27 @@ export class WorkspaceCacheStorageService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await this.cacheStorageService.del(
|
await this.cacheStorageService.del(
|
||||||
`${WorkspaceCacheKeys.MetadataRolesPermissions}:${workspaceId}`,
|
`${WorkspaceCacheKeys.MetadataPermissionsRolesPermissions}:${workspaceId}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.cacheStorageService.del(
|
await this.cacheStorageService.del(
|
||||||
`${WorkspaceCacheKeys.MetadataRolesPermissionsVersion}:${workspaceId}`,
|
`${WorkspaceCacheKeys.MetadataPermissionsRolesPermissionsVersion}:${workspaceId}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.cacheStorageService.del(
|
await this.cacheStorageService.del(
|
||||||
`${WorkspaceCacheKeys.MetadataRolesPermissionsOngoingCachingLock}:${workspaceId}`,
|
`${WorkspaceCacheKeys.MetadataPermissionsRolesPermissionsOngoingCachingLock}:${workspaceId}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.cacheStorageService.del(
|
||||||
|
`${WorkspaceCacheKeys.MetadataPermissionsUserWorkspaceRoleMap}:${workspaceId}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.cacheStorageService.del(
|
||||||
|
`${WorkspaceCacheKeys.MetadataPermissionsUserWorkspaceRoleMapVersion}:${workspaceId}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.cacheStorageService.del(
|
||||||
|
`${WorkspaceCacheKeys.MetadataPermissionsUserWorkspaceRoleMapOngoingCachingLock}:${workspaceId}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.cacheStorageService.del(
|
await this.cacheStorageService.del(
|
||||||
|
|||||||
Reference in New Issue
Block a user