From 701e6c0f487d3e2adc5fb7d7cb22c329594128d0 Mon Sep 17 00:00:00 2001 From: bosiraphael <71827178+bosiraphael@users.noreply.github.com> Date: Thu, 18 Apr 2024 15:06:13 +0200 Subject: [PATCH] 4746 create created listener on blocklist (#5031) Closes #4746 for messaging. I will create another PR to implement the listener on calendar. --- ...ettingsAccountsEmailsBlocklistTableRow.tsx | 4 +- .../integrations/message-queue/jobs.module.ts | 7 ++ .../google-calendar-search-filter.util.ts | 2 +- .../repositories/blocklist.repository.ts | 23 ++++++ .../jobs/delete-messages-from-handle.job.ts | 79 +++++++++++++++++++ .../listeners/messaging-blocklist.listener.ts | 32 ++++++++ .../src/modules/messaging/messaging.module.ts | 2 + ...-channel-message-association.repository.ts | 34 ++++++++ .../message-channel.repository.ts | 21 +++++ 9 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 packages/twenty-server/src/modules/messaging/jobs/delete-messages-from-handle.job.ts create mode 100644 packages/twenty-server/src/modules/messaging/listeners/messaging-blocklist.listener.ts diff --git a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsEmailsBlocklistTableRow.tsx b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsEmailsBlocklistTableRow.tsx index 303d5261c8c..6ffafb27cf1 100644 --- a/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsEmailsBlocklistTableRow.tsx +++ b/packages/twenty-front/src/modules/settings/accounts/components/SettingsAccountsEmailsBlocklistTableRow.tsx @@ -19,7 +19,9 @@ export const SettingsAccountsEmailsBlocklistTableRow = ({ {blocklistItem.handle} - {formatToHumanReadableDate(blocklistItem.createdAt)} + {blocklistItem.createdAt + ? formatToHumanReadableDate(blocklistItem.createdAt) + : ''} | null> { + const dataSourceSchema = + this.workspaceDataSourceService.getSchemaName(workspaceId); + + const blocklistItems = + await this.workspaceDataSourceService.executeRawQuery( + `SELECT * FROM ${dataSourceSchema}."blocklist" WHERE "id" = $1`, + [id], + workspaceId, + transactionManager, + ); + + if (!blocklistItems || blocklistItems.length === 0) { + return null; + } + + return blocklistItems[0]; + } + public async getByWorkspaceMemberId( workspaceMemberId: string, workspaceId: string, diff --git a/packages/twenty-server/src/modules/messaging/jobs/delete-messages-from-handle.job.ts b/packages/twenty-server/src/modules/messaging/jobs/delete-messages-from-handle.job.ts new file mode 100644 index 00000000000..46aabf5b98e --- /dev/null +++ b/packages/twenty-server/src/modules/messaging/jobs/delete-messages-from-handle.job.ts @@ -0,0 +1,79 @@ +import { Injectable, Logger } from '@nestjs/common'; + +import { MessageQueueJob } from 'src/engine/integrations/message-queue/interfaces/message-queue-job.interface'; + +import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator'; +import { BlocklistRepository } from 'src/modules/connected-account/repositories/blocklist.repository'; +import { BlocklistObjectMetadata } from 'src/modules/connected-account/standard-objects/blocklist.object-metadata'; +import { MessageChannelMessageAssociationRepository } from 'src/modules/messaging/repositories/message-channel-message-association.repository'; +import { MessageChannelRepository } from 'src/modules/messaging/repositories/message-channel.repository'; +import { ThreadCleanerService } from 'src/modules/messaging/services/thread-cleaner/thread-cleaner.service'; +import { MessageChannelMessageAssociationObjectMetadata } from 'src/modules/messaging/standard-objects/message-channel-message-association.object-metadata'; +import { MessageChannelObjectMetadata } from 'src/modules/messaging/standard-objects/message-channel.object-metadata'; + +export type DeleteMessagesFromHandleJobData = { + workspaceId: string; + blocklistItemId: string; +}; + +@Injectable() +export class DeleteMessagesFromHandleJob + implements MessageQueueJob +{ + private readonly logger = new Logger(DeleteMessagesFromHandleJob.name); + + constructor( + @InjectObjectMetadataRepository(MessageChannelObjectMetadata) + private readonly messageChannelRepository: MessageChannelRepository, + @InjectObjectMetadataRepository( + MessageChannelMessageAssociationObjectMetadata, + ) + private readonly messageChannelMessageAssociationRepository: MessageChannelMessageAssociationRepository, + @InjectObjectMetadataRepository(BlocklistObjectMetadata) + private readonly blocklistRepository: BlocklistRepository, + private readonly threadCleanerService: ThreadCleanerService, + ) {} + + async handle(data: DeleteMessagesFromHandleJobData): Promise { + const { workspaceId, blocklistItemId } = data; + + const blocklistItem = await this.blocklistRepository.getById( + blocklistItemId, + workspaceId, + ); + + if (!blocklistItem) { + this.logger.log( + `Blocklist item with id ${blocklistItemId} not found in workspace ${workspaceId}`, + ); + + return; + } + + const { handle, workspaceMemberId } = blocklistItem; + + this.logger.log( + `Deleting messages from ${handle} in workspace ${workspaceId} for workspace member ${workspaceMemberId}`, + ); + + const messageChannels = + await this.messageChannelRepository.getIdsByWorkspaceMemberId( + workspaceMemberId, + workspaceId, + ); + + const messageChannelIds = messageChannels.map(({ id }) => id); + + await this.messageChannelMessageAssociationRepository.deleteByMessageParticipantHandleAndMessageChannelIds( + handle, + messageChannelIds, + workspaceId, + ); + + await this.threadCleanerService.cleanWorkspaceThreads(workspaceId); + + this.logger.log( + `Deleted messages from handle ${handle} in workspace ${workspaceId} for workspace member ${workspaceMemberId}`, + ); + } +} diff --git a/packages/twenty-server/src/modules/messaging/listeners/messaging-blocklist.listener.ts b/packages/twenty-server/src/modules/messaging/listeners/messaging-blocklist.listener.ts new file mode 100644 index 00000000000..eb58ae6e56e --- /dev/null +++ b/packages/twenty-server/src/modules/messaging/listeners/messaging-blocklist.listener.ts @@ -0,0 +1,32 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; + +import { ObjectRecordCreateEvent } from 'src/engine/integrations/event-emitter/types/object-record-create.event'; +import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants'; +import { MessageQueueService } from 'src/engine/integrations/message-queue/services/message-queue.service'; +import { BlocklistObjectMetadata } from 'src/modules/connected-account/standard-objects/blocklist.object-metadata'; +import { + DeleteMessagesFromHandleJobData, + DeleteMessagesFromHandleJob, +} from 'src/modules/messaging/jobs/delete-messages-from-handle.job'; + +@Injectable() +export class MessagingBlocklistListener { + constructor( + @Inject(MessageQueue.messagingQueue) + private readonly messageQueueService: MessageQueueService, + ) {} + + @OnEvent('blocklist.created') + handleCreatedEvent( + payload: ObjectRecordCreateEvent, + ) { + this.messageQueueService.add( + DeleteMessagesFromHandleJob.name, + { + workspaceId: payload.workspaceId, + blocklistItemId: payload.recordId, + }, + ); + } +} diff --git a/packages/twenty-server/src/modules/messaging/messaging.module.ts b/packages/twenty-server/src/modules/messaging/messaging.module.ts index 1b8ba8ed819..907c5bcb018 100644 --- a/packages/twenty-server/src/modules/messaging/messaging.module.ts +++ b/packages/twenty-server/src/modules/messaging/messaging.module.ts @@ -6,6 +6,7 @@ import { MessagingConnectedAccountListener } from 'src/modules/messaging/listene import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { ParticipantPersonListener } from 'src/modules/calendar-messaging-participant/listeners/participant-person.listener'; import { ParticipantWorkspaceMemberListener } from 'src/modules/calendar-messaging-participant/listeners/participant-workspace-member.listener'; +import { MessagingBlocklistListener } from 'src/modules/messaging/listeners/messaging-blocklist.listener'; @Module({ imports: [TypeOrmModule.forFeature([FeatureFlagEntity], 'core')], @@ -14,6 +15,7 @@ import { ParticipantWorkspaceMemberListener } from 'src/modules/calendar-messagi ParticipantWorkspaceMemberListener, MessagingMessageChannelListener, MessagingConnectedAccountListener, + MessagingBlocklistListener, ], exports: [], }) diff --git a/packages/twenty-server/src/modules/messaging/repositories/message-channel-message-association.repository.ts b/packages/twenty-server/src/modules/messaging/repositories/message-channel-message-association.repository.ts index eb5f283e2d0..de2b5fcd4e4 100644 --- a/packages/twenty-server/src/modules/messaging/repositories/message-channel-message-association.repository.ts +++ b/packages/twenty-server/src/modules/messaging/repositories/message-channel-message-association.repository.ts @@ -67,6 +67,40 @@ export class MessageChannelMessageAssociationRepository { ); } + public async deleteByMessageParticipantHandleAndMessageChannelIds( + messageParticipantHandle: string, + messageChannelIds: string[], + workspaceId: string, + transactionManager?: EntityManager, + ) { + const dataSourceSchema = + this.workspaceDataSourceService.getSchemaName(workspaceId); + + const messageChannelMessageAssociationIdsToDelete = + await this.workspaceDataSourceService.executeRawQuery( + `SELECT "messageChannelMessageAssociation".id + FROM ${dataSourceSchema}."messageChannelMessageAssociation" "messageChannelMessageAssociation" + JOIN ${dataSourceSchema}."message" ON "messageChannelMessageAssociation"."messageId" = ${dataSourceSchema}."message"."id" + JOIN ${dataSourceSchema}."messageParticipant" "messageParticipant" ON ${dataSourceSchema}."message"."id" = "messageParticipant"."messageId" + WHERE "messageParticipant"."handle" = $1 AND "messageParticipant"."role"= ANY($2) AND "messageChannelMessageAssociation"."messageChannelId" = ANY($3)`, + [messageParticipantHandle, ['from', 'to'], messageChannelIds], + workspaceId, + transactionManager, + ); + + const messageChannelMessageAssociationIdsToDeleteArray = + messageChannelMessageAssociationIdsToDelete.map( + (messageChannelMessageAssociation: { id: string }) => + messageChannelMessageAssociation.id, + ); + + await this.deleteByIds( + messageChannelMessageAssociationIdsToDeleteArray, + workspaceId, + transactionManager, + ); + } + public async getByMessageChannelIds( messageChannelIds: string[], workspaceId: string, diff --git a/packages/twenty-server/src/modules/messaging/repositories/message-channel.repository.ts b/packages/twenty-server/src/modules/messaging/repositories/message-channel.repository.ts index 35a33473f40..cfa11172074 100644 --- a/packages/twenty-server/src/modules/messaging/repositories/message-channel.repository.ts +++ b/packages/twenty-server/src/modules/messaging/repositories/message-channel.repository.ts @@ -137,6 +137,27 @@ export class MessageChannelRepository { ); } + public async getIdsByWorkspaceMemberId( + workspaceMemberId: string, + workspaceId: string, + transactionManager?: EntityManager, + ): Promise[]> { + const dataSourceSchema = + this.workspaceDataSourceService.getSchemaName(workspaceId); + + const messageChannelIds = + await this.workspaceDataSourceService.executeRawQuery( + `SELECT "messageChannel".id FROM ${dataSourceSchema}."messageChannel" "messageChannel" + JOIN ${dataSourceSchema}."connectedAccount" ON "messageChannel"."connectedAccountId" = ${dataSourceSchema}."connectedAccount"."id" + WHERE ${dataSourceSchema}."connectedAccount"."accountOwnerId" = $1`, + [workspaceMemberId], + workspaceId, + transactionManager, + ); + + return messageChannelIds; + } + public async updateSyncStatus( id: string, syncStatus: MessageChannelSyncStatus,