[messaging] Fix thread cleaner service subqueries (#4416)

* [messaging] Fix thread cleaner service subqueries

* add pagination

* various fixes

* Fix thread merging

* fix

* fix
This commit is contained in:
Weiko
2024-03-12 17:49:45 +01:00
committed by GitHub
parent 91f4e1a853
commit 4476f5215b
17 changed files with 311 additions and 115 deletions

View File

@ -46,11 +46,19 @@ export class GmailFullSyncService {
connectedAccountId: string,
nextPageToken?: string,
): Promise<void> {
const connectedAccount = await this.connectedAccountService.getByIdOrFail(
const connectedAccount = await this.connectedAccountService.getById(
connectedAccountId,
workspaceId,
);
if (!connectedAccount) {
this.logger.error(
`Connected account ${connectedAccountId} not found in workspace ${workspaceId} during full-sync`,
);
return;
}
const accessToken = connectedAccount.accessToken;
const refreshToken = connectedAccount.refreshToken;
const workspaceMemberId = connectedAccount.accountOwnerId;
@ -62,11 +70,19 @@ export class GmailFullSyncService {
}
const gmailMessageChannel =
await this.messageChannelService.getFirstByConnectedAccountIdOrFail(
await this.messageChannelService.getFirstByConnectedAccountId(
connectedAccountId,
workspaceId,
);
if (!gmailMessageChannel) {
this.logger.error(
`No message channel found for connected account ${connectedAccountId} in workspace ${workspaceId} during full-syn`,
);
return;
}
const gmailMessageChannelId = gmailMessageChannel.id;
const gmailClient =
@ -173,7 +189,7 @@ export class GmailFullSyncService {
);
if (messagesToSave.length > 0) {
this.saveMessagesAndCreateContactsService.saveMessagesAndCreateContacts(
await this.saveMessagesAndCreateContactsService.saveMessagesAndCreateContacts(
messagesToSave,
connectedAccount,
workspaceId,

View File

@ -48,11 +48,19 @@ export class GmailPartialSyncService {
connectedAccountId: string,
maxResults = 500,
): Promise<void> {
const connectedAccount = await this.connectedAccountService.getByIdOrFail(
const connectedAccount = await this.connectedAccountService.getById(
connectedAccountId,
workspaceId,
);
if (!connectedAccount) {
this.logger.error(
`Connected account ${connectedAccountId} not found in workspace ${workspaceId} during partial-sync`,
);
return;
}
const lastSyncHistoryId = connectedAccount.lastSyncHistoryId;
if (!lastSyncHistoryId) {
@ -135,11 +143,19 @@ export class GmailPartialSyncService {
}
const gmailMessageChannel =
await this.messageChannelService.getFirstByConnectedAccountIdOrFail(
await this.messageChannelService.getFirstByConnectedAccountId(
connectedAccountId,
workspaceId,
);
if (!gmailMessageChannel) {
this.logger.error(
`No message channel found for connected account ${connectedAccountId} in workspace ${workspaceId} during partial-sync`,
);
return;
}
const gmailMessageChannelId = gmailMessageChannel.id;
const { messagesAdded, messagesDeleted } =

View File

@ -16,11 +16,17 @@ export class GmailRefreshAccessTokenService {
workspaceId: string,
connectedAccountId: string,
): Promise<void> {
const connectedAccount = await this.connectedAccountService.getByIdOrFail(
const connectedAccount = await this.connectedAccountService.getById(
connectedAccountId,
workspaceId,
);
if (!connectedAccount) {
throw new Error(
`No connected account found for ${connectedAccountId} in workspace ${workspaceId}`,
);
}
const refreshToken = connectedAccount.refreshToken;
if (!refreshToken) {

View File

@ -59,12 +59,23 @@ export class SaveMessagesAndCreateContactsService {
} in ${endTime - startTime}ms`,
);
const isContactAutoCreationEnabled =
await this.messageChannelService.getIsContactAutoCreationEnabledByConnectedAccountIdOrFail(
const gmailMessageChannel =
await this.messageChannelService.getFirstByConnectedAccountId(
connectedAccount.id,
workspaceId,
);
if (!gmailMessageChannel) {
this.logger.error(
`No message channel found for connected account ${connectedAccount.id} in workspace ${workspaceId} in saveMessagesAndCreateContacts`,
);
return;
}
const isContactAutoCreationEnabled =
gmailMessageChannel.isContactAutoCreationEnabled;
const participantsWithMessageId: ParticipantWithMessageId[] =
messagesToSave.flatMap((message) => {
const messageId = messageExternalIdsAndIdsMap.get(message.externalId);

View File

@ -4,6 +4,7 @@ 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';
import { deleteUsingPagination } from 'src/workspace/messaging/services/thread-cleaner/utils/delete-using-pagination.util';
@Injectable()
export class ThreadCleanerService {
@ -15,48 +16,22 @@ export class ThreadCleanerService {
) {}
public async cleanWorkspaceThreads(workspaceId: string) {
const dataSourceMetadata =
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
workspaceId,
);
await deleteUsingPagination(
workspaceId,
500,
this.messageService.getNonAssociatedMessageIdsPaginated.bind(
this.messageService,
),
this.messageService.deleteByIds.bind(this.messageService),
);
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,
);
}
});
await deleteUsingPagination(
workspaceId,
500,
this.messageThreadService.getOrphanThreadIdsPaginated.bind(
this.messageThreadService,
),
this.messageThreadService.deleteByIds.bind(this.messageThreadService),
);
}
}

View File

@ -0,0 +1,44 @@
import { deleteUsingPagination } from './delete-using-pagination.util';
describe('deleteUsingPagination', () => {
it('should delete items using pagination', async () => {
const workspaceId = 'workspace123';
const batchSize = 10;
const getterPaginated = jest
.fn()
.mockResolvedValueOnce(['id1', 'id2'])
.mockResolvedValueOnce([]);
const deleter = jest.fn();
const transactionManager = undefined;
await deleteUsingPagination(
workspaceId,
batchSize,
getterPaginated,
deleter,
transactionManager,
);
expect(getterPaginated).toHaveBeenNthCalledWith(
1,
batchSize,
0,
workspaceId,
transactionManager,
);
expect(getterPaginated).toHaveBeenNthCalledWith(
2,
batchSize,
batchSize,
workspaceId,
transactionManager,
);
expect(deleter).toHaveBeenNthCalledWith(
1,
['id1', 'id2'],
workspaceId,
transactionManager,
);
expect(deleter).toHaveBeenCalledTimes(1);
});
});

View File

@ -0,0 +1,38 @@
import { EntityManager } from 'typeorm';
export const deleteUsingPagination = async (
workspaceId: string,
batchSize: number,
getterPaginated: (
limit: number,
offset: number,
workspaceId: string,
transactionManager?: EntityManager,
) => Promise<string[]>,
deleter: (
ids: string[],
workspaceId: string,
transactionManager?: EntityManager,
) => Promise<void>,
transactionManager?: EntityManager,
) => {
let offset = 0;
let hasMoreData = true;
while (hasMoreData) {
const idsToDelete = await getterPaginated(
batchSize,
offset,
workspaceId,
transactionManager,
);
if (idsToDelete.length > 0) {
await deleter(idsToDelete, workspaceId, transactionManager);
} else {
hasMoreData = false;
}
offset += batchSize;
}
};