[Messaging] Fix duplicate messageChannelMessage (#3616)

* [Messaging] Fix duplicate channelMessageChannel

* add messageChannelMessage check before querying gmail

* rename messageChannelMessage to messageChannelMessageAssociation
This commit is contained in:
Weiko
2024-01-25 14:15:57 +01:00
committed by GitHub
parent 7845e04f6b
commit 6d997edabb
9 changed files with 87 additions and 34 deletions

View File

@ -1,4 +1,4 @@
import { Inject, Injectable } from '@nestjs/common'; import { ConflictException, Inject, Injectable } from '@nestjs/common';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
@ -48,9 +48,7 @@ export class GoogleGmailService {
); );
if (connectedAccount.length > 0) { if (connectedAccount.length > 0) {
console.log('This account is already connected to your workspace.'); throw new ConflictException('Connected account already exists');
return;
} }
const connectedAccountId = v4(); const connectedAccountId = v4();

View File

@ -57,12 +57,33 @@ export class GmailFullSyncService {
? messagesData.map((message) => message.id || '') ? messagesData.map((message) => message.id || '')
: []; : [];
if (!messagesData || messagesData?.length === 0) { if (!messageExternalIds || messageExternalIds?.length === 0) {
return; 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 = const messageQueries =
this.utils.createQueriesFromMessageIds(messageExternalIds); this.utils.createQueriesFromMessageIds(messagesToFetch);
const { messages: messagesToSave, errors } = const { messages: messagesToSave, errors } =
await this.fetchMessagesByBatchesService.fetchAllMessages( await this.fetchMessagesByBatchesService.fetchAllMessages(
@ -84,7 +105,7 @@ export class GmailFullSyncService {
if (errors.length) throw new Error('Error fetching messages'); if (errors.length) throw new Error('Error fetching messages');
const lastModifiedMessageId = messagesData[0].id; const lastModifiedMessageId = messagesToFetch[0];
const historyId = messagesToSave.find( const historyId = messagesToSave.find(
(message) => message.externalId === lastModifiedMessageId, (message) => message.externalId === lastModifiedMessageId,

View File

@ -125,7 +125,7 @@ export class GmailPartialSyncService {
const gmailMessageChannelId = gmailMessageChannel[0].id; const gmailMessageChannelId = gmailMessageChannel[0].id;
const { messagesAdded, messagesDeleted } = const { messagesAdded, messagesDeleted } =
await this.getMessageIdsAndThreadIdsFromHistory(history); await this.getMessageIdsFromHistory(history);
const messageQueries = const messageQueries =
this.utils.createQueriesFromMessageIds(messagesAdded); this.utils.createQueriesFromMessageIds(messagesAdded);
@ -144,7 +144,7 @@ export class GmailPartialSyncService {
gmailMessageChannelId, gmailMessageChannelId,
); );
await this.utils.deleteMessageChannelMessages( await this.utils.deleteMessageChannelMessageAssociations(
messagesDeleted, messagesDeleted,
gmailMessageChannelId, gmailMessageChannelId,
dataSourceMetadata, dataSourceMetadata,
@ -161,7 +161,7 @@ export class GmailPartialSyncService {
); );
} }
private async getMessageIdsAndThreadIdsFromHistory( private async getMessageIdsFromHistory(
history: gmail_v1.Schema$ListHistoryResponse, history: gmail_v1.Schema$ListHistoryResponse,
): Promise<{ ): Promise<{
messagesAdded: string[]; messagesAdded: string[];
@ -193,9 +193,17 @@ export class GmailPartialSyncService {
{ messagesAdded: [], messagesDeleted: [] }, { messagesAdded: [], messagesDeleted: [] },
); );
const uniqueMessagesAdded = messagesAdded.filter(
(messageId) => !messagesDeleted.includes(messageId),
);
const uniqueMessagesDeleted = messagesDeleted.filter(
(messageId) => !messagesAdded.includes(messageId),
);
return { return {
messagesAdded, messagesAdded: uniqueMessagesAdded,
messagesDeleted, messagesDeleted: uniqueMessagesDeleted,
}; };
} }
} }

View File

@ -36,6 +36,16 @@ export class MessagingUtilsService {
) { ) {
for (const message of messages) { for (const message of messages) {
await workspaceDataSource?.transaction(async (manager) => { 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 = const savedOrExistingMessageThreadId =
await this.saveMessageThreadOrReturnExistingMessageThread( await this.saveMessageThreadOrReturnExistingMessageThread(
message.messageThreadExternalId, message.messageThreadExternalId,
@ -53,7 +63,7 @@ export class MessagingUtilsService {
); );
await manager.query( 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, gmailMessageChannelId,
savedOrExistingMessageId, savedOrExistingMessageId,
@ -120,7 +130,7 @@ export class MessagingUtilsService {
workspaceDataSource: DataSource, workspaceDataSource: DataSource,
) { ) {
const existingMessageThreads = await workspaceDataSource?.query( 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], [messageThreadExternalId],
); );
@ -180,14 +190,14 @@ export class MessagingUtilsService {
} }
} }
public async deleteMessageChannelMessages( public async deleteMessageChannelMessageAssociations(
messageExternalIds: string[], messageExternalIds: string[],
connectedAccountId: string, connectedAccountId: string,
dataSourceMetadata: DataSourceEntity, dataSourceMetadata: DataSourceEntity,
workspaceDataSource: DataSource, workspaceDataSource: DataSource,
) { ) {
await workspaceDataSource?.query( 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], [messageExternalIds, connectedAccountId],
); );
} }
@ -265,4 +275,20 @@ export class MessagingUtilsService {
[historyId, connectedAccountId], [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;
}
} }

View File

@ -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 { 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 { 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 { 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 { 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 { 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'; import { MessageThreadObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-thread.object-metadata';
@ -43,5 +43,5 @@ export const standardObjectMetadata = [
MessageObjectMetadata, MessageObjectMetadata,
MessageChannelObjectMetadata, MessageChannelObjectMetadata,
MessageParticipantObjectMetadata, MessageParticipantObjectMetadata,
MessageChannelMessageObjectMetadata, MessageChannelMessageAssociationObjectMetadata,
]; ];

View File

@ -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'; import { MessageObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message.object-metadata';
@ObjectMetadata({ @ObjectMetadata({
namePlural: 'messageChannelMessages', namePlural: 'messageChannelMessageAssociations',
labelSingular: 'Message Channel Message', labelSingular: 'Message Channel Message Association',
labelPlural: 'Message Channel Messages', labelPlural: 'Message Channel Message Associations',
description: 'Message Synced with a Message Channel', description: 'Message Synced with a Message Channel',
icon: 'IconMessage', icon: 'IconMessage',
}) })
@ -20,7 +20,7 @@ import { MessageObjectMetadata } from 'src/workspace/workspace-sync-metadata/sta
featureFlag: 'IS_MESSAGING_ENABLED', featureFlag: 'IS_MESSAGING_ENABLED',
}) })
@IsSystem() @IsSystem()
export class MessageChannelMessageObjectMetadata extends BaseObjectMetadata { export class MessageChannelMessageAssociationObjectMetadata extends BaseObjectMetadata {
@FieldMetadata({ @FieldMetadata({
type: FieldMetadataType.RELATION, type: FieldMetadataType.RELATION,
label: 'Message Channel Id', label: 'Message Channel Id',

View File

@ -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 { 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 { 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 { 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({ @ObjectMetadata({
namePlural: 'messageChannels', namePlural: 'messageChannels',
@ -73,14 +73,14 @@ export class MessageChannelObjectMetadata extends BaseObjectMetadata {
@FieldMetadata({ @FieldMetadata({
type: FieldMetadataType.RELATION, type: FieldMetadataType.RELATION,
label: 'Message Channel Syncs', label: 'Message Channel Association',
description: 'Messages from the channel.', description: 'Messages from the channel.',
icon: 'IconMessage', icon: 'IconMessage',
}) })
@RelationMetadata({ @RelationMetadata({
type: RelationMetadataType.ONE_TO_MANY, type: RelationMetadataType.ONE_TO_MANY,
objectName: 'messageChannelMessage', objectName: 'messageChannelMessageAssociation',
}) })
@IsNullable() @IsNullable()
messageChannelMessage: MessageChannelMessageObjectMetadata[]; messageChannelMessageAssociation: MessageChannelMessageAssociationObjectMetadata[];
} }

View File

@ -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 { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator';
import { RelationMetadata } from 'src/workspace/workspace-sync-metadata/decorators/relation-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 { 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'; import { MessageObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message.object-metadata';
@ObjectMetadata({ @ObjectMetadata({
@ -37,14 +37,14 @@ export class MessageThreadObjectMetadata extends BaseObjectMetadata {
@FieldMetadata({ @FieldMetadata({
type: FieldMetadataType.RELATION, type: FieldMetadataType.RELATION,
label: 'Message Channel Syncs', label: 'Message Channel Association',
description: 'Messages from the channel.', description: 'Messages from the channel.',
icon: 'IconMessage', icon: 'IconMessage',
}) })
@RelationMetadata({ @RelationMetadata({
type: RelationMetadataType.ONE_TO_MANY, type: RelationMetadataType.ONE_TO_MANY,
objectName: 'messageChannelMessage', objectName: 'messageChannelMessageAssociation',
}) })
@IsNullable() @IsNullable()
messageChannelMessage: MessageChannelMessageObjectMetadata[]; messageChannelMessageAssociation: MessageChannelMessageAssociationObjectMetadata[];
} }

View File

@ -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 { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator';
import { RelationMetadata } from 'src/workspace/workspace-sync-metadata/decorators/relation-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 { 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 { 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'; import { MessageThreadObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-thread.object-metadata';
@ -103,14 +103,14 @@ export class MessageObjectMetadata extends BaseObjectMetadata {
@FieldMetadata({ @FieldMetadata({
type: FieldMetadataType.RELATION, type: FieldMetadataType.RELATION,
label: 'Message Channel Syncs', label: 'Message Channel Association',
description: 'Messages from the channel.', description: 'Messages from the channel.',
icon: 'IconMessage', icon: 'IconMessage',
}) })
@RelationMetadata({ @RelationMetadata({
type: RelationMetadataType.ONE_TO_MANY, type: RelationMetadataType.ONE_TO_MANY,
objectName: 'messageChannelMessage', objectName: 'messageChannelMessageAssociation',
}) })
@IsNullable() @IsNullable()
messageChannelMessage: MessageChannelMessageObjectMetadata[]; messageChannelMessageAssociation: MessageChannelMessageAssociationObjectMetadata[];
} }