From bdd0a7ed9549c4f86eda4b882e310959c5bd19a2 Mon Sep 17 00:00:00 2001 From: bosiraphael <71827178+bosiraphael@users.noreply.github.com> Date: Tue, 9 Jan 2024 14:14:32 +0100 Subject: [PATCH] 3242 all message recipients should be stored (#3320) * saveMessageRecipients * update * workspaceMemberId is working * merge * get direction of the message * fix * improve code * modify GmailMessage type --- .../services/fetch-batch-messages.service.ts | 54 +++++++++-- .../fetch-workspace-messages.service.ts | 89 ++++++++++++++++--- .../workspace/messaging/types/gmailMessage.ts | 15 ++-- 3 files changed, 136 insertions(+), 22 deletions(-) diff --git a/packages/twenty-server/src/workspace/messaging/services/fetch-batch-messages.service.ts b/packages/twenty-server/src/workspace/messaging/services/fetch-batch-messages.service.ts index e1a17fdb6..e2e17946e 100644 --- a/packages/twenty-server/src/workspace/messaging/services/fetch-batch-messages.service.ts +++ b/packages/twenty-server/src/workspace/messaging/services/fetch-batch-messages.service.ts @@ -1,9 +1,12 @@ import { Injectable } from '@nestjs/common'; import axios, { AxiosInstance, AxiosResponse } from 'axios'; -import { simpleParser } from 'mailparser'; +import { simpleParser, AddressObject } from 'mailparser'; -import { GmailMessage } from 'src/workspace/messaging/types/gmailMessage'; +import { + GmailMessage, + Recipient, +} from 'src/workspace/messaging/types/gmailMessage'; import { MessageOrThreadQuery } from 'src/workspace/messaging/types/messageOrThreadQuery'; import { GmailMessageParsedResponse } from 'src/workspace/messaging/types/gmailMessageParsedResponse'; import { GmailThreadParsedResponse } from 'src/workspace/messaging/types/gmailThreadParsedResponse'; @@ -208,16 +211,25 @@ export class FetchBatchMessagesService { attachments, } = parsed; + if (!from) throw new Error('From value is missing'); + if (!to) throw new Error('To value is missing'); + + const recipients = [ + ...this.formatAddressObjectAsRecipients(from, 'from'), + ...this.formatAddressObjectAsRecipients(to, 'to'), + ...this.formatAddressObjectAsRecipients(cc, 'cc'), + ...this.formatAddressObjectAsRecipients(bcc, 'bcc'), + ]; + const messageFromGmail: GmailMessage = { externalId: id, headerMessageId: messageId || '', subject: subject || '', messageThreadId: threadId, internalDate, - from, - to, - cc, - bcc, + fromHandle: from.value[0].address || '', + fromDisplayName: from.value[0].name || '', + recipients, text: text || '', html: html || '', attachments, @@ -237,6 +249,36 @@ export class FetchBatchMessagesService { return filteredResponse; } + formatAddressObjectAsArray( + addressObject: AddressObject | AddressObject[], + ): AddressObject[] { + return Array.isArray(addressObject) ? addressObject : [addressObject]; + } + + formatAddressObjectAsRecipients( + addressObject: AddressObject | AddressObject[] | undefined, + role: 'from' | 'to' | 'cc' | 'bcc', + ): Recipient[] { + if (!addressObject) return []; + const addressObjects = this.formatAddressObjectAsArray(addressObject); + + const recipients = addressObjects.map((addressObject) => { + const emailAdresses = addressObject.value; + + return emailAdresses.map((emailAddress) => { + const { name, address } = emailAddress; + + return { + role, + handle: address || '', + displayName: name || '', + }; + }); + }); + + return recipients.flat(); + } + async formatBatchResponsesAsGmailMessages( batchResponses: AxiosResponse[], ): Promise { diff --git a/packages/twenty-server/src/workspace/messaging/services/fetch-workspace-messages.service.ts b/packages/twenty-server/src/workspace/messaging/services/fetch-workspace-messages.service.ts index ee2a5c2bd..7363c9e80 100644 --- a/packages/twenty-server/src/workspace/messaging/services/fetch-workspace-messages.service.ts +++ b/packages/twenty-server/src/workspace/messaging/services/fetch-workspace-messages.service.ts @@ -2,12 +2,15 @@ import { Injectable } from '@nestjs/common'; import { gmail_v1 } from 'googleapis'; import { v4 } from 'uuid'; -import { DataSource } from 'typeorm'; +import { DataSource, EntityManager } from 'typeorm'; import { TypeORMService } from 'src/database/typeorm/typeorm.service'; import { DataSourceService } from 'src/metadata/data-source/data-source.service'; import { FetchBatchMessagesService } from 'src/workspace/messaging/services/fetch-batch-messages.service'; -import { GmailMessage } from 'src/workspace/messaging/types/gmailMessage'; +import { + GmailMessage, + Recipient, +} from 'src/workspace/messaging/types/gmailMessage'; import { MessageOrThreadQuery } from 'src/workspace/messaging/types/messageOrThreadQuery'; import { DataSourceEntity } from 'src/metadata/data-source/data-source.entity'; import { GmailClientProvider } from 'src/workspace/messaging/providers/gmail/gmail-client.provider'; @@ -34,7 +37,6 @@ export class FetchWorkspaceMessagesService { const accessToken = connectedAccount.accessToken; const refreshToken = connectedAccount.refreshToken; - const workspaceMemberId = connectedAccount.workspaceMemberId; if (!refreshToken) { throw new Error('No refresh token found'); @@ -106,7 +108,7 @@ export class FetchWorkspaceMessagesService { messagesResponse, dataSourceMetadata, workspaceDataSource, - workspaceMemberId, + connectedAccount, ); } @@ -137,7 +139,7 @@ export class FetchWorkspaceMessagesService { messages: GmailMessage[], dataSourceMetadata: DataSourceEntity, workspaceDataSource: DataSource, - workspaceMemberId: string, + connectedAccount, ) { for (const message of messages) { const { @@ -146,7 +148,9 @@ export class FetchWorkspaceMessagesService { subject, messageThreadId, internalDate, - from, + fromHandle, + fromDisplayName, + recipients, text, } = message; @@ -158,16 +162,26 @@ export class FetchWorkspaceMessagesService { ); const messageId = v4(); - const handle = from?.value[0]?.address; - const displayName = from?.value[0]?.name; const person = await workspaceDataSource?.query( `SELECT * FROM ${dataSourceMetadata.schema}."person" WHERE "email" = $1`, - [handle], + [fromHandle], ); const personId = person[0]?.id; + const workspaceMember = await workspaceDataSource?.query( + `SELECT "workspaceMember"."id" FROM ${dataSourceMetadata.schema}."workspaceMember" + JOIN ${dataSourceMetadata.schema}."connectedAccount" ON ${dataSourceMetadata.schema}."workspaceMember"."id" = ${dataSourceMetadata.schema}."connectedAccount"."accountOwnerId" + WHERE ${dataSourceMetadata.schema}."connectedAccount"."handle" = $1`, + [fromHandle], + ); + + const workspaceMemberId = workspaceMember[0]?.id; + + const messageDirection = + connectedAccount.handle === fromHandle ? 'outgoing' : 'incoming'; + await workspaceDataSource?.transaction(async (manager) => { await manager.query( `INSERT INTO ${dataSourceMetadata.schema}."message" ("id", "externalId", "headerMessageId", "subject", "date", "messageThreadId", "direction", "body") VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`, @@ -178,19 +192,72 @@ export class FetchWorkspaceMessagesService { subject, date, messageThread[0]?.id, - 'incoming', + messageDirection, text, ], ); await manager.query( `INSERT INTO ${dataSourceMetadata.schema}."messageRecipient" ("messageId", "role", "handle", "displayName", "personId", "workspaceMemberId") VALUES ($1, $2, $3, $4, $5, $6)`, - [messageId, 'from', handle, displayName, personId, workspaceMemberId], + [ + messageId, + 'from', + fromHandle, + fromDisplayName, + personId, + workspaceMemberId, + ], + ); + + await this.saveMessageRecipients( + recipients, + dataSourceMetadata, + messageId, + manager, ); }); } } + async saveMessageRecipients( + recipients: Recipient[], + dataSourceMetadata: DataSourceEntity, + messageId: string, + manager: EntityManager, + ): Promise { + if (!recipients) return; + + for (const recipient of recipients) { + const recipientPerson = await manager.query( + `SELECT * FROM ${dataSourceMetadata.schema}."person" WHERE "email" = $1`, + [recipient.handle], + ); + + const recipientPersonId = recipientPerson[0]?.id; + + const workspaceMember = await manager.query( + `SELECT "workspaceMember"."id" FROM ${dataSourceMetadata.schema}."workspaceMember" + JOIN ${dataSourceMetadata.schema}."connectedAccount" ON ${dataSourceMetadata.schema}."workspaceMember"."id" = ${dataSourceMetadata.schema}."connectedAccount"."accountOwnerId" + WHERE ${dataSourceMetadata.schema}."connectedAccount"."handle" = $1`, + [recipient.handle], + ); + + const recipientWorkspaceMemberId = workspaceMember[0]?.id; + + await manager.query( + `INSERT INTO ${dataSourceMetadata.schema}."messageRecipient" ("messageId", "role", "handle", "displayName", "personId", "workspaceMemberId") VALUES ($1, $2, $3, $4, $5, $6)`, + [ + messageId, + recipient.role, + recipient.handle, + recipient.displayName, + recipientPersonId, + recipientWorkspaceMemberId, + ], + ); + } + } + private async getAllSavedMessagesIdsAndMessageThreadsIdsForConnectedAccount( dataSourceMetadata: DataSourceEntity, workspaceDataSource: DataSource, diff --git a/packages/twenty-server/src/workspace/messaging/types/gmailMessage.ts b/packages/twenty-server/src/workspace/messaging/types/gmailMessage.ts index 9293ff065..ae12483cf 100644 --- a/packages/twenty-server/src/workspace/messaging/types/gmailMessage.ts +++ b/packages/twenty-server/src/workspace/messaging/types/gmailMessage.ts @@ -1,4 +1,4 @@ -import { AddressObject, Attachment } from 'mailparser'; +import { Attachment } from 'mailparser'; export type GmailMessage = { externalId: string; @@ -6,11 +6,16 @@ export type GmailMessage = { subject: string; messageThreadId: string; internalDate: string; - from: AddressObject | undefined; - to: AddressObject | AddressObject[] | undefined; - cc: AddressObject | AddressObject[] | undefined; - bcc: AddressObject | AddressObject[] | undefined; + fromHandle: string; + fromDisplayName: string; + recipients: Recipient[]; text: string; html: string; attachments: Attachment[]; }; + +export type Recipient = { + role: 'from' | 'to' | 'cc' | 'bcc'; + handle: string; + displayName: string; +};