Added relations to SSE events (#17683)
Fixes https://github.com/twentyhq/core-team-issues/issues/2192 This PR implements what is necessary to re-create the query that we build on the frontend to obtain the returned object record from a mutation, but on the backend, which was only partially implemented for REST API. Usually we want to have relations with only their id and label identifier field to have lighter payloads. In the event we only had depth 0 fields, with this PR we have all events with depth 1 relations. We have depth 2 for many-to-many cases, like updateOne or updateMany result : - Junction tables - Activity target tables
This commit is contained in:
+313
@@ -5,7 +5,10 @@ import {
|
||||
type RecordGqlOperationFilter,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import { ProcessNestedRelationsHelper } from 'src/engine/api/common/common-nested-relations-processor/process-nested-relations.helper';
|
||||
import { CommonSelectFieldsHelper } from 'src/engine/api/common/common-select-fields/common-select-fields-helper';
|
||||
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
|
||||
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 { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
|
||||
import { COMPANY_FLAT_FIELDS_MOCK } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/company-flat-fields.mock';
|
||||
@@ -16,6 +19,7 @@ import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object
|
||||
import { EventStreamService } from 'src/engine/subscriptions/event-stream.service';
|
||||
import { SubscriptionService } from 'src/engine/subscriptions/subscription.service';
|
||||
import { type EventStreamData } from 'src/engine/subscriptions/types/event-stream-data.type';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import { type WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event-batch.type';
|
||||
import { WorkspaceEventEmitterService } from 'src/engine/workspace-event-emitter/workspace-event-emitter.service';
|
||||
@@ -79,6 +83,19 @@ describe('WorkspaceEventEmitterService', () => {
|
||||
let mockWorkspaceCacheService: {
|
||||
getOrRecompute: jest.Mock;
|
||||
};
|
||||
let mockProcessNestedRelationsHelper: jest.Mocked<
|
||||
Pick<ProcessNestedRelationsHelper, 'processNestedRelations'>
|
||||
>;
|
||||
let mockWorkspaceManyOrAllFlatEntityMapsCacheService: jest.Mocked<
|
||||
Pick<
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
'getOrRecomputeManyOrAllFlatEntityMaps'
|
||||
>
|
||||
>;
|
||||
|
||||
let mockGlobalWorkspaceOrmManager: jest.Mocked<
|
||||
Pick<GlobalWorkspaceOrmManager, 'getGlobalWorkspaceDataSourceReplica'>
|
||||
>;
|
||||
|
||||
const workspaceId = COMPANY_FLAT_OBJECT_MOCK.workspaceId;
|
||||
const streamChannelId = 'test-stream-channel-id';
|
||||
@@ -225,6 +242,29 @@ describe('WorkspaceEventEmitterService', () => {
|
||||
getOrRecompute: jest.fn().mockImplementation(createCacheMock()),
|
||||
};
|
||||
|
||||
mockProcessNestedRelationsHelper = {
|
||||
processNestedRelations: jest.fn(),
|
||||
};
|
||||
|
||||
mockWorkspaceManyOrAllFlatEntityMapsCacheService = {
|
||||
getOrRecomputeManyOrAllFlatEntityMaps: jest.fn().mockResolvedValue({
|
||||
flatFieldMetadataMaps: mockFlatFieldMetadataMaps,
|
||||
flatObjectMetadataMaps: {
|
||||
byId: {
|
||||
[companyObjectMetadata.id]: companyObjectMetadata,
|
||||
},
|
||||
idByUniversalIdentifier: {},
|
||||
universalIdentifiersByApplicationId: {},
|
||||
},
|
||||
} as never),
|
||||
};
|
||||
|
||||
mockGlobalWorkspaceOrmManager = {
|
||||
getGlobalWorkspaceDataSourceReplica: jest.fn().mockResolvedValue({
|
||||
getRepository: jest.fn(),
|
||||
}),
|
||||
};
|
||||
|
||||
(buildRowLevelPermissionRecordFilter as jest.Mock).mockReturnValue({});
|
||||
(
|
||||
isRecordMatchingRLSRowLevelPermissionPredicate as jest.Mock
|
||||
@@ -245,6 +285,22 @@ describe('WorkspaceEventEmitterService', () => {
|
||||
provide: WorkspaceCacheService,
|
||||
useValue: mockWorkspaceCacheService,
|
||||
},
|
||||
{
|
||||
provide: ProcessNestedRelationsHelper,
|
||||
useValue: mockProcessNestedRelationsHelper,
|
||||
},
|
||||
{
|
||||
provide: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
useValue: mockWorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
},
|
||||
{
|
||||
provide: GlobalWorkspaceOrmManager,
|
||||
useValue: mockGlobalWorkspaceOrmManager,
|
||||
},
|
||||
{
|
||||
provide: CommonSelectFieldsHelper,
|
||||
useValue: new CommonSelectFieldsHelper(),
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
@@ -1154,5 +1210,262 @@ describe('WorkspaceEventEmitterService', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('nested relations enrichment', () => {
|
||||
it('should enrich events with nested relations when publishing', async () => {
|
||||
const recordAfter = { id: 'record-1', name: 'Test Company' };
|
||||
|
||||
const eventBatch: WorkspaceEventBatch<MockObjectRecordEvent> = {
|
||||
name: 'company.created',
|
||||
workspaceId,
|
||||
objectMetadata: companyObjectMetadata,
|
||||
events: [
|
||||
createMockEvent({
|
||||
properties: {
|
||||
after: recordAfter,
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
await service.publish(eventBatch as WorkspaceEventBatch<never>);
|
||||
|
||||
expect(
|
||||
mockWorkspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps,
|
||||
).toHaveBeenCalledWith({
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatObjectMetadataMaps', 'flatFieldMetadataMaps'],
|
||||
});
|
||||
|
||||
expect(
|
||||
mockGlobalWorkspaceOrmManager.getGlobalWorkspaceDataSourceReplica,
|
||||
).toHaveBeenCalled();
|
||||
|
||||
expect(
|
||||
mockProcessNestedRelationsHelper.processNestedRelations,
|
||||
).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
parentObjectMetadataItem: companyObjectMetadata,
|
||||
parentObjectRecords: expect.arrayContaining([recordAfter]),
|
||||
authContext: expect.objectContaining({
|
||||
userWorkspaceId,
|
||||
userId: 'test-user-id',
|
||||
}),
|
||||
workspaceDataSource: expect.objectContaining({
|
||||
getRepository: expect.any(Function),
|
||||
}),
|
||||
rolePermissionConfig: expect.objectContaining({
|
||||
intersectionOf: [roleId],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should include both before and after records when enriching update events', async () => {
|
||||
const recordBefore = { id: 'record-1', name: 'Old Name' };
|
||||
const recordAfter = { id: 'record-1', name: 'New Name' };
|
||||
|
||||
const eventBatch: WorkspaceEventBatch<MockObjectRecordEvent> = {
|
||||
name: 'company.updated',
|
||||
workspaceId,
|
||||
objectMetadata: companyObjectMetadata,
|
||||
events: [
|
||||
createMockEvent({
|
||||
properties: {
|
||||
before: recordBefore,
|
||||
after: recordAfter,
|
||||
updatedFields: ['name'],
|
||||
diff: { name: { before: 'Old Name', after: 'New Name' } },
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
await service.publish(eventBatch as WorkspaceEventBatch<never>);
|
||||
|
||||
expect(
|
||||
mockProcessNestedRelationsHelper.processNestedRelations,
|
||||
).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
parentObjectRecords: expect.arrayContaining([
|
||||
recordBefore,
|
||||
recordAfter,
|
||||
]),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should include only before records when enriching delete events', async () => {
|
||||
const recordBefore = { id: 'record-1', name: 'Deleted Company' };
|
||||
|
||||
const streamDataWithFilter: EventStreamData = {
|
||||
...mockStreamData,
|
||||
queries: {
|
||||
'query-1': {
|
||||
objectNameSingular: 'company',
|
||||
variables: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
mockEventStreamService.getStreamsData.mockResolvedValue(
|
||||
new Map([[streamChannelId, streamDataWithFilter]]) as Map<
|
||||
string,
|
||||
EventStreamData | undefined
|
||||
>,
|
||||
);
|
||||
|
||||
const eventBatch: WorkspaceEventBatch<MockObjectRecordEvent> = {
|
||||
name: 'company.deleted',
|
||||
workspaceId,
|
||||
objectMetadata: companyObjectMetadata,
|
||||
events: [
|
||||
createMockEvent({
|
||||
properties: {
|
||||
before: recordBefore,
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
await service.publish(eventBatch as WorkspaceEventBatch<never>);
|
||||
|
||||
expect(
|
||||
mockProcessNestedRelationsHelper.processNestedRelations,
|
||||
).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
parentObjectRecords: expect.arrayContaining([recordBefore]),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should enrich multiple records from batch events', async () => {
|
||||
const record1 = { id: 'record-1', name: 'Company 1' };
|
||||
const record2 = { id: 'record-2', name: 'Company 2' };
|
||||
const record3 = { id: 'record-3', name: 'Company 3' };
|
||||
|
||||
const eventBatch: WorkspaceEventBatch<MockObjectRecordEvent> = {
|
||||
name: 'company.created',
|
||||
workspaceId,
|
||||
objectMetadata: companyObjectMetadata,
|
||||
events: [
|
||||
createMockEvent({
|
||||
recordId: 'record-1',
|
||||
properties: { after: record1 },
|
||||
}),
|
||||
createMockEvent({
|
||||
recordId: 'record-2',
|
||||
properties: { after: record2 },
|
||||
}),
|
||||
createMockEvent({
|
||||
recordId: 'record-3',
|
||||
properties: { after: record3 },
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
await service.publish(eventBatch as WorkspaceEventBatch<never>);
|
||||
|
||||
expect(
|
||||
mockProcessNestedRelationsHelper.processNestedRelations,
|
||||
).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
parentObjectRecords: expect.arrayContaining([
|
||||
record1,
|
||||
record2,
|
||||
record3,
|
||||
]),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should not call processNestedRelations when no events match', async () => {
|
||||
const permissionsWithoutRead: ObjectsPermissionsByRoleId = {
|
||||
[roleId]: {
|
||||
[companyObjectMetadata.id]: {
|
||||
canReadObjectRecords: false,
|
||||
canUpdateObjectRecords: true,
|
||||
canSoftDeleteObjectRecords: true,
|
||||
canDestroyObjectRecords: true,
|
||||
restrictedFields: {},
|
||||
rowLevelPermissionPredicates: [],
|
||||
rowLevelPermissionPredicateGroups: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
mockWorkspaceCacheService.getOrRecompute.mockImplementation(
|
||||
createCacheMock({ rolesPermissions: permissionsWithoutRead }),
|
||||
);
|
||||
|
||||
const eventBatch: WorkspaceEventBatch<MockObjectRecordEvent> = {
|
||||
name: 'company.created',
|
||||
workspaceId,
|
||||
objectMetadata: companyObjectMetadata,
|
||||
events: [createMockEvent()],
|
||||
};
|
||||
|
||||
await service.publish(eventBatch as WorkspaceEventBatch<never>);
|
||||
|
||||
expect(
|
||||
mockProcessNestedRelationsHelper.processNestedRelations,
|
||||
).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should pass correct role permission config when enriching events', async () => {
|
||||
const customRoleId = 'custom-role-id';
|
||||
const customUserWorkspaceRoleMap = {
|
||||
[userWorkspaceId]: customRoleId,
|
||||
};
|
||||
|
||||
const customRolesPermissions: ObjectsPermissionsByRoleId = {
|
||||
[customRoleId]: {
|
||||
[companyObjectMetadata.id]: {
|
||||
canReadObjectRecords: true,
|
||||
canUpdateObjectRecords: true,
|
||||
canSoftDeleteObjectRecords: true,
|
||||
canDestroyObjectRecords: true,
|
||||
restrictedFields: {},
|
||||
rowLevelPermissionPredicates: [],
|
||||
rowLevelPermissionPredicateGroups: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
mockWorkspaceCacheService.getOrRecompute.mockImplementation(
|
||||
createCacheMock({
|
||||
userWorkspaceRoleMap: customUserWorkspaceRoleMap,
|
||||
rolesPermissions: customRolesPermissions,
|
||||
}),
|
||||
);
|
||||
|
||||
const recordAfter = { id: 'record-1', name: 'Test Company' };
|
||||
|
||||
const eventBatch: WorkspaceEventBatch<MockObjectRecordEvent> = {
|
||||
name: 'company.created',
|
||||
workspaceId,
|
||||
objectMetadata: companyObjectMetadata,
|
||||
events: [
|
||||
createMockEvent({
|
||||
properties: {
|
||||
after: recordAfter,
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
await service.publish(eventBatch as WorkspaceEventBatch<never>);
|
||||
|
||||
expect(
|
||||
mockProcessNestedRelationsHelper.processNestedRelations,
|
||||
).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
rolePermissionConfig: {
|
||||
intersectionOf: [customRoleId],
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+12
-1
@@ -1,5 +1,9 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
|
||||
import { ProcessNestedRelationsV2Helper } from 'src/engine/api/common/common-nested-relations-processor/process-nested-relations-v2.helper';
|
||||
import { ProcessNestedRelationsHelper } from 'src/engine/api/common/common-nested-relations-processor/process-nested-relations.helper';
|
||||
import { CommonSelectFieldsHelper } from 'src/engine/api/common/common-select-fields/common-select-fields-helper';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
|
||||
import { SubscriptionsModule } from 'src/engine/subscriptions/subscriptions.module';
|
||||
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
|
||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
@@ -8,11 +12,18 @@ import { WorkspaceEventEmitterService } from 'src/engine/workspace-event-emitter
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [SubscriptionsModule, WorkspaceCacheModule],
|
||||
imports: [
|
||||
SubscriptionsModule,
|
||||
WorkspaceCacheModule,
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
],
|
||||
providers: [
|
||||
WorkspaceEventEmitter,
|
||||
WorkspaceEventEmitterService,
|
||||
WorkspaceEventEmitterResolver,
|
||||
ProcessNestedRelationsHelper,
|
||||
ProcessNestedRelationsV2Helper,
|
||||
CommonSelectFieldsHelper,
|
||||
],
|
||||
exports: [WorkspaceEventEmitter, WorkspaceEventEmitterService],
|
||||
})
|
||||
|
||||
+117
-1
@@ -1,28 +1,39 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { QUERY_MAX_RECORDS_FROM_RELATION } from 'twenty-shared/constants';
|
||||
import { type ObjectRecordEvent } from 'twenty-shared/database-events';
|
||||
import {
|
||||
Nullable,
|
||||
ObjectRecord,
|
||||
type ObjectsPermissionsByRoleId,
|
||||
type RecordGqlOperationFilter,
|
||||
type RestrictedFieldsPermissions,
|
||||
} from 'twenty-shared/types';
|
||||
import { combineFilters, isDefined } from 'twenty-shared/utils';
|
||||
import { FindOptionsRelations, ObjectLiteral } from 'typeorm';
|
||||
|
||||
import { ProcessNestedRelationsHelper } from 'src/engine/api/common/common-nested-relations-processor/process-nested-relations.helper';
|
||||
import { CommonSelectFieldsHelper } from 'src/engine/api/common/common-select-fields/common-select-fields-helper';
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
|
||||
import { type SerializableAuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { type FlatWorkspaceMemberMaps } from 'src/engine/core-modules/user/types/flat-workspace-member-maps.type';
|
||||
import { transformEventToWebhookEvent } from 'src/engine/metadata-modules/webhook/utils/transform-event-to-webhook-event';
|
||||
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 { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
|
||||
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 { UserWorkspaceRoleMap } from 'src/engine/metadata-modules/role-target/types/user-workspace-role-map';
|
||||
import { type FlatRowLevelPermissionPredicateGroupMaps } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-group-maps.type';
|
||||
import { type FlatRowLevelPermissionPredicateMaps } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-maps.type';
|
||||
import { transformEventToWebhookEvent } from 'src/engine/metadata-modules/webhook/utils/transform-event-to-webhook-event';
|
||||
import { SubscriptionChannel } from 'src/engine/subscriptions/enums/subscription-channel.enum';
|
||||
import { EventStreamService } from 'src/engine/subscriptions/event-stream.service';
|
||||
import { SubscriptionService } from 'src/engine/subscriptions/subscription.service';
|
||||
import { type EventStreamData } from 'src/engine/subscriptions/types/event-stream-data.type';
|
||||
import { ObjectRecordSubscriptionEvent } from 'src/engine/subscriptions/types/object-record-subscription-event.type';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config';
|
||||
import { buildRowLevelPermissionRecordFilter } from 'src/engine/twenty-orm/utils/build-row-level-permission-record-filter.util';
|
||||
import { isRecordMatchingRLSRowLevelPermissionPredicate } from 'src/engine/twenty-orm/utils/is-record-matching-rls-row-level-permission-predicate.util';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
@@ -35,6 +46,10 @@ export class WorkspaceEventEmitterService {
|
||||
private readonly subscriptionService: SubscriptionService,
|
||||
private readonly eventStreamService: EventStreamService,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
private readonly processNestedRelationsHelper: ProcessNestedRelationsHelper,
|
||||
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly commonSelectFieldsHelper: CommonSelectFieldsHelper,
|
||||
) {}
|
||||
|
||||
async publish(
|
||||
@@ -214,6 +229,15 @@ export class WorkspaceEventEmitterService {
|
||||
}
|
||||
|
||||
if (matchedEvents.length > 0) {
|
||||
await this.enrichEventBatchWithNestedRelations({
|
||||
objectMetadata: workspaceEventBatch.objectMetadata,
|
||||
events: matchedEvents.map((e) => e.event),
|
||||
streamData,
|
||||
permissionsContext,
|
||||
workspaceId: workspaceEventBatch.workspaceId,
|
||||
roleId,
|
||||
});
|
||||
|
||||
await this.subscriptionService.publishToEventStream({
|
||||
workspaceId: workspaceEventBatch.workspaceId,
|
||||
eventStreamChannelId: streamChannelId,
|
||||
@@ -222,6 +246,98 @@ export class WorkspaceEventEmitterService {
|
||||
}
|
||||
}
|
||||
|
||||
private async enrichEventBatchWithNestedRelations({
|
||||
streamData,
|
||||
objectMetadata,
|
||||
events,
|
||||
workspaceId,
|
||||
permissionsContext,
|
||||
roleId,
|
||||
}: {
|
||||
streamData: EventStreamData;
|
||||
objectMetadata: FlatObjectMetadata;
|
||||
events: ObjectRecordEvent[];
|
||||
workspaceId: string;
|
||||
roleId: string;
|
||||
permissionsContext: {
|
||||
flatRowLevelPermissionPredicateMaps: FlatRowLevelPermissionPredicateMaps;
|
||||
flatRowLevelPermissionPredicateGroupMaps: FlatRowLevelPermissionPredicateGroupMaps;
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
userWorkspaceRoleMap: UserWorkspaceRoleMap;
|
||||
rolesPermissions: ObjectsPermissionsByRoleId;
|
||||
};
|
||||
}) {
|
||||
const { flatFieldMetadataMaps, flatObjectMetadataMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatObjectMetadataMaps', 'flatFieldMetadataMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const allRecords: ObjectRecord[] = [];
|
||||
|
||||
for (const event of events) {
|
||||
if ('before' in event.properties) {
|
||||
const recordBefore = event.properties.before as Nullable<ObjectRecord>;
|
||||
|
||||
if (isDefined(recordBefore)) {
|
||||
allRecords.push(recordBefore);
|
||||
}
|
||||
}
|
||||
|
||||
if ('after' in event.properties) {
|
||||
const recordAfter = event.properties.after as Nullable<ObjectRecord>;
|
||||
|
||||
if (isDefined(recordAfter)) {
|
||||
allRecords.push(recordAfter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const rolePermissionConfig: RolePermissionConfig = {
|
||||
intersectionOf: [roleId],
|
||||
};
|
||||
|
||||
const globalWorkspaceDataSource =
|
||||
await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSourceReplica();
|
||||
|
||||
const selectedFields = this.commonSelectFieldsHelper.computeFromDepth({
|
||||
depth: 1,
|
||||
flatObjectMetadata: objectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
objectsPermissions: permissionsContext.rolesPermissions[roleId],
|
||||
onlyUseLabelIdentifierFieldsInRelations: true,
|
||||
recurseIntoJunctionTableRelations: true,
|
||||
});
|
||||
|
||||
const commonQueryParser = new GraphqlQueryParser(
|
||||
objectMetadata,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
const selectedFieldsResult =
|
||||
commonQueryParser.parseSelectedFields(selectedFields);
|
||||
|
||||
await this.processNestedRelationsHelper.processNestedRelations({
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
parentObjectMetadataItem: objectMetadata,
|
||||
parentObjectRecords: allRecords,
|
||||
authContext: streamData.authContext,
|
||||
limit: QUERY_MAX_RECORDS_FROM_RELATION,
|
||||
rolePermissionConfig,
|
||||
workspaceDataSource: globalWorkspaceDataSource,
|
||||
relations: selectedFieldsResult.relations as Record<
|
||||
string,
|
||||
FindOptionsRelations<ObjectLiteral>
|
||||
>,
|
||||
selectedFields: selectedFieldsResult.select,
|
||||
});
|
||||
}
|
||||
|
||||
private buildSubscriberRLSFilter(
|
||||
subscriberAuthContext: SerializableAuthContext,
|
||||
roleId: string,
|
||||
|
||||
Reference in New Issue
Block a user