[messaging] clean orphan threads and messages after connected account deletion (#4195)

* [messaging] add connected account associated data delete

* add threadCleanerService

* fix

* fix import

* add thread cleaner import

* remove log
This commit is contained in:
Weiko 2024-02-26 21:29:44 +01:00 committed by GitHub
parent 6a1abba9ea
commit 214807588a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 326 additions and 116 deletions

View File

@ -24,8 +24,10 @@ import { CreateCompaniesAndContactsAfterSyncJob } from 'src/workspace/messaging/
import { CreateCompaniesAndContactsModule } from 'src/workspace/messaging/services/create-companies-and-contacts/create-companies-and-contacts.module';
import { MessageChannelModule } from 'src/workspace/messaging/repositories/message-channel/message-channel.module';
import { MessageParticipantModule } from 'src/workspace/messaging/repositories/message-participant/message-participant.module';
import { DataSeedDemoWorkspaceJob } from 'src/database/commands/data-seed-demo-workspace/jobs/data-seed-demo-workspace.job';
import { DataSeedDemoWorkspaceModule } from 'src/database/commands/data-seed-demo-workspace/data-seed-demo-workspace.module';
import { DataSeedDemoWorkspaceJob } from 'src/database/commands/data-seed-demo-workspace/jobs/data-seed-demo-workspace.job';
import { DeleteConnectedAccountAssociatedDataJob } from 'src/workspace/messaging/jobs/delete-connected-acount-associated-data.job';
import { ThreadCleanerModule } from 'src/workspace/messaging/services/thread-cleaner/thread-cleaner.module';
@Module({
imports: [
@ -44,6 +46,7 @@ import { DataSeedDemoWorkspaceModule } from 'src/database/commands/data-seed-dem
CreateCompaniesAndContactsModule,
MessageChannelModule,
DataSeedDemoWorkspaceModule,
ThreadCleanerModule,
],
providers: [
{
@ -83,6 +86,10 @@ import { DataSeedDemoWorkspaceModule } from 'src/database/commands/data-seed-dem
provide: DataSeedDemoWorkspaceJob.name,
useClass: DataSeedDemoWorkspaceJob,
},
{
provide: DeleteConnectedAccountAssociatedDataJob.name,
useClass: DeleteConnectedAccountAssociatedDataJob,
},
],
})
export class JobsModule {

View File

@ -24,6 +24,7 @@ export enum RelationOnDeleteAction {
CASCADE = 'CASCADE',
RESTRICT = 'RESTRICT',
SET_NULL = 'SET_NULL',
NO_ACTION = 'NO_ACTION',
}
@Entity('relationMetadata')

View File

@ -0,0 +1,35 @@
import { Injectable, Logger } from '@nestjs/common';
import { MessageQueueJob } from 'src/integrations/message-queue/interfaces/message-queue-job.interface';
import { ThreadCleanerService } from 'src/workspace/messaging/services/thread-cleaner/thread-cleaner.service';
export type DeleteConnectedAccountAssociatedDataJobData = {
workspaceId: string;
connectedAccountId: string;
};
@Injectable()
export class DeleteConnectedAccountAssociatedDataJob
implements MessageQueueJob<DeleteConnectedAccountAssociatedDataJobData>
{
private readonly logger = new Logger(
DeleteConnectedAccountAssociatedDataJob.name,
);
constructor(private readonly threadCleanerService: ThreadCleanerService) {}
async handle(
data: DeleteConnectedAccountAssociatedDataJobData,
): Promise<void> {
this.logger.log(
`Deleting connected account ${data.connectedAccountId} associated data in workspace ${data.workspaceId}`,
);
await this.threadCleanerService.cleanWorkspaceThreads(data.workspaceId);
this.logger.log(
`Deleted connected account ${data.connectedAccountId} associated data in workspace ${data.workspaceId}`,
);
}
}

View File

@ -1,40 +0,0 @@
import { Injectable, Logger } from '@nestjs/common';
import { MessageQueueJob } from 'src/integrations/message-queue/interfaces/message-queue-job.interface';
import { MessageChannelMessageAssociationService } from 'src/workspace/messaging/repositories/message-channel-message-association/message-channel-message-association.service';
export type DeleteMessageChannelMessageAssociationJobData = {
workspaceId: string;
messageChannelId: string;
};
@Injectable()
export class DeleteMessageChannelMessageAssociationJob
implements MessageQueueJob<DeleteMessageChannelMessageAssociationJobData>
{
private readonly logger = new Logger(
DeleteMessageChannelMessageAssociationJob.name,
);
constructor(
private readonly messageChannelMessageAssociationService: MessageChannelMessageAssociationService,
) {}
async handle(
data: DeleteMessageChannelMessageAssociationJobData,
): Promise<void> {
this.logger.log(
`Deleting message channel message association for message channel ${data.messageChannelId} in workspace ${data.workspaceId}`,
);
await this.messageChannelMessageAssociationService.deleteByMessageChannelId(
data.messageChannelId,
data.workspaceId,
);
this.logger.log(
`Deleted message channel message association for message channel ${data.messageChannelId} in workspace ${data.workspaceId}`,
);
}
}

View File

@ -1,41 +0,0 @@
import { Injectable, Inject } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { ObjectRecordUpdateEvent } from 'src/integrations/event-emitter/types/object-record-update.event';
import { MessageQueue } from 'src/integrations/message-queue/message-queue.constants';
import { MessageQueueService } from 'src/integrations/message-queue/services/message-queue.service';
import { MessageChannelObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-channel.object-metadata';
import { objectRecordChangedProperties as objectRecordUpdateEventChangedProperties } from 'src/integrations/event-emitter/utils/object-record-changed-properties.util';
import {
CreateCompaniesAndContactsAfterSyncJob,
CreateCompaniesAndContactsAfterSyncJobData,
} from 'src/workspace/messaging/jobs/create-companies-and-contacts-after-sync.job';
@Injectable()
export class IsContactAutoCreationEnabledListener {
constructor(
@Inject(MessageQueue.messagingQueue)
private readonly messageQueueService: MessageQueueService,
) {}
@OnEvent('messageChannel.updated')
handleUpdatedEvent(
payload: ObjectRecordUpdateEvent<MessageChannelObjectMetadata>,
) {
if (
objectRecordUpdateEventChangedProperties(
payload.previousRecord,
payload.updatedRecord,
).includes('isContactAutoCreationEnabled') &&
payload.updatedRecord.isContactAutoCreationEnabled
) {
this.messageQueueService.add<CreateCompaniesAndContactsAfterSyncJobData>(
CreateCompaniesAndContactsAfterSyncJob.name,
{
workspaceId: payload.workspaceId,
messageChannelId: payload.updatedRecord.id,
},
);
}
}
}

View File

@ -0,0 +1,32 @@
import { Injectable, Inject } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { ObjectRecordDeleteEvent } from 'src/integrations/event-emitter/types/object-record-delete.event';
import { MessageQueue } from 'src/integrations/message-queue/message-queue.constants';
import { MessageQueueService } from 'src/integrations/message-queue/services/message-queue.service';
import {
DeleteConnectedAccountAssociatedDataJobData,
DeleteConnectedAccountAssociatedDataJob,
} from 'src/workspace/messaging/jobs/delete-connected-acount-associated-data.job';
import { ConnectedAccountObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/connected-account.object-metadata';
@Injectable()
export class MessagingConnectedAccountListener {
constructor(
@Inject(MessageQueue.messagingQueue)
private readonly messageQueueService: MessageQueueService,
) {}
@OnEvent('connectedAccount.deleted')
handleDeletedEvent(
payload: ObjectRecordDeleteEvent<ConnectedAccountObjectMetadata>,
) {
this.messageQueueService.add<DeleteConnectedAccountAssociatedDataJobData>(
DeleteConnectedAccountAssociatedDataJob.name,
{
workspaceId: payload.workspaceId,
connectedAccountId: payload.deletedRecord.id,
},
);
}
}

View File

@ -1,13 +1,14 @@
import { Inject, Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { ObjectRecordDeleteEvent } from 'src/integrations/event-emitter/types/object-record-delete.event';
import { ObjectRecordUpdateEvent } from 'src/integrations/event-emitter/types/object-record-update.event';
import { objectRecordChangedProperties } from 'src/integrations/event-emitter/utils/object-record-changed-properties.util';
import { MessageQueue } from 'src/integrations/message-queue/message-queue.constants';
import { MessageQueueService } from 'src/integrations/message-queue/services/message-queue.service';
import {
DeleteMessageChannelMessageAssociationJob,
DeleteMessageChannelMessageAssociationJobData,
} from 'src/workspace/messaging/jobs/delete-message-channel-message-association.job';
CreateCompaniesAndContactsAfterSyncJobData,
CreateCompaniesAndContactsAfterSyncJob,
} from 'src/workspace/messaging/jobs/create-companies-and-contacts-after-sync.job';
import { MessageChannelObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-channel.object-metadata';
@Injectable()
@ -17,16 +18,24 @@ export class MessagingMessageChannelListener {
private readonly messageQueueService: MessageQueueService,
) {}
@OnEvent('messageChannel.deleted')
handleDeletedEvent(
payload: ObjectRecordDeleteEvent<MessageChannelObjectMetadata>,
@OnEvent('messageChannel.updated')
handleUpdatedEvent(
payload: ObjectRecordUpdateEvent<MessageChannelObjectMetadata>,
) {
this.messageQueueService.add<DeleteMessageChannelMessageAssociationJobData>(
DeleteMessageChannelMessageAssociationJob.name,
if (
objectRecordChangedProperties(
payload.previousRecord,
payload.updatedRecord,
).includes('isContactAutoCreationEnabled') &&
payload.updatedRecord.isContactAutoCreationEnabled
) {
this.messageQueueService.add<CreateCompaniesAndContactsAfterSyncJobData>(
CreateCompaniesAndContactsAfterSyncJob.name,
{
workspaceId: payload.workspaceId,
messageChannelId: payload.deletedRecord.id,
messageChannelId: payload.updatedRecord.id,
},
);
}
}
}

View File

@ -18,7 +18,6 @@ import { GmailRefreshAccessTokenService } from 'src/workspace/messaging/services
import { WorkspaceDataSourceModule } from 'src/workspace/workspace-datasource/workspace-datasource.module';
import { MessageParticipantModule } from 'src/workspace/messaging/repositories/message-participant/message-participant.module';
import { MessagingWorkspaceMemberListener } from 'src/workspace/messaging/listeners/messaging-workspace-member.listener';
import { IsContactAutoCreationEnabledListener } from 'src/workspace/messaging/listeners/is-contact-auto-creation-enabled-listener';
import { MessagingMessageChannelListener } from 'src/workspace/messaging/listeners/messaging-message-channel.listener';
import { MessageService } from 'src/workspace/messaging/repositories/message/message.service';
import { WorkspaceMemberModule } from 'src/workspace/messaging/repositories/workspace-member/workspace-member.module';
@ -26,6 +25,7 @@ import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
import { CreateCompaniesAndContactsModule } from 'src/workspace/messaging/services/create-companies-and-contacts/create-companies-and-contacts.module';
import { CompanyModule } from 'src/workspace/messaging/repositories/company/company.module';
import { PersonModule } from 'src/workspace/messaging/repositories/person/person.module';
import { MessagingConnectedAccountListener } from 'src/workspace/messaging/listeners/messaging-connected-account.listener';
@Module({
imports: [
EnvironmentModule,
@ -52,9 +52,9 @@ import { PersonModule } from 'src/workspace/messaging/repositories/person/person
CreateCompanyService,
MessagingPersonListener,
MessagingWorkspaceMemberListener,
IsContactAutoCreationEnabledListener,
MessagingMessageChannelListener,
MessageService,
MessagingConnectedAccountListener,
],
exports: [
GmailPartialSyncService,

View File

@ -4,6 +4,13 @@ import { EntityManager } from 'typeorm';
import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service';
export type CompanyToCreate = {
id: string;
domainName: string;
name?: string;
city?: string;
};
// TODO: Move outside of the messaging module
@Injectable()
export class CompanyService {
@ -31,20 +38,22 @@ export class CompanyService {
}
public async createCompany(
id: string,
name: string,
domainName: string,
city: string,
workspaceId: string,
companyToCreate: CompanyToCreate,
transactionManager?: EntityManager,
): Promise<void> {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
await this.workspaceDataSourceService.executeRawQuery(
`INSERT INTO ${dataSourceSchema}.company (id, name, "domainName", address)
`INSERT INTO ${dataSourceSchema}.company (id, "domainName", name, address)
VALUES ($1, $2, $3, $4)`,
[id, name, domainName, city],
[
companyToCreate.id,
companyToCreate.domainName,
companyToCreate.name ?? '',
companyToCreate.city ?? '',
],
workspaceId,
transactionManager,
);

View File

@ -67,17 +67,50 @@ export class MessageChannelMessageAssociationService {
);
}
public async getByMessageChannelIds(
messageChannelIds: string[],
workspaceId: string,
transactionManager?: EntityManager,
): Promise<ObjectRecord<MessageChannelMessageAssociationObjectMetadata>[]> {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
return await this.workspaceDataSourceService.executeRawQuery(
`SELECT * FROM ${dataSourceSchema}."messageChannelMessageAssociation"
WHERE "messageChannelId" = ANY($1)`,
[messageChannelIds],
workspaceId,
transactionManager,
);
}
public async deleteByMessageChannelId(
messageChannelId: string,
workspaceId: string,
transactionManager?: EntityManager,
) {
this.deleteByMessageChannelIds(
[messageChannelId],
workspaceId,
transactionManager,
);
}
public async deleteByMessageChannelIds(
messageChannelIds: string[],
workspaceId: string,
transactionManager?: EntityManager,
) {
if (messageChannelIds.length === 0) {
return;
}
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
await this.workspaceDataSourceService.executeRawQuery(
`DELETE FROM ${dataSourceSchema}."messageChannelMessageAssociation" WHERE "messageChannelId" = $1`,
[messageChannelId],
`DELETE FROM ${dataSourceSchema}."messageChannelMessageAssociation" WHERE "messageChannelId" = ANY($1)`,
[messageChannelIds],
workspaceId,
transactionManager,
);

View File

@ -6,6 +6,8 @@ import { v4 } from 'uuid';
import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service';
import { DataSourceEntity } from 'src/metadata/data-source/data-source.entity';
import { MessageChannelMessageAssociationService } from 'src/workspace/messaging/repositories/message-channel-message-association/message-channel-message-association.service';
import { MessageThreadObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-thread.object-metadata';
import { ObjectRecord } from 'src/workspace/workspace-sync-metadata/types/object-record';
@Injectable()
export class MessageThreadService {
@ -14,6 +16,25 @@ export class MessageThreadService {
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
) {}
public async getOrphanThreads(
workspaceId: string,
transactionManager?: EntityManager,
): Promise<ObjectRecord<MessageThreadObjectMetadata>[]> {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
return await this.workspaceDataSourceService.executeRawQuery(
`SELECT mt.* FROM ${dataSourceSchema}."messageThread" mt
WHERE NOT EXISTS (
SELECT 1 FROM ${dataSourceSchema}."message" m
WHERE m."messageThreadId" = mt.id
)`,
[],
workspaceId,
transactionManager,
);
}
public async deleteByIds(
messageThreadIds: string[],
workspaceId: string,

View File

@ -26,6 +26,25 @@ export class MessageService {
private readonly createCompaniesAndContactsService: CreateCompaniesAndContactsService,
) {}
public async getNonAssociatedMessages(
workspaceId: string,
transactionManager?: EntityManager,
): Promise<ObjectRecord<MessageObjectMetadata>[]> {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
return await this.workspaceDataSourceService.executeRawQuery(
`SELECT m.* FROM ${dataSourceSchema}."message" m
WHERE NOT EXISTS (
SELECT 1 FROM ${dataSourceSchema}."messageChannelMessageAssociation" mcma
WHERE mcma."messageId" = m.id
)`,
[],
workspaceId,
transactionManager,
);
}
public async getFirstByHeaderMessageId(
headerMessageId: string,
workspaceId: string,

View File

@ -77,11 +77,13 @@ export class CreateCompanyService {
const { name, city } = await this.getCompanyInfoFromDomainName(domainName);
this.companyService.createCompany(
companyId,
name,
domainName,
city,
workspaceId,
{
id: companyId,
domainName,
name,
city,
},
transactionManager,
);

View File

@ -0,0 +1,19 @@
import { Module } from '@nestjs/common';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { DataSourceModule } from 'src/metadata/data-source/data-source.module';
import { MessageThreadModule } from 'src/workspace/messaging/repositories/message-thread/message-thread.module';
import { MessageModule } from 'src/workspace/messaging/repositories/message/message.module';
import { ThreadCleanerService } from 'src/workspace/messaging/services/thread-cleaner/thread-cleaner.service';
@Module({
imports: [
DataSourceModule,
TypeORMModule,
MessageThreadModule,
MessageModule,
],
providers: [ThreadCleanerService],
exports: [ThreadCleanerService],
})
export class ThreadCleanerModule {}

View File

@ -0,0 +1,62 @@
import { Injectable } from '@nestjs/common';
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
import { DataSourceService } from 'src/metadata/data-source/data-source.service';
import { MessageThreadService } from 'src/workspace/messaging/repositories/message-thread/message-thread.service';
import { MessageService } from 'src/workspace/messaging/repositories/message/message.service';
@Injectable()
export class ThreadCleanerService {
constructor(
private readonly dataSourceService: DataSourceService,
private readonly typeORMService: TypeORMService,
private readonly messageService: MessageService,
private readonly messageThreadService: MessageThreadService,
) {}
public async cleanWorkspaceThreads(workspaceId: string) {
const dataSourceMetadata =
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
workspaceId,
);
const workspaceDataSource =
await this.typeORMService.connectToDataSource(dataSourceMetadata);
await workspaceDataSource?.transaction(async (transactionManager) => {
const messagesToDelete =
await this.messageService.getNonAssociatedMessages(
workspaceId,
transactionManager,
);
const messageIdsToDelete = messagesToDelete.map(({ id }) => id);
if (messageIdsToDelete.length > 0) {
await this.messageService.deleteByIds(
messageIdsToDelete,
workspaceId,
transactionManager,
);
}
const messageThreadsToDelete =
await this.messageThreadService.getOrphanThreads(
workspaceId,
transactionManager,
);
const messageThreadToDeleteIds = messageThreadsToDelete.map(
({ id }) => id,
);
if (messageThreadToDeleteIds.length > 0) {
await this.messageThreadService.deleteByIds(
messageThreadToDeleteIds,
workspaceId,
transactionManager,
);
}
});
}
}

View File

@ -23,6 +23,7 @@ import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metada
import { createRelationForeignKeyColumnName } from 'src/metadata/relation-metadata/utils/create-relation-foreign-key-column-name.util';
import { createRelationForeignKeyFieldMetadataName } from 'src/metadata/relation-metadata/utils/create-relation-foreign-key-field-metadata-name.util';
import { isRelationFieldMetadataType } from 'src/workspace/utils/is-relation-field-metadata-type.util';
import { convertOnDeleteActionToOnDelete } from 'src/workspace/workspace-migration-runner/utils/convert-on-delete-action-to-on-delete.util';
@Injectable()
export class RelationMetadataHealthService {
@ -210,7 +211,7 @@ export class RelationMetadataHealthService {
}
if (
relationMetadata.onDeleteAction?.replace(/_/g, ' ') !==
convertOnDeleteActionToOnDelete(relationMetadata.onDeleteAction) !==
relationColumn.onDeleteAction
) {
issues.push({

View File

@ -0,0 +1,22 @@
import { RelationOnDeleteAction } from 'src/metadata/relation-metadata/relation-metadata.entity';
export const convertOnDeleteActionToOnDelete = (
onDeleteAction: RelationOnDeleteAction | undefined,
): 'CASCADE' | 'SET NULL' | 'RESTRICT' | 'NO ACTION' | undefined => {
if (!onDeleteAction) {
return;
}
switch (onDeleteAction) {
case 'CASCADE':
return 'CASCADE';
case 'SET_NULL':
return 'SET NULL';
case 'RESTRICT':
return 'RESTRICT';
case 'NO_ACTION':
return 'NO ACTION';
default:
throw new Error('Invalid onDeleteAction');
}
};

View File

@ -21,6 +21,7 @@ import {
} from 'src/metadata/workspace-migration/workspace-migration.entity';
import { WorkspaceCacheVersionService } from 'src/metadata/workspace-cache-version/workspace-cache-version.service';
import { WorkspaceMigrationEnumService } from 'src/workspace/workspace-migration-runner/services/workspace-migration-enum.service';
import { convertOnDeleteActionToOnDelete } from 'src/workspace/workspace-migration-runner/utils/convert-on-delete-action-to-on-delete.util';
import { customTableDefaultColumns } from './utils/custom-table-default-column.util';
import { WorkspaceMigrationTypeService } from './services/workspace-migration-type.service';
@ -343,7 +344,7 @@ export class WorkspaceMigrationRunnerService {
referencedColumnNames: [migrationColumn.referencedTableColumnName],
referencedTableName: migrationColumn.referencedTableName,
referencedSchema: schemaName,
onDelete: migrationColumn.onDelete?.replace(/_/g, ' '),
onDelete: convertOnDeleteActionToOnDelete(migrationColumn.onDelete),
}),
);

View File

@ -1,5 +1,8 @@
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity';
import {
RelationMetadataType,
RelationOnDeleteAction,
} from 'src/metadata/relation-metadata/relation-metadata.entity';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.decorator';
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
@ -76,6 +79,7 @@ export class ConnectedAccountObjectMetadata extends BaseObjectMetadata {
@RelationMetadata({
type: RelationMetadataType.ONE_TO_MANY,
objectName: 'messageChannel',
onDelete: RelationOnDeleteAction.CASCADE,
})
@IsNullable()
messageChannels: MessageChannelObjectMetadata[];

View File

@ -1,5 +1,8 @@
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity';
import {
RelationMetadataType,
RelationOnDeleteAction,
} from 'src/metadata/relation-metadata/relation-metadata.entity';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.decorator';
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
@ -85,6 +88,7 @@ export class MessageChannelObjectMetadata extends BaseObjectMetadata {
@RelationMetadata({
type: RelationMetadataType.ONE_TO_MANY,
objectName: 'messageChannelMessageAssociation',
onDelete: RelationOnDeleteAction.CASCADE,
})
@IsNullable()
messageChannelMessageAssociations: MessageChannelMessageAssociationObjectMetadata[];

View File

@ -1,5 +1,8 @@
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity';
import {
RelationMetadataType,
RelationOnDeleteAction,
} from 'src/metadata/relation-metadata/relation-metadata.entity';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.decorator';
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
@ -27,6 +30,7 @@ export class MessageThreadObjectMetadata extends BaseObjectMetadata {
@RelationMetadata({
type: RelationMetadataType.ONE_TO_MANY,
objectName: 'message',
onDelete: RelationOnDeleteAction.CASCADE,
})
@IsNullable()
messages: MessageObjectMetadata[];
@ -40,6 +44,7 @@ export class MessageThreadObjectMetadata extends BaseObjectMetadata {
@RelationMetadata({
type: RelationMetadataType.ONE_TO_MANY,
objectName: 'messageChannelMessageAssociation',
onDelete: RelationOnDeleteAction.RESTRICT,
})
@IsNullable()
messageChannelMessageAssociations: MessageChannelMessageAssociationObjectMetadata[];

View File

@ -1,5 +1,8 @@
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity';
import {
RelationMetadataType,
RelationOnDeleteAction,
} from 'src/metadata/relation-metadata/relation-metadata.entity';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.decorator';
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
@ -93,6 +96,7 @@ export class MessageObjectMetadata extends BaseObjectMetadata {
type: RelationMetadataType.ONE_TO_MANY,
objectName: 'messageParticipant',
inverseSideFieldName: 'message',
onDelete: RelationOnDeleteAction.CASCADE,
})
@IsNullable()
messageParticipants: MessageParticipantObjectMetadata[];
@ -106,6 +110,7 @@ export class MessageObjectMetadata extends BaseObjectMetadata {
@RelationMetadata({
type: RelationMetadataType.ONE_TO_MANY,
objectName: 'messageChannelMessageAssociation',
onDelete: RelationOnDeleteAction.CASCADE,
})
@IsNullable()
messageChannelMessageAssociations: MessageChannelMessageAssociationObjectMetadata[];