mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-26 05:24:04 +03:00
[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:
parent
6a1abba9ea
commit
214807588a
@ -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 {
|
||||
|
@ -24,6 +24,7 @@ export enum RelationOnDeleteAction {
|
||||
CASCADE = 'CASCADE',
|
||||
RESTRICT = 'RESTRICT',
|
||||
SET_NULL = 'SET_NULL',
|
||||
NO_ACTION = 'NO_ACTION',
|
||||
}
|
||||
|
||||
@Entity('relationMetadata')
|
||||
|
@ -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}`,
|
||||
);
|
||||
}
|
||||
}
|
@ -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}`,
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
);
|
||||
|
@ -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,
|
||||
);
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
);
|
||||
|
||||
|
@ -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 {}
|
@ -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,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -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({
|
||||
|
@ -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');
|
||||
}
|
||||
};
|
@ -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),
|
||||
}),
|
||||
);
|
||||
|
||||
|
@ -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[];
|
||||
|
@ -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[];
|
||||
|
@ -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[];
|
||||
|
@ -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[];
|
||||
|
Loading…
Reference in New Issue
Block a user