* calendar module * wip * creating a folder for common files between calendar and messages * wip * wip * wip * wip * update calendar search filter * wip * working on full sync service * reorganizing folders * adding repositories * fix typo * working on full-sync service * Add calendarQueue to MessageQueue enum and update dependencies * start transaction * wip * add save and update functions for event * wip * save events * improving step by step * add calendar scope * fix nest modules imports * renaming * create calendar channel * create job for google calendar full-sync * call GoogleCalendarFullSyncJob after connected account creation * ask for scope conditionnally * fixes * create channels conditionnally * fix * fixes * fix FK bug * filter out canceled events * create save and update functions for calendarEventAttendee repository * saving messageParticipants is working * save calendarEventAttendees is working * add calendarEvent cleaner * calendar event cleaner is working * working on updating attendees * wip * reintroducing google-gmail endpoint to ensure smooth deploy * modify callbackURL * modify front url * changes to be able to merge * put back feature flag * fixes after PR comments * add feature flag check * remove unused modules * separate delete connected account associated job data in two jobs * fix error * rename calendar_v3 as calendarV3 * Update packages/twenty-server/src/workspace/calendar-and-messaging/utils/valueStringForBatchRawQuery.util.ts Co-authored-by: Jérémy M <jeremy.magrin@gmail.com> * improve readability * renaming to remove plural * renaming to remove plural * don't throw if no connected account is found * use calendar queue * modify usage of HttpService in fetch-by-batch * modify valuesStringForBatchRawQuery to improve api and return flattened values * fix auth module feature flag import * fix getFlattenedValuesAndValuesStringForBatchRawQuery --------- Co-authored-by: Jérémy M <jeremy.magrin@gmail.com>
182 lines
5.9 KiB
TypeScript
182 lines
5.9 KiB
TypeScript
import { Injectable, Logger } from '@nestjs/common';
|
|
|
|
import { EntityManager } from 'typeorm';
|
|
|
|
import { MessageChannelService } from 'src/workspace/messaging/repositories/message-channel/message-channel.service';
|
|
import { MessageParticipantService } from 'src/workspace/messaging/repositories/message-participant/message-participant.service';
|
|
import { MessageService } from 'src/workspace/messaging/repositories/message/message.service';
|
|
import { CreateCompanyAndContactService } from 'src/workspace/auto-companies-and-contacts-creation/create-company-and-contact/create-company-and-contact.service';
|
|
import {
|
|
GmailMessage,
|
|
ParticipantWithMessageId,
|
|
} from 'src/workspace/messaging/types/gmail-message';
|
|
import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service';
|
|
import { ConnectedAccountObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/connected-account.object-metadata';
|
|
import { ObjectRecord } from 'src/workspace/workspace-sync-metadata/types/object-record';
|
|
|
|
@Injectable()
|
|
export class SaveMessagesAndCreateContactsService {
|
|
private readonly logger = new Logger(
|
|
SaveMessagesAndCreateContactsService.name,
|
|
);
|
|
|
|
constructor(
|
|
private readonly messageService: MessageService,
|
|
private readonly messageChannelService: MessageChannelService,
|
|
private readonly createCompaniesAndContactsService: CreateCompanyAndContactService,
|
|
private readonly messageParticipantService: MessageParticipantService,
|
|
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
|
) {}
|
|
|
|
async saveMessagesAndCreateContacts(
|
|
messagesToSave: GmailMessage[],
|
|
connectedAccount: ObjectRecord<ConnectedAccountObjectMetadata>,
|
|
workspaceId: string,
|
|
gmailMessageChannelId: string,
|
|
jobName?: string,
|
|
) {
|
|
const { dataSource: workspaceDataSource, dataSourceMetadata } =
|
|
await this.workspaceDataSourceService.connectedToWorkspaceDataSourceAndReturnMetadata(
|
|
workspaceId,
|
|
);
|
|
|
|
let startTime = Date.now();
|
|
|
|
const messageExternalIdsAndIdsMap = await this.messageService.saveMessages(
|
|
messagesToSave,
|
|
dataSourceMetadata,
|
|
workspaceDataSource,
|
|
connectedAccount,
|
|
gmailMessageChannelId,
|
|
workspaceId,
|
|
);
|
|
|
|
let endTime = Date.now();
|
|
|
|
this.logger.log(
|
|
`${jobName} saving messages for workspace ${workspaceId} and account ${
|
|
connectedAccount.id
|
|
} in ${endTime - startTime}ms`,
|
|
);
|
|
|
|
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);
|
|
|
|
return messageId
|
|
? message.participants.map((participant) => ({
|
|
...participant,
|
|
messageId,
|
|
}))
|
|
: [];
|
|
});
|
|
|
|
const contactsToCreate = messagesToSave
|
|
.filter((message) => connectedAccount.handle === message.fromHandle)
|
|
.flatMap((message) => message.participants);
|
|
|
|
if (isContactAutoCreationEnabled) {
|
|
startTime = Date.now();
|
|
|
|
await workspaceDataSource?.transaction(
|
|
async (transactionManager: EntityManager) => {
|
|
await this.createCompaniesAndContactsService.createCompaniesAndContacts(
|
|
connectedAccount.handle,
|
|
contactsToCreate,
|
|
workspaceId,
|
|
transactionManager,
|
|
);
|
|
},
|
|
);
|
|
|
|
const handles = participantsWithMessageId.map(
|
|
(participant) => participant.handle,
|
|
);
|
|
|
|
const messageParticipantsWithoutPersonIdAndWorkspaceMemberId =
|
|
await this.messageParticipantService.getByHandlesWithoutPersonIdAndWorkspaceMemberId(
|
|
handles,
|
|
workspaceId,
|
|
);
|
|
|
|
await this.messageParticipantService.updateMessageParticipantsAfterPeopleCreation(
|
|
messageParticipantsWithoutPersonIdAndWorkspaceMemberId,
|
|
workspaceId,
|
|
);
|
|
|
|
endTime = Date.now();
|
|
|
|
this.logger.log(
|
|
`${jobName} creating companies and contacts for workspace ${workspaceId} and account ${
|
|
connectedAccount.id
|
|
} in ${endTime - startTime}ms`,
|
|
);
|
|
}
|
|
|
|
startTime = Date.now();
|
|
|
|
await this.tryToSaveMessageParticipantsOrDeleteMessagesIfError(
|
|
participantsWithMessageId,
|
|
gmailMessageChannelId,
|
|
workspaceId,
|
|
connectedAccount,
|
|
jobName,
|
|
);
|
|
|
|
endTime = Date.now();
|
|
|
|
this.logger.log(
|
|
`${jobName} saving message participants for workspace ${workspaceId} and account in ${
|
|
connectedAccount.id
|
|
} ${endTime - startTime}ms`,
|
|
);
|
|
}
|
|
|
|
private async tryToSaveMessageParticipantsOrDeleteMessagesIfError(
|
|
participantsWithMessageId: ParticipantWithMessageId[],
|
|
gmailMessageChannelId: string,
|
|
workspaceId: string,
|
|
connectedAccount: ObjectRecord<ConnectedAccountObjectMetadata>,
|
|
jobName?: string,
|
|
) {
|
|
try {
|
|
await this.messageParticipantService.saveMessageParticipants(
|
|
participantsWithMessageId,
|
|
workspaceId,
|
|
);
|
|
} catch (error) {
|
|
this.logger.error(
|
|
`${jobName} error saving message participants for workspace ${workspaceId} and account ${connectedAccount.id}`,
|
|
error,
|
|
);
|
|
|
|
const messagesToDelete = participantsWithMessageId.map(
|
|
(participant) => participant.messageId,
|
|
);
|
|
|
|
await this.messageService.deleteMessages(
|
|
messagesToDelete,
|
|
gmailMessageChannelId,
|
|
workspaceId,
|
|
);
|
|
}
|
|
}
|
|
}
|