[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:
@ -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 { 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 { MessageChannelModule } from 'src/workspace/messaging/repositories/message-channel/message-channel.module';
|
||||||
import { MessageParticipantModule } from 'src/workspace/messaging/repositories/message-participant/message-participant.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 { 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({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -44,6 +46,7 @@ import { DataSeedDemoWorkspaceModule } from 'src/database/commands/data-seed-dem
|
|||||||
CreateCompaniesAndContactsModule,
|
CreateCompaniesAndContactsModule,
|
||||||
MessageChannelModule,
|
MessageChannelModule,
|
||||||
DataSeedDemoWorkspaceModule,
|
DataSeedDemoWorkspaceModule,
|
||||||
|
ThreadCleanerModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
@ -83,6 +86,10 @@ import { DataSeedDemoWorkspaceModule } from 'src/database/commands/data-seed-dem
|
|||||||
provide: DataSeedDemoWorkspaceJob.name,
|
provide: DataSeedDemoWorkspaceJob.name,
|
||||||
useClass: DataSeedDemoWorkspaceJob,
|
useClass: DataSeedDemoWorkspaceJob,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: DeleteConnectedAccountAssociatedDataJob.name,
|
||||||
|
useClass: DeleteConnectedAccountAssociatedDataJob,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class JobsModule {
|
export class JobsModule {
|
||||||
|
|||||||
@ -24,6 +24,7 @@ export enum RelationOnDeleteAction {
|
|||||||
CASCADE = 'CASCADE',
|
CASCADE = 'CASCADE',
|
||||||
RESTRICT = 'RESTRICT',
|
RESTRICT = 'RESTRICT',
|
||||||
SET_NULL = 'SET_NULL',
|
SET_NULL = 'SET_NULL',
|
||||||
|
NO_ACTION = 'NO_ACTION',
|
||||||
}
|
}
|
||||||
|
|
||||||
@Entity('relationMetadata')
|
@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 { Inject, Injectable } from '@nestjs/common';
|
||||||
import { OnEvent } from '@nestjs/event-emitter';
|
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 { MessageQueue } from 'src/integrations/message-queue/message-queue.constants';
|
||||||
import { MessageQueueService } from 'src/integrations/message-queue/services/message-queue.service';
|
import { MessageQueueService } from 'src/integrations/message-queue/services/message-queue.service';
|
||||||
import {
|
import {
|
||||||
DeleteMessageChannelMessageAssociationJob,
|
CreateCompaniesAndContactsAfterSyncJobData,
|
||||||
DeleteMessageChannelMessageAssociationJobData,
|
CreateCompaniesAndContactsAfterSyncJob,
|
||||||
} from 'src/workspace/messaging/jobs/delete-message-channel-message-association.job';
|
} 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';
|
import { MessageChannelObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-channel.object-metadata';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -17,16 +18,24 @@ export class MessagingMessageChannelListener {
|
|||||||
private readonly messageQueueService: MessageQueueService,
|
private readonly messageQueueService: MessageQueueService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@OnEvent('messageChannel.deleted')
|
@OnEvent('messageChannel.updated')
|
||||||
handleDeletedEvent(
|
handleUpdatedEvent(
|
||||||
payload: ObjectRecordDeleteEvent<MessageChannelObjectMetadata>,
|
payload: ObjectRecordUpdateEvent<MessageChannelObjectMetadata>,
|
||||||
) {
|
) {
|
||||||
this.messageQueueService.add<DeleteMessageChannelMessageAssociationJobData>(
|
if (
|
||||||
DeleteMessageChannelMessageAssociationJob.name,
|
objectRecordChangedProperties(
|
||||||
{
|
payload.previousRecord,
|
||||||
workspaceId: payload.workspaceId,
|
payload.updatedRecord,
|
||||||
messageChannelId: payload.deletedRecord.id,
|
).includes('isContactAutoCreationEnabled') &&
|
||||||
},
|
payload.updatedRecord.isContactAutoCreationEnabled
|
||||||
);
|
) {
|
||||||
|
this.messageQueueService.add<CreateCompaniesAndContactsAfterSyncJobData>(
|
||||||
|
CreateCompaniesAndContactsAfterSyncJob.name,
|
||||||
|
{
|
||||||
|
workspaceId: payload.workspaceId,
|
||||||
|
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 { WorkspaceDataSourceModule } from 'src/workspace/workspace-datasource/workspace-datasource.module';
|
||||||
import { MessageParticipantModule } from 'src/workspace/messaging/repositories/message-participant/message-participant.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 { 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 { MessagingMessageChannelListener } from 'src/workspace/messaging/listeners/messaging-message-channel.listener';
|
||||||
import { MessageService } from 'src/workspace/messaging/repositories/message/message.service';
|
import { MessageService } from 'src/workspace/messaging/repositories/message/message.service';
|
||||||
import { WorkspaceMemberModule } from 'src/workspace/messaging/repositories/workspace-member/workspace-member.module';
|
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 { 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 { CompanyModule } from 'src/workspace/messaging/repositories/company/company.module';
|
||||||
import { PersonModule } from 'src/workspace/messaging/repositories/person/person.module';
|
import { PersonModule } from 'src/workspace/messaging/repositories/person/person.module';
|
||||||
|
import { MessagingConnectedAccountListener } from 'src/workspace/messaging/listeners/messaging-connected-account.listener';
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
EnvironmentModule,
|
EnvironmentModule,
|
||||||
@ -52,9 +52,9 @@ import { PersonModule } from 'src/workspace/messaging/repositories/person/person
|
|||||||
CreateCompanyService,
|
CreateCompanyService,
|
||||||
MessagingPersonListener,
|
MessagingPersonListener,
|
||||||
MessagingWorkspaceMemberListener,
|
MessagingWorkspaceMemberListener,
|
||||||
IsContactAutoCreationEnabledListener,
|
|
||||||
MessagingMessageChannelListener,
|
MessagingMessageChannelListener,
|
||||||
MessageService,
|
MessageService,
|
||||||
|
MessagingConnectedAccountListener,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
GmailPartialSyncService,
|
GmailPartialSyncService,
|
||||||
|
|||||||
@ -4,6 +4,13 @@ import { EntityManager } from 'typeorm';
|
|||||||
|
|
||||||
import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service';
|
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
|
// TODO: Move outside of the messaging module
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CompanyService {
|
export class CompanyService {
|
||||||
@ -31,20 +38,22 @@ export class CompanyService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async createCompany(
|
public async createCompany(
|
||||||
id: string,
|
|
||||||
name: string,
|
|
||||||
domainName: string,
|
|
||||||
city: string,
|
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
|
companyToCreate: CompanyToCreate,
|
||||||
transactionManager?: EntityManager,
|
transactionManager?: EntityManager,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const dataSourceSchema =
|
const dataSourceSchema =
|
||||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||||
|
|
||||||
await this.workspaceDataSourceService.executeRawQuery(
|
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)`,
|
VALUES ($1, $2, $3, $4)`,
|
||||||
[id, name, domainName, city],
|
[
|
||||||
|
companyToCreate.id,
|
||||||
|
companyToCreate.domainName,
|
||||||
|
companyToCreate.name ?? '',
|
||||||
|
companyToCreate.city ?? '',
|
||||||
|
],
|
||||||
workspaceId,
|
workspaceId,
|
||||||
transactionManager,
|
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(
|
public async deleteByMessageChannelId(
|
||||||
messageChannelId: string,
|
messageChannelId: string,
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
transactionManager?: EntityManager,
|
transactionManager?: EntityManager,
|
||||||
) {
|
) {
|
||||||
|
this.deleteByMessageChannelIds(
|
||||||
|
[messageChannelId],
|
||||||
|
workspaceId,
|
||||||
|
transactionManager,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async deleteByMessageChannelIds(
|
||||||
|
messageChannelIds: string[],
|
||||||
|
workspaceId: string,
|
||||||
|
transactionManager?: EntityManager,
|
||||||
|
) {
|
||||||
|
if (messageChannelIds.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const dataSourceSchema =
|
const dataSourceSchema =
|
||||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||||
|
|
||||||
await this.workspaceDataSourceService.executeRawQuery(
|
await this.workspaceDataSourceService.executeRawQuery(
|
||||||
`DELETE FROM ${dataSourceSchema}."messageChannelMessageAssociation" WHERE "messageChannelId" = $1`,
|
`DELETE FROM ${dataSourceSchema}."messageChannelMessageAssociation" WHERE "messageChannelId" = ANY($1)`,
|
||||||
[messageChannelId],
|
[messageChannelIds],
|
||||||
workspaceId,
|
workspaceId,
|
||||||
transactionManager,
|
transactionManager,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import { v4 } from 'uuid';
|
|||||||
import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service';
|
import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service';
|
||||||
import { DataSourceEntity } from 'src/metadata/data-source/data-source.entity';
|
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 { 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()
|
@Injectable()
|
||||||
export class MessageThreadService {
|
export class MessageThreadService {
|
||||||
@ -14,6 +16,25 @@ export class MessageThreadService {
|
|||||||
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
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(
|
public async deleteByIds(
|
||||||
messageThreadIds: string[],
|
messageThreadIds: string[],
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
|
|||||||
@ -26,6 +26,25 @@ export class MessageService {
|
|||||||
private readonly createCompaniesAndContactsService: CreateCompaniesAndContactsService,
|
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(
|
public async getFirstByHeaderMessageId(
|
||||||
headerMessageId: string,
|
headerMessageId: string,
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
|
|||||||
@ -77,11 +77,13 @@ export class CreateCompanyService {
|
|||||||
const { name, city } = await this.getCompanyInfoFromDomainName(domainName);
|
const { name, city } = await this.getCompanyInfoFromDomainName(domainName);
|
||||||
|
|
||||||
this.companyService.createCompany(
|
this.companyService.createCompany(
|
||||||
companyId,
|
|
||||||
name,
|
|
||||||
domainName,
|
|
||||||
city,
|
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
{
|
||||||
|
id: companyId,
|
||||||
|
domainName,
|
||||||
|
name,
|
||||||
|
city,
|
||||||
|
},
|
||||||
transactionManager,
|
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 { 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 { 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 { 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()
|
@Injectable()
|
||||||
export class RelationMetadataHealthService {
|
export class RelationMetadataHealthService {
|
||||||
@ -210,7 +211,7 @@ export class RelationMetadataHealthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
relationMetadata.onDeleteAction?.replace(/_/g, ' ') !==
|
convertOnDeleteActionToOnDelete(relationMetadata.onDeleteAction) !==
|
||||||
relationColumn.onDeleteAction
|
relationColumn.onDeleteAction
|
||||||
) {
|
) {
|
||||||
issues.push({
|
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';
|
} from 'src/metadata/workspace-migration/workspace-migration.entity';
|
||||||
import { WorkspaceCacheVersionService } from 'src/metadata/workspace-cache-version/workspace-cache-version.service';
|
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 { 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 { customTableDefaultColumns } from './utils/custom-table-default-column.util';
|
||||||
import { WorkspaceMigrationTypeService } from './services/workspace-migration-type.service';
|
import { WorkspaceMigrationTypeService } from './services/workspace-migration-type.service';
|
||||||
@ -343,7 +344,7 @@ export class WorkspaceMigrationRunnerService {
|
|||||||
referencedColumnNames: [migrationColumn.referencedTableColumnName],
|
referencedColumnNames: [migrationColumn.referencedTableColumnName],
|
||||||
referencedTableName: migrationColumn.referencedTableName,
|
referencedTableName: migrationColumn.referencedTableName,
|
||||||
referencedSchema: schemaName,
|
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 { 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 { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
|
||||||
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.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';
|
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
|
||||||
@ -76,6 +79,7 @@ export class ConnectedAccountObjectMetadata extends BaseObjectMetadata {
|
|||||||
@RelationMetadata({
|
@RelationMetadata({
|
||||||
type: RelationMetadataType.ONE_TO_MANY,
|
type: RelationMetadataType.ONE_TO_MANY,
|
||||||
objectName: 'messageChannel',
|
objectName: 'messageChannel',
|
||||||
|
onDelete: RelationOnDeleteAction.CASCADE,
|
||||||
})
|
})
|
||||||
@IsNullable()
|
@IsNullable()
|
||||||
messageChannels: MessageChannelObjectMetadata[];
|
messageChannels: MessageChannelObjectMetadata[];
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
|
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 { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
|
||||||
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.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';
|
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
|
||||||
@ -85,6 +88,7 @@ export class MessageChannelObjectMetadata extends BaseObjectMetadata {
|
|||||||
@RelationMetadata({
|
@RelationMetadata({
|
||||||
type: RelationMetadataType.ONE_TO_MANY,
|
type: RelationMetadataType.ONE_TO_MANY,
|
||||||
objectName: 'messageChannelMessageAssociation',
|
objectName: 'messageChannelMessageAssociation',
|
||||||
|
onDelete: RelationOnDeleteAction.CASCADE,
|
||||||
})
|
})
|
||||||
@IsNullable()
|
@IsNullable()
|
||||||
messageChannelMessageAssociations: MessageChannelMessageAssociationObjectMetadata[];
|
messageChannelMessageAssociations: MessageChannelMessageAssociationObjectMetadata[];
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
|
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 { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
|
||||||
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.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';
|
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
|
||||||
@ -27,6 +30,7 @@ export class MessageThreadObjectMetadata extends BaseObjectMetadata {
|
|||||||
@RelationMetadata({
|
@RelationMetadata({
|
||||||
type: RelationMetadataType.ONE_TO_MANY,
|
type: RelationMetadataType.ONE_TO_MANY,
|
||||||
objectName: 'message',
|
objectName: 'message',
|
||||||
|
onDelete: RelationOnDeleteAction.CASCADE,
|
||||||
})
|
})
|
||||||
@IsNullable()
|
@IsNullable()
|
||||||
messages: MessageObjectMetadata[];
|
messages: MessageObjectMetadata[];
|
||||||
@ -40,6 +44,7 @@ export class MessageThreadObjectMetadata extends BaseObjectMetadata {
|
|||||||
@RelationMetadata({
|
@RelationMetadata({
|
||||||
type: RelationMetadataType.ONE_TO_MANY,
|
type: RelationMetadataType.ONE_TO_MANY,
|
||||||
objectName: 'messageChannelMessageAssociation',
|
objectName: 'messageChannelMessageAssociation',
|
||||||
|
onDelete: RelationOnDeleteAction.RESTRICT,
|
||||||
})
|
})
|
||||||
@IsNullable()
|
@IsNullable()
|
||||||
messageChannelMessageAssociations: MessageChannelMessageAssociationObjectMetadata[];
|
messageChannelMessageAssociations: MessageChannelMessageAssociationObjectMetadata[];
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
|
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 { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
|
||||||
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.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';
|
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,
|
type: RelationMetadataType.ONE_TO_MANY,
|
||||||
objectName: 'messageParticipant',
|
objectName: 'messageParticipant',
|
||||||
inverseSideFieldName: 'message',
|
inverseSideFieldName: 'message',
|
||||||
|
onDelete: RelationOnDeleteAction.CASCADE,
|
||||||
})
|
})
|
||||||
@IsNullable()
|
@IsNullable()
|
||||||
messageParticipants: MessageParticipantObjectMetadata[];
|
messageParticipants: MessageParticipantObjectMetadata[];
|
||||||
@ -106,6 +110,7 @@ export class MessageObjectMetadata extends BaseObjectMetadata {
|
|||||||
@RelationMetadata({
|
@RelationMetadata({
|
||||||
type: RelationMetadataType.ONE_TO_MANY,
|
type: RelationMetadataType.ONE_TO_MANY,
|
||||||
objectName: 'messageChannelMessageAssociation',
|
objectName: 'messageChannelMessageAssociation',
|
||||||
|
onDelete: RelationOnDeleteAction.CASCADE,
|
||||||
})
|
})
|
||||||
@IsNullable()
|
@IsNullable()
|
||||||
messageChannelMessageAssociations: MessageChannelMessageAssociationObjectMetadata[];
|
messageChannelMessageAssociations: MessageChannelMessageAssociationObjectMetadata[];
|
||||||
|
|||||||
Reference in New Issue
Block a user