Refactor workspace cache service (#16208)

## Context
We've recently introduced a new workspace cache service which now acts
as a cache access and local storage for all workspace related data,
deprecating the individual specific services.
- Better performance through multiple caching/fetching strategies
- Consistent data access patterns across the codebase
- Reduced redis queries through MGET/MSET/PIPELINE with multiple cache
keys
This commit is contained in:
Weiko
2025-12-01 17:08:21 +01:00
committed by GitHub
parent 4e0545ebc5
commit 1eb2e44058
77 changed files with 1306 additions and 1863 deletions
@@ -14,7 +14,7 @@ import {
PermissionsExceptionMessage,
} from 'src/engine/metadata-modules/permissions/permissions.exception';
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
describe('ObjectPermissionService', () => {
let service: ObjectPermissionService;
@@ -22,7 +22,7 @@ describe('ObjectPermissionService', () => {
Repository<ObjectPermissionEntity>
>;
let roleRepository: jest.Mocked<Repository<RoleEntity>>;
let workspacePermissionsCacheService: jest.Mocked<WorkspacePermissionsCacheService>;
let workspaceCacheService: jest.Mocked<WorkspaceCacheService>;
let workspaceManyOrAllFlatEntityMapsCacheService: jest.Mocked<WorkspaceManyOrAllFlatEntityMapsCacheService>;
beforeEach(async () => {
@@ -49,9 +49,10 @@ describe('ObjectPermissionService', () => {
},
},
{
provide: WorkspacePermissionsCacheService,
provide: WorkspaceCacheService,
useValue: {
recomputeRolesPermissionsCache: jest.fn(),
invalidate: jest.fn(),
invalidateAndRecompute: jest.fn(),
},
},
{
@@ -68,9 +69,7 @@ describe('ObjectPermissionService', () => {
getRepositoryToken(ObjectPermissionEntity),
);
roleRepository = module.get(getRepositoryToken(RoleEntity));
workspacePermissionsCacheService = module.get(
WorkspacePermissionsCacheService,
);
workspaceCacheService = module.get(WorkspaceCacheService);
workspaceManyOrAllFlatEntityMapsCacheService = module.get(
WorkspaceManyOrAllFlatEntityMapsCacheService,
);
@@ -145,7 +144,7 @@ describe('ObjectPermissionService', () => {
// Verify that upsert was never called
expect(objectPermissionRepository.upsert).not.toHaveBeenCalled();
expect(
workspacePermissionsCacheService.recomputeRolesPermissionsCache,
workspaceCacheService.invalidateAndRecompute,
).not.toHaveBeenCalled();
});
@@ -230,12 +229,10 @@ describe('ObjectPermissionService', () => {
conflictPaths: ['objectMetadataId', 'roleId'],
},
);
expect(
workspacePermissionsCacheService.recomputeRolesPermissionsCache,
).toHaveBeenCalledWith({
expect(workspaceCacheService.invalidateAndRecompute).toHaveBeenCalledWith(
workspaceId,
roleIds: [roleId],
});
['rolesPermissions'],
);
});
it('should throw PermissionsException when object metadata is not found', async () => {
@@ -20,7 +20,7 @@ import {
PermissionsExceptionMessage,
} from 'src/engine/metadata-modules/permissions/permissions.exception';
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
import { getMockFieldMetadataEntity } from 'src/utils/__test__/get-field-metadata-entity.mock';
describe('FieldPermissionService', () => {
@@ -30,7 +30,7 @@ describe('FieldPermissionService', () => {
>;
let roleRepository: jest.Mocked<Repository<RoleEntity>>;
let fieldMetadataRepository: jest.Mocked<Repository<FieldMetadataEntity>>;
let workspacePermissionsCacheService: jest.Mocked<WorkspacePermissionsCacheService>;
let workspaceCacheService: jest.Mocked<WorkspaceCacheService>;
let workspaceManyOrAllFlatEntityMapsCacheService: jest.Mocked<WorkspaceManyOrAllFlatEntityMapsCacheService>;
const testWorkspaceId = '20202020-0000-0000-0000-000000000000';
@@ -91,10 +91,11 @@ describe('FieldPermissionService', () => {
},
},
{
provide: WorkspacePermissionsCacheService,
provide: WorkspaceCacheService,
useValue: {
getRolesPermissionsFromCache: jest.fn(),
recomputeRolesPermissionsCache: jest.fn(),
getOrRecompute: jest.fn(),
invalidate: jest.fn(),
invalidateAndRecompute: jest.fn(),
},
},
{
@@ -120,9 +121,7 @@ describe('FieldPermissionService', () => {
fieldMetadataRepository = module.get(
getRepositoryToken(FieldMetadataEntity),
);
workspacePermissionsCacheService = module.get(
WorkspacePermissionsCacheService,
);
workspaceCacheService = module.get(WorkspaceCacheService);
workspaceManyOrAllFlatEntityMapsCacheService = module.get(
WorkspaceManyOrAllFlatEntityMapsCacheService,
);
@@ -133,12 +132,9 @@ describe('FieldPermissionService', () => {
fieldTextMock,
fieldRelationMock,
]);
workspacePermissionsCacheService.getRolesPermissionsFromCache.mockResolvedValue(
{
version: '1',
data: mockRolesPermissions,
},
);
(workspaceCacheService.getOrRecompute as jest.Mock).mockResolvedValue({
rolesPermissions: mockRolesPermissions,
} as any);
const testFieldMetadata = getMockFieldMetadataEntity({
...fieldTextMock,
label: 'Test Field',
@@ -240,11 +236,8 @@ describe('FieldPermissionService', () => {
);
expect(
workspacePermissionsCacheService.recomputeRolesPermissionsCache,
).toHaveBeenCalledWith({
workspaceId: testWorkspaceId,
roleIds: [testRoleId],
});
workspaceCacheService.invalidateAndRecompute,
).toHaveBeenCalledWith(testWorkspaceId, ['rolesPermissions']);
});
it('should delete field permissions when both canReadFieldValue and canUpdateFieldValue are null', async () => {
@@ -533,12 +526,9 @@ describe('FieldPermissionService', () => {
});
it('should throw error when object permission is not found', async () => {
workspacePermissionsCacheService.getRolesPermissionsFromCache.mockResolvedValue(
{
version: '1',
data: {},
},
);
(workspaceCacheService.getOrRecompute as jest.Mock).mockResolvedValue({
rolesPermissions: {},
} as any);
const input = createUpsertInput([
{
@@ -14,8 +14,8 @@ import {
} from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { isFieldMetadataTypeRelation } from 'src/engine/metadata-modules/field-metadata/utils/is-field-metadata-type-relation.util';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { type UpsertFieldPermissionsInput } from 'src/engine/metadata-modules/object-permission/dtos/upsert-field-permissions.input';
@@ -26,7 +26,7 @@ import {
PermissionsExceptionMessage,
} from 'src/engine/metadata-modules/permissions/permissions.exception';
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
@Injectable()
export class FieldPermissionService {
@@ -37,7 +37,7 @@ export class FieldPermissionService {
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
@InjectRepository(FieldPermissionEntity)
private readonly fieldPermissionsRepository: Repository<FieldPermissionEntity>,
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
private readonly workspaceCacheService: WorkspaceCacheService,
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
) {}
@@ -53,10 +53,10 @@ export class FieldPermissionService {
workspaceId,
});
const { data: rolesPermissions } =
await this.workspacePermissionsCacheService.getRolesPermissionsFromCache({
workspaceId,
});
const { rolesPermissions } =
await this.workspaceCacheService.getOrRecompute(workspaceId, [
'rolesPermissions',
]);
await this.validateRoleIsEditableOrThrow({
role,
@@ -161,10 +161,9 @@ export class FieldPermissionService {
});
}
await this.workspacePermissionsCacheService.recomputeRolesPermissionsCache({
workspaceId,
roleIds: [input.roleId],
});
await this.workspaceCacheService.invalidateAndRecompute(workspaceId, [
'rolesPermissions',
]);
return this.fieldPermissionsRepository.find({
where: {
@@ -10,6 +10,7 @@ import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permi
import { ObjectPermissionService } from 'src/engine/metadata-modules/object-permission/object-permission.service';
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module';
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
@Module({
imports: [
@@ -22,6 +23,7 @@ import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/wor
]),
WorkspacePermissionsCacheModule,
WorkspaceManyOrAllFlatEntityMapsCacheModule,
WorkspaceCacheModule,
],
providers: [ObjectPermissionService, FieldPermissionService],
exports: [ObjectPermissionService, FieldPermissionService],
@@ -17,7 +17,7 @@ import {
PermissionsExceptionMessage,
} from 'src/engine/metadata-modules/permissions/permissions.exception';
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
export class ObjectPermissionService {
constructor(
@@ -27,7 +27,7 @@ export class ObjectPermissionService {
private readonly roleRepository: Repository<RoleEntity>,
@InjectRepository(ObjectMetadataEntity)
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
private readonly workspaceCacheService: WorkspaceCacheService,
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
) {}
@@ -107,12 +107,9 @@ export class ObjectPermissionService {
throw new Error('Failed to upsert object permission');
}
await this.workspacePermissionsCacheService.recomputeRolesPermissionsCache(
{
workspaceId,
roleIds: [input.roleId],
},
);
await this.workspaceCacheService.invalidateAndRecompute(workspaceId, [
'rolesPermissions',
]);
return this.objectPermissionRepository.find({
where: {