Fix critical bug in MessagingMessageListFetchService (#13350)

## Bug description

All old messages were deleted after the first partial sync because the
diff between the existing messages and the messages returned from the
fetch was done considering that it was always a full sync (ie, that we
always retrieve the full list of message ids). But in a partial sync,
only the new messages appear in the list.

This bug was introduced by https://github.com/twentyhq/twenty/pull/13302
when trying to merge the logic between the full sync and the partial
sync.

## Fix

The fix is to adapt the behavior to the type of sync, by looking at the
presence of the sync cursor.

---------

Co-authored-by: Guillim <guillim@users.noreply.github.com>
This commit is contained in:
Raphaël Bosi
2025-07-22 20:19:10 +02:00
committed by GitHub
parent 5cb163daa1
commit 332c3a04bb
2 changed files with 27 additions and 9 deletions

View File

@ -97,6 +97,8 @@ describe('MessagingMessageListFetchService', () => {
],
nextSyncCursor: 'new-google-history-id',
folderId: undefined,
messageExternalIdsToDelete: [],
previousSyncCursor: 'google-sync-cursor',
},
];
} else {
@ -109,6 +111,8 @@ describe('MessagingMessageListFetchService', () => {
],
nextSyncCursor: 'new-sync-cursor',
folderId: 'inbox-folder-id',
messageExternalIdsToDelete: [],
previousSyncCursor: 'inbox-sync-cursor',
},
];
}

View File

@ -57,7 +57,13 @@ export class MessagingMessageListFetchService {
}
for (const messageList of messageLists) {
const { messageExternalIds, nextSyncCursor, folderId } = messageList;
const {
messageExternalIds,
nextSyncCursor,
folderId,
messageExternalIdsToDelete,
previousSyncCursor,
} = messageList;
const messageChannelMessageAssociationRepository =
await this.twentyORMManager.getRepository<MessageChannelMessageAssociationWorkspaceEntity>(
@ -84,17 +90,25 @@ export class MessagingMessageListFetchService {
),
);
const messageExternalIdsToDelete =
existingMessageChannelMessageAssociationsExternalIds.filter(
(existingMessageCMAExternalId) =>
existingMessageCMAExternalId &&
!messageExternalIds.includes(existingMessageCMAExternalId),
);
const isFullSync = !previousSyncCursor;
if (messageExternalIdsToDelete.length) {
const additionalMessageExternalIdsToDelete = isFullSync
? existingMessageChannelMessageAssociationsExternalIds.filter(
(existingMessageCMAExternalId) =>
existingMessageCMAExternalId &&
!messageExternalIds.includes(existingMessageCMAExternalId),
)
: [];
const allMessageExternalIdsToDelete = [
...messageExternalIdsToDelete,
...additionalMessageExternalIdsToDelete,
];
if (allMessageExternalIdsToDelete.length) {
await messageChannelMessageAssociationRepository.delete({
messageChannelId: messageChannel.id,
messageExternalId: In(messageExternalIdsToDelete),
messageExternalId: In(allMessageExternalIdsToDelete),
});
await this.messagingMessageCleanerService.cleanWorkspaceThreads(