Add db event emitter in twenty orm (#13167)
## Context Add an eventEmitter instance to twenty datasources so we can emit DB events. Add input and output formatting to twenty orm (formatData, formatResult) Those 2 elements simplified existing logic when we interact with the ORM, input will be formatted by the ORM so we can directly use field-like structure instead of column-like. The output will be formatted, for builder queries it will be in `result.generatedMaps` where `result.raw` preserves the previous column-like structure. Important change: We now have an authContext that we can pass when we get a repository, this will be used for the different events emitted in the ORM. We also removed the caching for repositories as it was not scaling well and not necessary imho Note: An upcoming PR should handle the onDelete: cascade behavior where we send DESTROY events in cascade when there is an onDelete: CASCADE on the FK. --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -13,7 +13,6 @@ import {
|
||||
Not,
|
||||
} from 'typeorm';
|
||||
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
import {
|
||||
generateBulkDeleteToolSchema,
|
||||
generateFindOneToolSchema,
|
||||
@ -25,13 +24,11 @@ import { isWorkflowRelatedObject } from 'src/engine/metadata-modules/agent/utils
|
||||
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||
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 { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
|
||||
@Injectable()
|
||||
export class ToolService {
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||
private readonly objectMetadataService: ObjectMetadataService,
|
||||
protected readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
|
||||
) {}
|
||||
@ -378,13 +375,6 @@ export class ToolService {
|
||||
|
||||
const createdRecord = await repository.save(parameters);
|
||||
|
||||
await this.emitDatabaseEvent({
|
||||
objectName,
|
||||
action: DatabaseEventAction.CREATED,
|
||||
records: [createdRecord],
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
record: createdRecord,
|
||||
@ -449,14 +439,6 @@ export class ToolService {
|
||||
};
|
||||
}
|
||||
|
||||
await this.emitDatabaseEvent({
|
||||
objectName,
|
||||
action: DatabaseEventAction.UPDATED,
|
||||
records: [updatedRecord],
|
||||
workspaceId,
|
||||
beforeRecords: [existingRecord],
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
record: updatedRecord,
|
||||
@ -509,13 +491,6 @@ export class ToolService {
|
||||
|
||||
await repository.softDelete(id);
|
||||
|
||||
await this.emitDatabaseEvent({
|
||||
objectName,
|
||||
action: DatabaseEventAction.DELETED,
|
||||
records: [existingRecord],
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Successfully soft deleted ${objectName}`,
|
||||
@ -567,13 +542,6 @@ export class ToolService {
|
||||
|
||||
await repository.remove(existingRecord);
|
||||
|
||||
await this.emitDatabaseEvent({
|
||||
objectName,
|
||||
action: DatabaseEventAction.DESTROYED,
|
||||
records: [existingRecord],
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Successfully destroyed ${objectName}`,
|
||||
@ -636,13 +604,6 @@ export class ToolService {
|
||||
|
||||
await repository.softDelete({ id: { in: recordIds } });
|
||||
|
||||
await this.emitDatabaseEvent({
|
||||
objectName,
|
||||
action: DatabaseEventAction.DELETED,
|
||||
records: existingRecords,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
count: existingRecords.length,
|
||||
@ -706,13 +667,6 @@ export class ToolService {
|
||||
|
||||
await repository.delete({ id: { in: recordIds } });
|
||||
|
||||
await this.emitDatabaseEvent({
|
||||
objectName,
|
||||
action: DatabaseEventAction.DESTROYED,
|
||||
records: existingRecords,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
count: existingRecords.length,
|
||||
@ -726,53 +680,4 @@ export class ToolService {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private async emitDatabaseEvent({
|
||||
objectName,
|
||||
action,
|
||||
records,
|
||||
workspaceId,
|
||||
beforeRecords,
|
||||
}: {
|
||||
objectName: string;
|
||||
action: DatabaseEventAction;
|
||||
records: Record<string, unknown>[];
|
||||
workspaceId: string;
|
||||
beforeRecords?: Record<string, unknown>[];
|
||||
}) {
|
||||
const objectMetadata =
|
||||
await this.objectMetadataService.findOneWithinWorkspace(workspaceId, {
|
||||
where: {
|
||||
nameSingular: objectName,
|
||||
isActive: true,
|
||||
},
|
||||
relations: ['fields'],
|
||||
});
|
||||
|
||||
if (!objectMetadata) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
||||
objectMetadataNameSingular: objectName,
|
||||
action,
|
||||
events: records.map((record) => {
|
||||
const beforeRecord = beforeRecords?.find((r) => r.id === record.id);
|
||||
|
||||
return {
|
||||
recordId: record.id as string,
|
||||
objectMetadata,
|
||||
properties: {
|
||||
before: beforeRecord || undefined,
|
||||
after:
|
||||
action === DatabaseEventAction.DELETED ||
|
||||
action === DatabaseEventAction.DESTROYED
|
||||
? undefined
|
||||
: (record as Record<string, unknown>),
|
||||
},
|
||||
};
|
||||
}),
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
import {
|
||||
CalendarChannelVisibility,
|
||||
CalendarChannelWorkspaceEntity,
|
||||
@ -26,9 +21,6 @@ export type CreateCalendarChannelInput = {
|
||||
export class CreateCalendarChannelService {
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
) {}
|
||||
|
||||
async createCalendarChannel(
|
||||
@ -60,26 +52,6 @@ export class CreateCalendarChannelService {
|
||||
manager,
|
||||
);
|
||||
|
||||
const calendarChannelMetadata =
|
||||
await this.objectMetadataRepository.findOneOrFail({
|
||||
where: { nameSingular: 'calendarChannel', workspaceId },
|
||||
});
|
||||
|
||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
||||
objectMetadataNameSingular: 'calendarChannel',
|
||||
action: DatabaseEventAction.CREATED,
|
||||
events: [
|
||||
{
|
||||
recordId: newCalendarChannel.id,
|
||||
objectMetadata: calendarChannelMetadata,
|
||||
properties: {
|
||||
after: newCalendarChannel,
|
||||
},
|
||||
},
|
||||
],
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return newCalendarChannel.id;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
|
||||
export type CreateConnectedAccountInput = {
|
||||
@ -27,9 +22,6 @@ export type CreateConnectedAccountInput = {
|
||||
export class CreateConnectedAccountService {
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
) {}
|
||||
|
||||
async createConnectedAccount(
|
||||
@ -53,7 +45,7 @@ export class CreateConnectedAccountService {
|
||||
'connectedAccount',
|
||||
);
|
||||
|
||||
const newConnectedAccount = await connectedAccountRepository.save(
|
||||
await connectedAccountRepository.save(
|
||||
{
|
||||
id: connectedAccountId,
|
||||
handle,
|
||||
@ -66,25 +58,5 @@ export class CreateConnectedAccountService {
|
||||
{},
|
||||
manager,
|
||||
);
|
||||
|
||||
const connectedAccountMetadata =
|
||||
await this.objectMetadataRepository.findOneOrFail({
|
||||
where: { nameSingular: 'connectedAccount', workspaceId },
|
||||
});
|
||||
|
||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
||||
objectMetadataNameSingular: 'connectedAccount',
|
||||
action: DatabaseEventAction.CREATED,
|
||||
events: [
|
||||
{
|
||||
recordId: newConnectedAccount.id,
|
||||
objectMetadata: connectedAccountMetadata,
|
||||
properties: {
|
||||
after: newConnectedAccount,
|
||||
},
|
||||
},
|
||||
],
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,14 +1,9 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
import {
|
||||
MessageChannelSyncStatus,
|
||||
MessageChannelType,
|
||||
@ -28,9 +23,6 @@ export type CreateMessageChannelInput = {
|
||||
export class CreateMessageChannelService {
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
) {}
|
||||
|
||||
async createMessageChannel(
|
||||
@ -64,26 +56,6 @@ export class CreateMessageChannelService {
|
||||
manager,
|
||||
);
|
||||
|
||||
const messageChannelMetadata =
|
||||
await this.objectMetadataRepository.findOneOrFail({
|
||||
where: { nameSingular: 'messageChannel', workspaceId },
|
||||
});
|
||||
|
||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
||||
objectMetadataNameSingular: 'messageChannel',
|
||||
action: DatabaseEventAction.CREATED,
|
||||
events: [
|
||||
{
|
||||
recordId: newMessageChannel.id,
|
||||
objectMetadata: messageChannelMetadata,
|
||||
properties: {
|
||||
after: newMessageChannel,
|
||||
},
|
||||
},
|
||||
],
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return newMessageChannel.id;
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,7 +16,6 @@ import { getQueueToken } from 'src/engine/core-modules/message-queue/utils/get-q
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
import {
|
||||
CalendarChannelSyncStage,
|
||||
CalendarChannelVisibility,
|
||||
@ -161,12 +160,6 @@ describe('GoogleAPIsService', () => {
|
||||
removeAccountToReconnect: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: WorkspaceEventEmitter,
|
||||
useValue: {
|
||||
emitDatabaseBatchEvent: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: getQueueToken(MessageQueue.messagingQueue),
|
||||
useValue: mockMessageQueueService,
|
||||
|
||||
@ -17,7 +17,6 @@ import { getQueueToken } from 'src/engine/core-modules/message-queue/utils/get-q
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
import {
|
||||
CalendarChannelSyncStage,
|
||||
CalendarChannelVisibility,
|
||||
@ -166,12 +165,6 @@ describe('MicrosoftAPIsService', () => {
|
||||
removeAccountToReconnect: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: WorkspaceEventEmitter,
|
||||
useValue: {
|
||||
emitDatabaseBatchEvent: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: getQueueToken(MessageQueue.messagingQueue),
|
||||
useValue: mockMessageQueueService,
|
||||
|
||||
@ -1,13 +1,7 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
import {
|
||||
CalendarChannelSyncStage,
|
||||
CalendarChannelWorkspaceEntity,
|
||||
@ -23,9 +17,6 @@ export type ResetCalendarChannelsInput = {
|
||||
export class ResetCalendarChannelService {
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
) {}
|
||||
|
||||
async resetCalendarChannels(
|
||||
@ -39,11 +30,7 @@ export class ResetCalendarChannelService {
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
const calendarChannels = await calendarChannelRepository.find({
|
||||
where: { connectedAccountId },
|
||||
});
|
||||
|
||||
const calendarChannelUpdates = await calendarChannelRepository.update(
|
||||
await calendarChannelRepository.update(
|
||||
{
|
||||
connectedAccountId,
|
||||
},
|
||||
@ -57,28 +44,6 @@ export class ResetCalendarChannelService {
|
||||
manager,
|
||||
);
|
||||
|
||||
const calendarChannelMetadata =
|
||||
await this.objectMetadataRepository.findOneOrFail({
|
||||
where: { nameSingular: 'calendarChannel', workspaceId },
|
||||
});
|
||||
|
||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
||||
objectMetadataNameSingular: 'calendarChannel',
|
||||
action: DatabaseEventAction.UPDATED,
|
||||
events: calendarChannels.map((calendarChannel) => ({
|
||||
recordId: calendarChannel.id,
|
||||
objectMetadata: calendarChannelMetadata,
|
||||
properties: {
|
||||
before: calendarChannel,
|
||||
after: {
|
||||
...calendarChannel,
|
||||
...calendarChannelUpdates.raw[0],
|
||||
},
|
||||
},
|
||||
})),
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,13 +1,7 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
import {
|
||||
MessageChannelSyncStage,
|
||||
MessageChannelSyncStatus,
|
||||
@ -24,9 +18,6 @@ export type ResetMessageChannelsInput = {
|
||||
export class ResetMessageChannelService {
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
) {}
|
||||
|
||||
async resetMessageChannels(input: ResetMessageChannelsInput): Promise<void> {
|
||||
@ -38,11 +29,7 @@ export class ResetMessageChannelService {
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
const messageChannels = await messageChannelRepository.find({
|
||||
where: { connectedAccountId },
|
||||
});
|
||||
|
||||
const messageChannelUpdates = await messageChannelRepository.update(
|
||||
await messageChannelRepository.update(
|
||||
{
|
||||
connectedAccountId,
|
||||
},
|
||||
@ -55,25 +42,6 @@ export class ResetMessageChannelService {
|
||||
manager,
|
||||
);
|
||||
|
||||
const messageChannelMetadata =
|
||||
await this.objectMetadataRepository.findOneOrFail({
|
||||
where: { nameSingular: 'messageChannel', workspaceId },
|
||||
});
|
||||
|
||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
||||
objectMetadataNameSingular: 'messageChannel',
|
||||
action: DatabaseEventAction.UPDATED,
|
||||
events: messageChannels.map((messageChannel) => ({
|
||||
recordId: messageChannel.id,
|
||||
objectMetadata: messageChannelMetadata,
|
||||
properties: {
|
||||
before: messageChannel,
|
||||
after: { ...messageChannel, ...messageChannelUpdates.raw[0] },
|
||||
},
|
||||
})),
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,13 +1,7 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
|
||||
export type UpdateConnectedAccountOnReconnectInput = {
|
||||
@ -24,9 +18,6 @@ export type UpdateConnectedAccountOnReconnectInput = {
|
||||
export class UpdateConnectedAccountOnReconnectService {
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
) {}
|
||||
|
||||
async updateConnectedAccountOnReconnect(
|
||||
@ -38,7 +29,6 @@ export class UpdateConnectedAccountOnReconnectService {
|
||||
accessToken,
|
||||
refreshToken,
|
||||
scopes,
|
||||
connectedAccount,
|
||||
manager,
|
||||
} = input;
|
||||
|
||||
@ -48,7 +38,7 @@ export class UpdateConnectedAccountOnReconnectService {
|
||||
'connectedAccount',
|
||||
);
|
||||
|
||||
const updatedConnectedAccount = await connectedAccountRepository.update(
|
||||
await connectedAccountRepository.update(
|
||||
{
|
||||
id: connectedAccountId,
|
||||
},
|
||||
@ -60,29 +50,5 @@ export class UpdateConnectedAccountOnReconnectService {
|
||||
},
|
||||
manager,
|
||||
);
|
||||
|
||||
const connectedAccountMetadata =
|
||||
await this.objectMetadataRepository.findOneOrFail({
|
||||
where: { nameSingular: 'connectedAccount', workspaceId },
|
||||
});
|
||||
|
||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
||||
objectMetadataNameSingular: 'connectedAccount',
|
||||
action: DatabaseEventAction.UPDATED,
|
||||
events: [
|
||||
{
|
||||
recordId: connectedAccountId,
|
||||
objectMetadata: connectedAccountMetadata,
|
||||
properties: {
|
||||
before: connectedAccount,
|
||||
after: {
|
||||
...connectedAccount,
|
||||
...updatedConnectedAccount.raw[0],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||
|
||||
import { objectRecordChangedValues } from 'src/engine/core-modules/event-emitter/utils/object-record-changed-values';
|
||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
|
||||
const mockObjectMetadata: ObjectMetadataInterface = {
|
||||
const mockObjectMetadata: ObjectMetadataItemWithFieldMaps = {
|
||||
id: '1',
|
||||
icon: 'Icon123',
|
||||
nameSingular: 'Object',
|
||||
@ -12,14 +11,16 @@ const mockObjectMetadata: ObjectMetadataInterface = {
|
||||
description: 'Test object metadata',
|
||||
targetTableName: 'test_table',
|
||||
workspaceId: '1',
|
||||
fields: [],
|
||||
indexMetadatas: [],
|
||||
fieldsById: {},
|
||||
fieldIdByName: {},
|
||||
isSystem: false,
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
isRemote: false,
|
||||
isAuditLogged: true,
|
||||
isSearchable: true,
|
||||
indexMetadatas: [],
|
||||
fieldIdByJoinColumnName: {},
|
||||
};
|
||||
|
||||
describe('objectRecordChangedValues', () => {
|
||||
@ -38,7 +39,6 @@ describe('objectRecordChangedValues', () => {
|
||||
const result = objectRecordChangedValues(
|
||||
oldRecord,
|
||||
newRecord,
|
||||
['name'],
|
||||
mockObjectMetadata,
|
||||
);
|
||||
|
||||
@ -60,7 +60,6 @@ describe('objectRecordChangedValues', () => {
|
||||
const result = objectRecordChangedValues(
|
||||
oldRecord,
|
||||
newRecord,
|
||||
[],
|
||||
mockObjectMetadata,
|
||||
);
|
||||
|
||||
@ -82,7 +81,6 @@ describe('objectRecordChangedValues', () => {
|
||||
const result = objectRecordChangedValues(
|
||||
oldRecord,
|
||||
newRecord,
|
||||
['name', 'value'],
|
||||
mockObjectMetadata,
|
||||
);
|
||||
|
||||
@ -112,7 +110,6 @@ describe('objectRecordChangedValues', () => {
|
||||
const result = objectRecordChangedValues(
|
||||
oldRecord,
|
||||
newRecord,
|
||||
['name', 'config', 'status'],
|
||||
mockObjectMetadata,
|
||||
);
|
||||
|
||||
|
||||
@ -2,23 +2,25 @@ import deepEqual from 'deep-equal';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
||||
|
||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
|
||||
export const objectRecordChangedValues = (
|
||||
oldRecord: Partial<ObjectRecord>,
|
||||
newRecord: Partial<ObjectRecord>,
|
||||
updatedKeys: string[] | undefined,
|
||||
objectMetadataItem: ObjectMetadataInterface,
|
||||
objectMetadataItem: ObjectMetadataItemWithFieldMaps,
|
||||
) => {
|
||||
return Object.keys(newRecord).reduce(
|
||||
(acc, key) => {
|
||||
const field = objectMetadataItem.fields.find((f) => f.name === key);
|
||||
const field =
|
||||
objectMetadataItem.fieldsById[objectMetadataItem.fieldIdByName[key]];
|
||||
|
||||
const oldRecordValue = oldRecord[key];
|
||||
const newRecordValue = newRecord[key];
|
||||
|
||||
if (
|
||||
key === 'updatedAt' ||
|
||||
!updatedKeys?.includes(key) ||
|
||||
key === 'searchVector' ||
|
||||
field?.type === FieldMetadataType.RELATION ||
|
||||
deepEqual(oldRecordValue, newRecordValue)
|
||||
) {
|
||||
|
||||
@ -6,7 +6,6 @@ import { DataSource, Repository } from 'typeorm';
|
||||
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
||||
|
||||
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
import { ApprovedAccessDomain } from 'src/engine/core-modules/approved-access-domain/approved-access-domain.entity';
|
||||
import { ApprovedAccessDomainService } from 'src/engine/core-modules/approved-access-domain/services/approved-access-domain.service';
|
||||
import { AuthException } from 'src/engine/core-modules/auth/auth.exception';
|
||||
@ -28,17 +27,14 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat
|
||||
import { PermissionsException } from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
|
||||
describe('UserWorkspaceService', () => {
|
||||
let service: UserWorkspaceService;
|
||||
let userWorkspaceRepository: Repository<UserWorkspace>;
|
||||
let userRepository: Repository<User>;
|
||||
let objectMetadataRepository: Repository<ObjectMetadataEntity>;
|
||||
let typeORMService: TypeORMService;
|
||||
let workspaceInvitationService: WorkspaceInvitationService;
|
||||
let workspaceEventEmitter: WorkspaceEventEmitter;
|
||||
let approvedAccessDomainService: ApprovedAccessDomainService;
|
||||
let twentyORMGlobalManager: TwentyORMGlobalManager;
|
||||
let userRoleService: UserRoleService;
|
||||
@ -92,13 +88,6 @@ describe('UserWorkspaceService', () => {
|
||||
findInvitationsByEmail: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: WorkspaceEventEmitter,
|
||||
useValue: {
|
||||
emitCustomBatchEvent: jest.fn(),
|
||||
emitDatabaseBatchEvent: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: DomainManagerService,
|
||||
useValue: {
|
||||
@ -155,16 +144,10 @@ describe('UserWorkspaceService', () => {
|
||||
getRepositoryToken(UserWorkspace, 'core'),
|
||||
);
|
||||
userRepository = module.get(getRepositoryToken(User, 'core'));
|
||||
objectMetadataRepository = module.get(
|
||||
getRepositoryToken(ObjectMetadataEntity, 'core'),
|
||||
);
|
||||
typeORMService = module.get<TypeORMService>(TypeORMService);
|
||||
workspaceInvitationService = module.get<WorkspaceInvitationService>(
|
||||
WorkspaceInvitationService,
|
||||
);
|
||||
workspaceEventEmitter = module.get<WorkspaceEventEmitter>(
|
||||
WorkspaceEventEmitter,
|
||||
);
|
||||
approvedAccessDomainService = module.get<ApprovedAccessDomainService>(
|
||||
ApprovedAccessDomainService,
|
||||
);
|
||||
@ -329,9 +312,6 @@ describe('UserWorkspaceService', () => {
|
||||
userEmail: 'test@example.com',
|
||||
},
|
||||
];
|
||||
const objectMetadata = {
|
||||
nameSingular: 'workspaceMember',
|
||||
} as ObjectMetadataEntity;
|
||||
const workspaceMemberRepository = {
|
||||
insert: jest.fn(),
|
||||
find: jest.fn().mockResolvedValue(workspaceMember),
|
||||
@ -344,13 +324,6 @@ describe('UserWorkspaceService', () => {
|
||||
.spyOn(mainDataSource, 'query')
|
||||
.mockResolvedValueOnce(undefined)
|
||||
.mockResolvedValueOnce(workspaceMember);
|
||||
jest
|
||||
.spyOn(objectMetadataRepository, 'findOneOrFail')
|
||||
.mockResolvedValue(objectMetadata);
|
||||
jest
|
||||
.spyOn(workspaceEventEmitter, 'emitDatabaseBatchEvent')
|
||||
.mockImplementation();
|
||||
|
||||
jest
|
||||
.spyOn(twentyORMGlobalManager, 'getRepositoryForWorkspace')
|
||||
.mockResolvedValue(workspaceMemberRepository as any);
|
||||
@ -372,28 +345,6 @@ describe('UserWorkspaceService', () => {
|
||||
locale: 'en',
|
||||
avatarUrl: 'userWorkspace-avatar-url',
|
||||
});
|
||||
expect(objectMetadataRepository.findOneOrFail).toHaveBeenCalledWith({
|
||||
where: {
|
||||
nameSingular: 'workspaceMember',
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
expect(workspaceEventEmitter.emitDatabaseBatchEvent).toHaveBeenCalledWith(
|
||||
{
|
||||
objectMetadataNameSingular: 'workspaceMember',
|
||||
action: DatabaseEventAction.CREATED,
|
||||
events: [
|
||||
{
|
||||
recordId: workspaceMember[0].id,
|
||||
objectMetadata,
|
||||
properties: {
|
||||
after: workspaceMember[0],
|
||||
},
|
||||
},
|
||||
],
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -9,7 +9,6 @@ import { IsNull, Not, Repository } from 'typeorm';
|
||||
import { FileStorageExceptionCode } from 'src/engine/core-modules/file-storage/interfaces/file-storage-exception';
|
||||
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
||||
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
import { AppToken } from 'src/engine/core-modules/app-token/app-token.entity';
|
||||
import { ApprovedAccessDomainService } from 'src/engine/core-modules/approved-access-domain/services/approved-access-domain.service';
|
||||
import {
|
||||
@ -27,7 +26,6 @@ import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-in
|
||||
import { AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import {
|
||||
PermissionsException,
|
||||
PermissionsExceptionCode,
|
||||
@ -35,7 +33,6 @@ import {
|
||||
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
import { assert } from 'src/utils/assert';
|
||||
import { getDomainNameByEmail } from 'src/utils/get-domain-name-by-email';
|
||||
@ -46,10 +43,7 @@ export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> {
|
||||
private readonly userWorkspaceRepository: Repository<UserWorkspace>,
|
||||
@InjectRepository(User, 'core')
|
||||
private readonly userRepository: Repository<User>,
|
||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
private readonly workspaceInvitationService: WorkspaceInvitationService,
|
||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
private readonly loginTokenService: LoginTokenService,
|
||||
private readonly approvedAccessDomainService: ApprovedAccessDomainService,
|
||||
@ -124,27 +118,6 @@ export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> {
|
||||
workspaceMember?.length === 1,
|
||||
`Error while creating workspace member ${user.email} on workspace ${workspaceId}`,
|
||||
);
|
||||
const objectMetadata = await this.objectMetadataRepository.findOneOrFail({
|
||||
where: {
|
||||
nameSingular: 'workspaceMember',
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
||||
objectMetadataNameSingular: 'workspaceMember',
|
||||
action: DatabaseEventAction.CREATED,
|
||||
events: [
|
||||
{
|
||||
recordId: workspaceMember[0].id,
|
||||
objectMetadata,
|
||||
properties: {
|
||||
after: workspaceMember[0],
|
||||
},
|
||||
},
|
||||
],
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
async addUserToWorkspaceIfUserNotInWorkspace(
|
||||
|
||||
@ -6,17 +6,14 @@ import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||
import { isWorkspaceActiveOrSuspended } from 'twenty-shared/workspace';
|
||||
import { IsNull, Not, Repository } from 'typeorm';
|
||||
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { userValidator } from 'src/engine/core-modules/user/user.validate';
|
||||
import { WorkspaceService } from 'src/engine/core-modules/workspace/services/workspace.service';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import {
|
||||
PermissionsException,
|
||||
PermissionsExceptionCode,
|
||||
@ -24,7 +21,6 @@ import {
|
||||
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
|
||||
// eslint-disable-next-line @nx/workspace-inject-workspace-repository
|
||||
@ -32,13 +28,9 @@ export class UserService extends TypeOrmQueryService<User> {
|
||||
constructor(
|
||||
@InjectRepository(User, 'core')
|
||||
private readonly userRepository: Repository<User>,
|
||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||
private readonly workspaceService: WorkspaceService,
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly userRoleService: UserRoleService,
|
||||
private readonly userWorkspaceService: UserWorkspaceService,
|
||||
) {
|
||||
super(userRepository);
|
||||
}
|
||||
@ -157,38 +149,14 @@ export class UserService extends TypeOrmQueryService<User> {
|
||||
workspaceId,
|
||||
workspaceMemberRepository,
|
||||
workspaceMembers,
|
||||
workspaceMember,
|
||||
}) => {
|
||||
await workspaceMemberRepository.delete({ userId });
|
||||
|
||||
const objectMetadata =
|
||||
await this.objectMetadataRepository.findOneOrFail({
|
||||
where: {
|
||||
nameSingular: 'workspaceMember',
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
if (workspaceMembers.length === 1) {
|
||||
await this.workspaceService.deleteWorkspace(workspaceId);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
||||
objectMetadataNameSingular: 'workspaceMember',
|
||||
action: DatabaseEventAction.DELETED,
|
||||
events: [
|
||||
{
|
||||
recordId: workspaceMember.id,
|
||||
objectMetadata,
|
||||
properties: {
|
||||
before: workspaceMember,
|
||||
},
|
||||
},
|
||||
],
|
||||
workspaceId,
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user