diff --git a/packages/twenty-server/src/core/auth/services/google-gmail.service.ts b/packages/twenty-server/src/core/auth/services/google-gmail.service.ts index 286c3e82f..25e33c208 100644 --- a/packages/twenty-server/src/core/auth/services/google-gmail.service.ts +++ b/packages/twenty-server/src/core/auth/services/google-gmail.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { ConflictException, Inject, Injectable } from '@nestjs/common'; import { v4 } from 'uuid'; @@ -48,9 +48,7 @@ export class GoogleGmailService { ); if (connectedAccount.length > 0) { - console.log('This account is already connected to your workspace.'); - - return; + throw new ConflictException('Connected account already exists'); } const connectedAccountId = v4(); diff --git a/packages/twenty-server/src/workspace/messaging/services/gmail-full-sync.service.ts b/packages/twenty-server/src/workspace/messaging/services/gmail-full-sync.service.ts index efe8cd2b4..425047865 100644 --- a/packages/twenty-server/src/workspace/messaging/services/gmail-full-sync.service.ts +++ b/packages/twenty-server/src/workspace/messaging/services/gmail-full-sync.service.ts @@ -57,12 +57,33 @@ export class GmailFullSyncService { ? messagesData.map((message) => message.id || '') : []; - if (!messagesData || messagesData?.length === 0) { + if (!messageExternalIds || messageExternalIds?.length === 0) { return; } + const existingMessageChannelMessageAssociations = + await this.utils.getMessageChannelMessageAssociations( + messageExternalIds, + gmailMessageChannelId, + dataSourceMetadata, + workspaceDataSource, + ); + + const existingMessageChannelMessageAssociationsExternalIds = + existingMessageChannelMessageAssociations.map( + (messageChannelMessageAssociation) => + messageChannelMessageAssociation.messageExternalId, + ); + + const messagesToFetch = messageExternalIds.filter( + (messageExternalId) => + !existingMessageChannelMessageAssociationsExternalIds.includes( + messageExternalId, + ), + ); + const messageQueries = - this.utils.createQueriesFromMessageIds(messageExternalIds); + this.utils.createQueriesFromMessageIds(messagesToFetch); const { messages: messagesToSave, errors } = await this.fetchMessagesByBatchesService.fetchAllMessages( @@ -84,7 +105,7 @@ export class GmailFullSyncService { if (errors.length) throw new Error('Error fetching messages'); - const lastModifiedMessageId = messagesData[0].id; + const lastModifiedMessageId = messagesToFetch[0]; const historyId = messagesToSave.find( (message) => message.externalId === lastModifiedMessageId, diff --git a/packages/twenty-server/src/workspace/messaging/services/gmail-partial-sync.service.ts b/packages/twenty-server/src/workspace/messaging/services/gmail-partial-sync.service.ts index 0b1a5ff8a..0b138de5f 100644 --- a/packages/twenty-server/src/workspace/messaging/services/gmail-partial-sync.service.ts +++ b/packages/twenty-server/src/workspace/messaging/services/gmail-partial-sync.service.ts @@ -125,7 +125,7 @@ export class GmailPartialSyncService { const gmailMessageChannelId = gmailMessageChannel[0].id; const { messagesAdded, messagesDeleted } = - await this.getMessageIdsAndThreadIdsFromHistory(history); + await this.getMessageIdsFromHistory(history); const messageQueries = this.utils.createQueriesFromMessageIds(messagesAdded); @@ -144,7 +144,7 @@ export class GmailPartialSyncService { gmailMessageChannelId, ); - await this.utils.deleteMessageChannelMessages( + await this.utils.deleteMessageChannelMessageAssociations( messagesDeleted, gmailMessageChannelId, dataSourceMetadata, @@ -161,7 +161,7 @@ export class GmailPartialSyncService { ); } - private async getMessageIdsAndThreadIdsFromHistory( + private async getMessageIdsFromHistory( history: gmail_v1.Schema$ListHistoryResponse, ): Promise<{ messagesAdded: string[]; @@ -193,9 +193,17 @@ export class GmailPartialSyncService { { messagesAdded: [], messagesDeleted: [] }, ); + const uniqueMessagesAdded = messagesAdded.filter( + (messageId) => !messagesDeleted.includes(messageId), + ); + + const uniqueMessagesDeleted = messagesDeleted.filter( + (messageId) => !messagesAdded.includes(messageId), + ); + return { - messagesAdded, - messagesDeleted, + messagesAdded: uniqueMessagesAdded, + messagesDeleted: uniqueMessagesDeleted, }; } } diff --git a/packages/twenty-server/src/workspace/messaging/services/messaging-utils.service.ts b/packages/twenty-server/src/workspace/messaging/services/messaging-utils.service.ts index ecfd50bea..7c8e2c212 100644 --- a/packages/twenty-server/src/workspace/messaging/services/messaging-utils.service.ts +++ b/packages/twenty-server/src/workspace/messaging/services/messaging-utils.service.ts @@ -36,6 +36,16 @@ export class MessagingUtilsService { ) { for (const message of messages) { await workspaceDataSource?.transaction(async (manager) => { + const existingMessageChannelMessageAssociations = await manager.query( + `SELECT COUNT(*) FROM ${dataSourceMetadata.schema}."messageChannelMessageAssociation" + WHERE "messageExternalId" = $1 AND "messageChannelId" = $2`, + [message.externalId, gmailMessageChannelId], + ); + + if (existingMessageChannelMessageAssociations[0]?.count > 0) { + return; + } + const savedOrExistingMessageThreadId = await this.saveMessageThreadOrReturnExistingMessageThread( message.messageThreadExternalId, @@ -53,7 +63,7 @@ export class MessagingUtilsService { ); await manager.query( - `INSERT INTO ${dataSourceMetadata.schema}."messageChannelMessage" ("messageChannelId", "messageId", "messageExternalId", "messageThreadId", "messageThreadExternalId") VALUES ($1, $2, $3, $4, $5)`, + `INSERT INTO ${dataSourceMetadata.schema}."messageChannelMessageAssociation" ("messageChannelId", "messageId", "messageExternalId", "messageThreadId", "messageThreadExternalId") VALUES ($1, $2, $3, $4, $5)`, [ gmailMessageChannelId, savedOrExistingMessageId, @@ -120,7 +130,7 @@ export class MessagingUtilsService { workspaceDataSource: DataSource, ) { const existingMessageThreads = await workspaceDataSource?.query( - `SELECT "messageChannelMessage"."messageThreadId" FROM ${dataSourceMetadata.schema}."messageChannelMessage" WHERE "messageThreadExternalId" = $1 LIMIT 1`, + `SELECT "messageChannelMessageAssociation"."messageThreadId" FROM ${dataSourceMetadata.schema}."messageChannelMessageAssociation" WHERE "messageThreadExternalId" = $1 LIMIT 1`, [messageThreadExternalId], ); @@ -180,14 +190,14 @@ export class MessagingUtilsService { } } - public async deleteMessageChannelMessages( + public async deleteMessageChannelMessageAssociations( messageExternalIds: string[], connectedAccountId: string, dataSourceMetadata: DataSourceEntity, workspaceDataSource: DataSource, ) { await workspaceDataSource?.query( - `DELETE FROM ${dataSourceMetadata.schema}."messageChannelMessage" WHERE "messageExternalId" = ANY($1) AND "messageChannelId" = $2`, + `DELETE FROM ${dataSourceMetadata.schema}."messageChannelMessageAssociation" WHERE "messageExternalId" = ANY($1) AND "messageChannelId" = $2`, [messageExternalIds, connectedAccountId], ); } @@ -265,4 +275,20 @@ export class MessagingUtilsService { [historyId, connectedAccountId], ); } + + public async getMessageChannelMessageAssociations( + messageExternalIds: string[], + gmailMessageChannelId: string, + dataSourceMetadata: DataSourceEntity, + workspaceDataSource: DataSource, + ) { + const existingMessageChannelMessageAssociation = + await workspaceDataSource?.query( + `SELECT * FROM ${dataSourceMetadata.schema}."messageChannelMessageAssociation" + WHERE "messageExternalId" = ANY($1) AND "messageChannelId" = $2`, + [messageExternalIds, gmailMessageChannelId], + ); + + return existingMessageChannelMessageAssociation; + } } diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/index.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/index.ts index a31204069..955d7727b 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/index.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/index.ts @@ -6,7 +6,7 @@ import { CommentObjectMetadata } from 'src/workspace/workspace-sync-metadata/sta import { CompanyObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/company.object-metadata'; import { ConnectedAccountObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/connected-account.object-metadata'; import { FavoriteObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/favorite.object-metadata'; -import { MessageChannelMessageObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-channel-message.object-metadata'; +import { MessageChannelMessageAssociationObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-channel-message-association.object-metadata'; import { MessageChannelObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-channel.object-metadata'; import { MessageParticipantObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-participant.object-metadata'; import { MessageThreadObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-thread.object-metadata'; @@ -43,5 +43,5 @@ export const standardObjectMetadata = [ MessageObjectMetadata, MessageChannelObjectMetadata, MessageParticipantObjectMetadata, - MessageChannelMessageObjectMetadata, + MessageChannelMessageAssociationObjectMetadata, ]; diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message-channel-message.object-metadata.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message-channel-message-association.object-metadata.ts similarity index 90% rename from packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message-channel-message.object-metadata.ts rename to packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message-channel-message-association.object-metadata.ts index da585ad61..e0b8cf588 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message-channel-message.object-metadata.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message-channel-message-association.object-metadata.ts @@ -10,9 +10,9 @@ import { MessageThreadObjectMetadata } from 'src/workspace/workspace-sync-metada import { MessageObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message.object-metadata'; @ObjectMetadata({ - namePlural: 'messageChannelMessages', - labelSingular: 'Message Channel Message', - labelPlural: 'Message Channel Messages', + namePlural: 'messageChannelMessageAssociations', + labelSingular: 'Message Channel Message Association', + labelPlural: 'Message Channel Message Associations', description: 'Message Synced with a Message Channel', icon: 'IconMessage', }) @@ -20,7 +20,7 @@ import { MessageObjectMetadata } from 'src/workspace/workspace-sync-metadata/sta featureFlag: 'IS_MESSAGING_ENABLED', }) @IsSystem() -export class MessageChannelMessageObjectMetadata extends BaseObjectMetadata { +export class MessageChannelMessageAssociationObjectMetadata extends BaseObjectMetadata { @FieldMetadata({ type: FieldMetadataType.RELATION, label: 'Message Channel Id', diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message-channel.object-metadata.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message-channel.object-metadata.ts index 470ea4f13..33b4aef22 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message-channel.object-metadata.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message-channel.object-metadata.ts @@ -8,7 +8,7 @@ import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators import { RelationMetadata } from 'src/workspace/workspace-sync-metadata/decorators/relation-metadata.decorator'; import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; import { ConnectedAccountObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/connected-account.object-metadata'; -import { MessageChannelMessageObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-channel-message.object-metadata'; +import { MessageChannelMessageAssociationObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-channel-message-association.object-metadata'; @ObjectMetadata({ namePlural: 'messageChannels', @@ -73,14 +73,14 @@ export class MessageChannelObjectMetadata extends BaseObjectMetadata { @FieldMetadata({ type: FieldMetadataType.RELATION, - label: 'Message Channel Syncs', + label: 'Message Channel Association', description: 'Messages from the channel.', icon: 'IconMessage', }) @RelationMetadata({ type: RelationMetadataType.ONE_TO_MANY, - objectName: 'messageChannelMessage', + objectName: 'messageChannelMessageAssociation', }) @IsNullable() - messageChannelMessage: MessageChannelMessageObjectMetadata[]; + messageChannelMessageAssociation: MessageChannelMessageAssociationObjectMetadata[]; } diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message-thread.object-metadata.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message-thread.object-metadata.ts index cb7e094bd..c77d3890d 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message-thread.object-metadata.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message-thread.object-metadata.ts @@ -7,7 +7,7 @@ import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-sy import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator'; import { RelationMetadata } from 'src/workspace/workspace-sync-metadata/decorators/relation-metadata.decorator'; import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; -import { MessageChannelMessageObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-channel-message.object-metadata'; +import { MessageChannelMessageAssociationObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-channel-message-association.object-metadata'; import { MessageObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message.object-metadata'; @ObjectMetadata({ @@ -37,14 +37,14 @@ export class MessageThreadObjectMetadata extends BaseObjectMetadata { @FieldMetadata({ type: FieldMetadataType.RELATION, - label: 'Message Channel Syncs', + label: 'Message Channel Association', description: 'Messages from the channel.', icon: 'IconMessage', }) @RelationMetadata({ type: RelationMetadataType.ONE_TO_MANY, - objectName: 'messageChannelMessage', + objectName: 'messageChannelMessageAssociation', }) @IsNullable() - messageChannelMessage: MessageChannelMessageObjectMetadata[]; + messageChannelMessageAssociation: MessageChannelMessageAssociationObjectMetadata[]; } diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message.object-metadata.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message.object-metadata.ts index 31eae1dd3..f95621f05 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message.object-metadata.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message.object-metadata.ts @@ -7,7 +7,7 @@ import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-sy import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator'; import { RelationMetadata } from 'src/workspace/workspace-sync-metadata/decorators/relation-metadata.decorator'; import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; -import { MessageChannelMessageObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-channel-message.object-metadata'; +import { MessageChannelMessageAssociationObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-channel-message-association.object-metadata'; import { MessageParticipantObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-participant.object-metadata'; import { MessageThreadObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-thread.object-metadata'; @@ -103,14 +103,14 @@ export class MessageObjectMetadata extends BaseObjectMetadata { @FieldMetadata({ type: FieldMetadataType.RELATION, - label: 'Message Channel Syncs', + label: 'Message Channel Association', description: 'Messages from the channel.', icon: 'IconMessage', }) @RelationMetadata({ type: RelationMetadataType.ONE_TO_MANY, - objectName: 'messageChannelMessage', + objectName: 'messageChannelMessageAssociation', }) @IsNullable() - messageChannelMessage: MessageChannelMessageObjectMetadata[]; + messageChannelMessageAssociation: MessageChannelMessageAssociationObjectMetadata[]; }