diff --git a/packages/twenty-front/src/modules/activities/emails/components/Threads.tsx b/packages/twenty-front/src/modules/activities/emails/components/Threads.tsx
index bb9fc06b1..d1f78b505 100644
--- a/packages/twenty-front/src/modules/activities/emails/components/Threads.tsx
+++ b/packages/twenty-front/src/modules/activities/emails/components/Threads.tsx
@@ -62,19 +62,22 @@ export const Threads = ({ entity }: { entity: ActivityTargetableObject }) => {
title={
<>
Inbox{' '}
- {timelineThreads.length}
+
+ {timelineThreads && timelineThreads.length}
+
>
}
fontColor={H1TitleFontColor.Primary}
/>
- {timelineThreads.map((thread: TimelineThread, index: number) => (
-
- ))}
+ {timelineThreads &&
+ timelineThreads.map((thread: TimelineThread, index: number) => (
+
+ ))}
diff --git a/packages/twenty-server/src/core/auth/services/google-gmail.service.ts b/packages/twenty-server/src/core/auth/services/google-gmail.service.ts
index cb73dab96..286c3e82f 100644
--- a/packages/twenty-server/src/core/auth/services/google-gmail.service.ts
+++ b/packages/twenty-server/src/core/auth/services/google-gmail.service.ts
@@ -21,13 +21,14 @@ export class GoogleGmailService {
private readonly messageQueueService: MessageQueueService,
) {}
+ providerName = 'google';
+
async saveConnectedAccount(
saveConnectedAccountInput: SaveConnectedAccountInput,
) {
const {
handle,
workspaceId,
- provider,
accessToken,
refreshToken,
workspaceMemberId,
@@ -43,7 +44,7 @@ export class GoogleGmailService {
const connectedAccount = await workspaceDataSource?.query(
`SELECT * FROM ${dataSourceMetadata.schema}."connectedAccount" WHERE "handle" = $1 AND "provider" = $2 AND "accountOwnerId" = $3`,
- [handle, provider, workspaceMemberId],
+ [handle, this.providerName, workspaceMemberId],
);
if (connectedAccount.length > 0) {
@@ -60,7 +61,7 @@ export class GoogleGmailService {
[
connectedAccountId,
handle,
- provider,
+ this.providerName,
accessToken,
refreshToken,
workspaceMemberId,
@@ -69,7 +70,7 @@ export class GoogleGmailService {
await manager.query(
`INSERT INTO ${dataSourceMetadata.schema}."messageChannel" ("visibility", "handle", "connectedAccountId", "type") VALUES ($1, $2, $3, $4)`,
- ['share_everything', handle, connectedAccountId, 'gmail'],
+ ['share_everything', handle, connectedAccountId, 'email'],
);
});
diff --git a/packages/twenty-server/src/core/messaging/timeline-messaging.resolver.ts b/packages/twenty-server/src/core/messaging/timeline-messaging.resolver.ts
index 591af18b6..f98df9e13 100644
--- a/packages/twenty-server/src/core/messaging/timeline-messaging.resolver.ts
+++ b/packages/twenty-server/src/core/messaging/timeline-messaging.resolver.ts
@@ -10,7 +10,7 @@ import { TimelineMessagingService } from 'src/core/messaging/timeline-messaging.
@Entity({ name: 'timelineThread', schema: 'core' })
@ObjectType('TimelineThread')
-class TimelineThread {
+export class TimelineThread {
@Field()
@Column()
read: boolean;
diff --git a/packages/twenty-server/src/core/messaging/timeline-messaging.service.ts b/packages/twenty-server/src/core/messaging/timeline-messaging.service.ts
index 1e31954a5..46784068e 100644
--- a/packages/twenty-server/src/core/messaging/timeline-messaging.service.ts
+++ b/packages/twenty-server/src/core/messaging/timeline-messaging.service.ts
@@ -1,5 +1,6 @@
import { Injectable } from '@nestjs/common';
+import { TimelineThread } from 'src/core/messaging/timeline-messaging.resolver';
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
import { DataSourceService } from 'src/metadata/data-source/data-source.service';
@@ -10,7 +11,10 @@ export class TimelineMessagingService {
private readonly typeORMService: TypeORMService,
) {}
- async getMessagesFromPersonIds(workspaceId: string, personIds: string[]) {
+ async getMessagesFromPersonIds(
+ workspaceId: string,
+ personIds: string[],
+ ): Promise {
const dataSourceMetadata =
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
workspaceId,
diff --git a/packages/twenty-server/src/workspace/messaging/services/gmail-full-sync.service.ts b/packages/twenty-server/src/workspace/messaging/services/gmail-full-sync.service.ts
index 1fa1f632e..efe8cd2b4 100644
--- a/packages/twenty-server/src/workspace/messaging/services/gmail-full-sync.service.ts
+++ b/packages/twenty-server/src/workspace/messaging/services/gmail-full-sync.service.ts
@@ -30,6 +30,19 @@ export class GmailFullSyncService {
throw new Error('No refresh token found');
}
+ const gmailMessageChannel = await workspaceDataSource?.query(
+ `SELECT * FROM ${dataSourceMetadata.schema}."messageChannel" WHERE "connectedAccountId" = $1 AND "type" = 'email' LIMIT 1`,
+ [connectedAccountId],
+ );
+
+ if (!gmailMessageChannel.length) {
+ throw new Error(
+ `No gmail message channel found for connected account ${connectedAccountId}`,
+ );
+ }
+
+ const gmailMessageChannelId = gmailMessageChannel[0].id;
+
const gmailClient =
await this.gmailClientProvider.getGmailClient(refreshToken);
@@ -48,20 +61,8 @@ export class GmailFullSyncService {
return;
}
- const { savedMessageIds, savedThreadIds } =
- await this.utils.getSavedMessageIdsAndThreadIds(
- messageExternalIds,
- connectedAccountId,
- dataSourceMetadata,
- workspaceDataSource,
- );
-
- const messageIdsToSave = messageExternalIds.filter(
- (messageId) => !savedMessageIds.includes(messageId),
- );
-
const messageQueries =
- this.utils.createQueriesFromMessageIds(messageIdsToSave);
+ this.utils.createQueriesFromMessageIds(messageExternalIds);
const { messages: messagesToSave, errors } =
await this.fetchMessagesByBatchesService.fetchAllMessages(
@@ -69,32 +70,20 @@ export class GmailFullSyncService {
accessToken,
);
- const threads = this.utils.getThreadsFromMessages(messagesToSave);
-
- const threadsToSave = threads.filter(
- (threadId) => !savedThreadIds.includes(threadId.id),
- );
-
- await this.utils.saveMessageThreads(
- threadsToSave,
- dataSourceMetadata,
- workspaceDataSource,
- connectedAccount.id,
- );
+ if (messagesToSave.length === 0) {
+ return;
+ }
await this.utils.saveMessages(
messagesToSave,
dataSourceMetadata,
workspaceDataSource,
connectedAccount,
+ gmailMessageChannelId,
);
if (errors.length) throw new Error('Error fetching messages');
- if (messagesToSave.length === 0) {
- return;
- }
-
const lastModifiedMessageId = messagesData[0].id;
const historyId = messagesToSave.find(
diff --git a/packages/twenty-server/src/workspace/messaging/services/gmail-partial-sync.service.ts b/packages/twenty-server/src/workspace/messaging/services/gmail-partial-sync.service.ts
index 63022acad..0b1a5ff8a 100644
--- a/packages/twenty-server/src/workspace/messaging/services/gmail-partial-sync.service.ts
+++ b/packages/twenty-server/src/workspace/messaging/services/gmail-partial-sync.service.ts
@@ -111,40 +111,24 @@ export class GmailPartialSyncService {
return;
}
+ const gmailMessageChannel = await workspaceDataSource?.query(
+ `SELECT * FROM ${dataSourceMetadata.schema}."messageChannel" WHERE "connectedAccountId" = $1 AND "type" = 'email' LIMIT 1`,
+ [connectedAccountId],
+ );
+
+ if (!gmailMessageChannel.length) {
+ throw new Error(
+ `No gmail message channel found for connected account ${connectedAccountId}`,
+ );
+ }
+
+ const gmailMessageChannelId = gmailMessageChannel[0].id;
+
const { messagesAdded, messagesDeleted } =
await this.getMessageIdsAndThreadIdsFromHistory(history);
- const {
- savedMessageIds: messagesAddedAlreadySaved,
- savedThreadIds: threadsAddedAlreadySaved,
- } = await this.utils.getSavedMessageIdsAndThreadIds(
- messagesAdded,
- connectedAccountId,
- dataSourceMetadata,
- workspaceDataSource,
- );
-
- const messageExternalIdsToSave = messagesAdded.filter(
- (messageId) =>
- !messagesAddedAlreadySaved.includes(messageId) &&
- !messagesDeleted.includes(messageId),
- );
-
- const { savedMessageIds: messagesDeletedAlreadySaved } =
- await this.utils.getSavedMessageIdsAndThreadIds(
- messagesDeleted,
- connectedAccountId,
- dataSourceMetadata,
- workspaceDataSource,
- );
-
- const messageExternalIdsToDelete = messagesDeleted.filter((messageId) =>
- messagesDeletedAlreadySaved.includes(messageId),
- );
-
- const messageQueries = this.utils.createQueriesFromMessageIds(
- messageExternalIdsToSave,
- );
+ const messageQueries =
+ this.utils.createQueriesFromMessageIds(messagesAdded);
const { messages: messagesToSave, errors } =
await this.fetchMessagesByBatchesService.fetchAllMessages(
@@ -152,35 +136,17 @@ export class GmailPartialSyncService {
accessToken,
);
- const threads = this.utils.getThreadsFromMessages(messagesToSave);
-
- const threadsToSave = threads.filter(
- (thread) => !threadsAddedAlreadySaved.includes(thread.id),
- );
-
- await this.utils.saveMessageThreads(
- threadsToSave,
- dataSourceMetadata,
- workspaceDataSource,
- connectedAccount.id,
- );
-
await this.utils.saveMessages(
messagesToSave,
dataSourceMetadata,
workspaceDataSource,
connectedAccount,
+ gmailMessageChannelId,
);
- await this.utils.deleteMessages(
- messageExternalIdsToDelete,
- dataSourceMetadata,
- workspaceDataSource,
- );
-
- await this.utils.deleteEmptyThreads(
+ await this.utils.deleteMessageChannelMessages(
messagesDeleted,
- connectedAccountId,
+ gmailMessageChannelId,
dataSourceMetadata,
workspaceDataSource,
);
diff --git a/packages/twenty-server/src/workspace/messaging/services/gmail-refresh-access-token.service.ts b/packages/twenty-server/src/workspace/messaging/services/gmail-refresh-access-token.service.ts
index fcfbb45c6..cd08d00cb 100644
--- a/packages/twenty-server/src/workspace/messaging/services/gmail-refresh-access-token.service.ts
+++ b/packages/twenty-server/src/workspace/messaging/services/gmail-refresh-access-token.service.ts
@@ -31,7 +31,7 @@ export class GmailRefreshAccessTokenService {
}
const connectedAccounts = await workspaceDataSource?.query(
- `SELECT * FROM ${dataSourceMetadata.schema}."connectedAccount" WHERE "provider" = 'gmail' AND "id" = $1`,
+ `SELECT * FROM ${dataSourceMetadata.schema}."connectedAccount" WHERE "provider" = 'google' AND "id" = $1`,
[connectedAccountId],
);
diff --git a/packages/twenty-server/src/workspace/messaging/services/messaging-utils.service.ts b/packages/twenty-server/src/workspace/messaging/services/messaging-utils.service.ts
index 955c9b103..30d54e122 100644
--- a/packages/twenty-server/src/workspace/messaging/services/messaging-utils.service.ts
+++ b/packages/twenty-server/src/workspace/messaging/services/messaging-utils.service.ts
@@ -10,7 +10,6 @@ import {
GmailMessage,
Participant,
} from 'src/workspace/messaging/types/gmailMessage';
-import { GmailThread } from 'src/workspace/messaging/types/gmailThread';
import { MessageQuery } from 'src/workspace/messaging/types/messageOrThreadQuery';
@Injectable()
@@ -28,137 +27,129 @@ export class MessagingUtilsService {
}));
}
- public getThreadsFromMessages(messages: GmailMessage[]): GmailThread[] {
- return messages.reduce((acc, message) => {
- if (message.externalId === message.messageThreadExternalId) {
- acc.push({
- id: message.messageThreadExternalId,
- subject: message.subject,
- });
- }
-
- return acc;
- }, [] as GmailThread[]);
- }
-
- public async saveMessageThreads(
- threads: GmailThread[],
- dataSourceMetadata: DataSourceEntity,
- workspaceDataSource: DataSource,
- connectedAccountId: string,
- ) {
- const messageChannel = await workspaceDataSource?.query(
- `SELECT * FROM ${dataSourceMetadata.schema}."messageChannel" WHERE "connectedAccountId" = $1`,
- [connectedAccountId],
- );
-
- if (!messageChannel.length) {
- throw new Error('No message channel found for this connected account');
- }
-
- for (const thread of threads) {
- await workspaceDataSource?.query(
- `INSERT INTO ${dataSourceMetadata.schema}."messageThread" ("externalId", "subject", "messageChannelId", "visibility") VALUES ($1, $2, $3, $4)`,
- [thread.id, thread.subject, messageChannel[0].id, 'default'],
- );
- }
- }
-
public async saveMessages(
messages: GmailMessage[],
dataSourceMetadata: DataSourceEntity,
workspaceDataSource: DataSource,
connectedAccount,
+ gmailMessageChannelId: string,
) {
for (const message of messages) {
- const {
- externalId,
- headerMessageId,
- subject,
- messageThreadExternalId,
- internalDate,
- fromHandle,
- fromDisplayName,
- participants,
- text,
- } = message;
-
- const receivedAt = new Date(parseInt(internalDate));
-
- const messageThread = await workspaceDataSource?.query(
- `SELECT * FROM ${dataSourceMetadata.schema}."messageThread" WHERE "externalId" = $1`,
- [messageThreadExternalId],
- );
-
- const messageId = v4();
-
- const person = await workspaceDataSource?.query(
- `SELECT * FROM ${dataSourceMetadata.schema}."person" WHERE "email" = $1`,
- [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", "receivedAt", "messageThreadId", "direction", "body") VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
- [
- messageId,
- externalId,
- headerMessageId,
- subject,
- receivedAt,
- messageThread[0]?.id,
- messageDirection,
- text,
- ],
- );
+ const savedOrExistingMessageThreadId =
+ await this.saveMessageThreadOrReturnExistingMessageThread(
+ message.messageThreadExternalId,
+ dataSourceMetadata,
+ workspaceDataSource,
+ );
+
+ const savedOrExistingMessageId =
+ await this.saveMessageOrReturnExistingMessage(
+ message,
+ savedOrExistingMessageThreadId,
+ connectedAccount,
+ dataSourceMetadata,
+ manager,
+ );
await manager.query(
- `INSERT INTO ${dataSourceMetadata.schema}."messageParticipant" ("messageId", "role", "handle", "displayName", "personId", "workspaceMemberId") VALUES ($1, $2, $3, $4, $5, $6)`,
+ `INSERT INTO ${dataSourceMetadata.schema}."messageChannelMessage" ("messageChannelId", "messageId", "messageExternalId", "messageThreadId", "messageThreadExternalId") VALUES ($1, $2, $3, $4, $5)`,
[
- messageId,
- 'from',
- fromHandle,
- fromDisplayName,
- personId,
- workspaceMemberId,
+ gmailMessageChannelId,
+ savedOrExistingMessageId,
+ message.externalId,
+ savedOrExistingMessageThreadId,
+ message.messageThreadExternalId,
],
);
-
- await this.saveMessageParticipants(
- participants,
- dataSourceMetadata,
- messageId,
- manager,
- );
});
}
}
- public async saveMessageParticipants(
- participants: Participant[],
+ private async saveMessageOrReturnExistingMessage(
+ message: GmailMessage,
+ messageThreadId: string,
+ connectedAccount,
dataSourceMetadata: DataSourceEntity,
+ manager: EntityManager,
+ ): Promise {
+ const existingMessages = await manager.query(
+ `SELECT "message"."id" FROM ${dataSourceMetadata.schema}."message" WHERE ${dataSourceMetadata.schema}."message"."headerMessageId" = $1 LIMIT 1`,
+ [message.headerMessageId],
+ );
+ const existingMessageId: string = existingMessages[0]?.id;
+
+ if (existingMessageId) {
+ return Promise.resolve(existingMessageId);
+ }
+
+ const newMessageId = v4();
+
+ const messageDirection =
+ connectedAccount.handle === message.fromHandle ? 'outgoing' : 'incoming';
+
+ const receivedAt = new Date(parseInt(message.internalDate));
+
+ await manager.query(
+ `INSERT INTO ${dataSourceMetadata.schema}."message" ("id", "headerMessageId", "subject", "receivedAt", "direction", "messageThreadId", "body") VALUES ($1, $2, $3, $4, $5, $6, $7)`,
+ [
+ newMessageId,
+ message.headerMessageId,
+ message.subject,
+ receivedAt,
+ messageDirection,
+ messageThreadId,
+ message.text,
+ ],
+ );
+
+ await this.saveMessageParticipants(
+ message.participants,
+ newMessageId,
+ dataSourceMetadata,
+ manager,
+ );
+
+ return Promise.resolve(newMessageId);
+ }
+
+ private async saveMessageThreadOrReturnExistingMessageThread(
+ messageThreadExternalId: string,
+ dataSourceMetadata: DataSourceEntity,
+ workspaceDataSource: DataSource,
+ ) {
+ const existingMessageThreads = await workspaceDataSource?.query(
+ `SELECT "messageChannelMessage"."messageThreadId" FROM ${dataSourceMetadata.schema}."messageChannelMessage" WHERE "messageThreadExternalId" = $1 LIMIT 1`,
+ [messageThreadExternalId],
+ );
+
+ const existingMessageThread = existingMessageThreads[0]?.messageThreadId;
+
+ if (existingMessageThread) {
+ return Promise.resolve(existingMessageThread);
+ }
+
+ const newMessageThreadId = v4();
+
+ await workspaceDataSource?.query(
+ `INSERT INTO ${dataSourceMetadata.schema}."messageThread" ("id") VALUES ($1)`,
+ [newMessageThreadId],
+ );
+
+ return Promise.resolve(newMessageThreadId);
+ }
+
+ private async saveMessageParticipants(
+ participants: Participant[],
messageId: string,
+ dataSourceMetadata: DataSourceEntity,
manager: EntityManager,
): Promise {
if (!participants) return;
for (const participant of participants) {
const participantPerson = await manager.query(
- `SELECT * FROM ${dataSourceMetadata.schema}."person" WHERE "email" = $1`,
+ `SELECT "person"."id" FROM ${dataSourceMetadata.schema}."person" WHERE "email" = $1 LIMIT 1`,
[participant.handle],
);
@@ -167,7 +158,8 @@ export class MessagingUtilsService {
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`,
+ WHERE ${dataSourceMetadata.schema}."connectedAccount"."handle" = $1
+ LIMIT 1`,
[participant.handle],
);
@@ -187,41 +179,16 @@ export class MessagingUtilsService {
}
}
- public async getSavedMessageIdsAndThreadIds(
- messageEternalIds: string[],
+ public async deleteMessageChannelMessages(
+ messageExternalIds: string[],
connectedAccountId: string,
dataSourceMetadata: DataSourceEntity,
workspaceDataSource: DataSource,
- ): Promise<{
- savedMessageIds: string[];
- savedThreadIds: string[];
- }> {
- const messageIdsInDatabase: {
- messageExternalId: string;
- messageThreadExternalId: string;
- }[] = await workspaceDataSource?.query(
- `SELECT message."externalId" AS "messageExternalId",
- "messageThread"."externalId" AS "messageThreadExternalId"
- FROM ${dataSourceMetadata.schema}."message" message
- LEFT JOIN ${dataSourceMetadata.schema}."messageThread" "messageThread" ON message."messageThreadId" = "messageThread"."id"
- LEFT JOIN ${dataSourceMetadata.schema}."messageChannel" ON "messageThread"."messageChannelId" = ${dataSourceMetadata.schema}."messageChannel"."id"
- WHERE ${dataSourceMetadata.schema}."messageChannel"."connectedAccountId" = $1
- AND message."externalId" = ANY($2)`,
- [connectedAccountId, messageEternalIds],
+ ) {
+ await workspaceDataSource?.query(
+ `DELETE FROM ${dataSourceMetadata.schema}."messageChannelMessage" WHERE "messageExternalId" = ANY($1) AND "messageChannelId" = $2`,
+ [messageExternalIds, connectedAccountId],
);
-
- return {
- savedMessageIds: messageIdsInDatabase.map(
- (message) => message.messageExternalId,
- ),
- savedThreadIds: [
- ...new Set(
- messageIdsInDatabase.map(
- (message) => message.messageThreadExternalId,
- ),
- ),
- ],
- };
}
public async getConnectedAccountsFromWorkspaceId(
@@ -240,7 +207,7 @@ export class MessagingUtilsService {
}
const connectedAccounts = await workspaceDataSource?.query(
- `SELECT * FROM ${dataSourceMetadata.schema}."connectedAccount" WHERE "provider" = 'gmail'`,
+ `SELECT * FROM ${dataSourceMetadata.schema}."connectedAccount" WHERE "provider" = 'google'`,
);
if (!connectedAccounts || connectedAccounts.length === 0) {
@@ -271,7 +238,7 @@ export class MessagingUtilsService {
}
const connectedAccounts = await workspaceDataSource?.query(
- `SELECT * FROM ${dataSourceMetadata.schema}."connectedAccount" WHERE "provider" = 'gmail' AND "id" = $1`,
+ `SELECT * FROM ${dataSourceMetadata.schema}."connectedAccount" WHERE "provider" = 'google' AND "id" = $1`,
[connectedAccountId],
);
@@ -297,50 +264,4 @@ export class MessagingUtilsService {
[historyId, connectedAccountId],
);
}
-
- public async deleteMessages(
- messageIds: string[],
- dataSourceMetadata: DataSourceEntity,
- workspaceDataSource: DataSource,
- ) {
- if (!messageIds || messageIds.length === 0) {
- return;
- }
-
- await workspaceDataSource?.query(
- `DELETE FROM ${dataSourceMetadata.schema}."message" WHERE "externalId" = ANY($1)`,
- [messageIds],
- );
- }
-
- public async deleteEmptyThreads(
- messageIds: string[],
- connectedAccountId: string,
- dataSourceMetadata: DataSourceEntity,
- workspaceDataSource: DataSource,
- ) {
- const messageThreadsToDelete = await workspaceDataSource?.query(
- `SELECT "messageThread"."id" FROM ${dataSourceMetadata.schema}."messageThread" "messageThread"
- LEFT JOIN ${dataSourceMetadata.schema}."message" message ON "messageThread"."id" = message."messageThreadId"
- LEFT JOIN ${dataSourceMetadata.schema}."messageChannel" ON "messageThread"."messageChannelId" = ${dataSourceMetadata.schema}."messageChannel"."id"
- WHERE "messageThread"."externalId" = ANY($1)
- AND ${dataSourceMetadata.schema}."messageChannel"."connectedAccountId" = $2
- GROUP BY "messageThread"."id"
- HAVING COUNT(message."id") = 0`,
- [messageIds, connectedAccountId],
- );
-
- if (!messageThreadsToDelete || messageThreadsToDelete.length === 0) {
- return;
- }
-
- const messageThreadIdsToDelete = messageThreadsToDelete.map(
- (messageThread) => messageThread.id,
- );
-
- await workspaceDataSource?.query(
- `DELETE FROM ${dataSourceMetadata.schema}."messageThread" WHERE "id" = ANY($1)`,
- [messageThreadIdsToDelete],
- );
- }
}
diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/index.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/index.ts
index ab09b5f9e..a31204069 100644
--- a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/index.ts
+++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/index.ts
@@ -6,6 +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 { 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 { MessageChannelMessageObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-channel-message.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 { MessageThreadObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-thread.object-metadata';
@@ -42,4 +43,5 @@ export const standardObjectMetadata = [
MessageObjectMetadata,
MessageChannelObjectMetadata,
MessageParticipantObjectMetadata,
+ MessageChannelMessageObjectMetadata,
];
diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message-channel-message.object-metadata.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message-channel-message.object-metadata.ts
new file mode 100644
index 000000000..da585ad61
--- /dev/null
+++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message-channel-message.object-metadata.ts
@@ -0,0 +1,71 @@
+import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
+import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
+import { Gate } from 'src/workspace/workspace-sync-metadata/decorators/gate.decorator';
+import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.decorator';
+import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
+import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator';
+import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata';
+import { MessageChannelObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-channel.object-metadata';
+import { MessageThreadObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-thread.object-metadata';
+import { MessageObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message.object-metadata';
+
+@ObjectMetadata({
+ namePlural: 'messageChannelMessages',
+ labelSingular: 'Message Channel Message',
+ labelPlural: 'Message Channel Messages',
+ description: 'Message Synced with a Message Channel',
+ icon: 'IconMessage',
+})
+@Gate({
+ featureFlag: 'IS_MESSAGING_ENABLED',
+})
+@IsSystem()
+export class MessageChannelMessageObjectMetadata extends BaseObjectMetadata {
+ @FieldMetadata({
+ type: FieldMetadataType.RELATION,
+ label: 'Message Channel Id',
+ description: 'Message Channel Id',
+ icon: 'IconHash',
+ joinColumn: 'messageChannelId',
+ })
+ @IsNullable()
+ messageChannel: MessageChannelObjectMetadata;
+
+ @FieldMetadata({
+ type: FieldMetadataType.RELATION,
+ label: 'Message Id',
+ description: 'Message Id',
+ icon: 'IconHash',
+ joinColumn: 'messageId',
+ })
+ @IsNullable()
+ message: MessageObjectMetadata;
+
+ @FieldMetadata({
+ type: FieldMetadataType.TEXT,
+ label: 'Message External Id',
+ description: 'Message id from the messaging provider',
+ icon: 'IconHash',
+ })
+ @IsNullable()
+ messageExternalId: string;
+
+ @FieldMetadata({
+ type: FieldMetadataType.RELATION,
+ label: 'Message Thread Id',
+ description: 'Message Thread Id',
+ icon: 'IconHash',
+ joinColumn: 'messageThreadId',
+ })
+ @IsNullable()
+ messageThread: MessageThreadObjectMetadata;
+
+ @FieldMetadata({
+ type: FieldMetadataType.TEXT,
+ label: 'Thread External Id',
+ description: 'Thread id from the messaging provider',
+ icon: 'IconHash',
+ })
+ @IsNullable()
+ messageThreadExternalId: string;
+}
diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message-channel.object-metadata.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message-channel.object-metadata.ts
index 3d0cec4d8..470ea4f13 100644
--- a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message-channel.object-metadata.ts
+++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message-channel.object-metadata.ts
@@ -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 { 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 { MessageThreadObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-thread.object-metadata';
+import { MessageChannelMessageObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-channel-message.object-metadata';
@ObjectMetadata({
namePlural: 'messageChannels',
@@ -23,12 +23,21 @@ import { MessageThreadObjectMetadata } from 'src/workspace/workspace-sync-metada
@IsSystem()
export class MessageChannelObjectMetadata extends BaseObjectMetadata {
@FieldMetadata({
- // This will be a type select later: metadata, subject, share_everything
- type: FieldMetadataType.TEXT,
+ type: FieldMetadataType.SELECT,
label: 'Visibility',
description: 'Visibility',
icon: 'IconEyeglass',
- defaultValue: { value: 'metadata' },
+ options: [
+ { value: 'metadata', label: 'Metadata', position: 0, color: 'green' },
+ { value: 'subject', label: 'Subject', position: 1, color: 'blue' },
+ {
+ value: 'share_everything',
+ label: 'Share Everything',
+ position: 2,
+ color: 'orange',
+ },
+ ],
+ defaultValue: { value: 'share_everything' },
})
visibility: string;
@@ -50,24 +59,28 @@ export class MessageChannelObjectMetadata extends BaseObjectMetadata {
connectedAccount: ConnectedAccountObjectMetadata;
@FieldMetadata({
- // This will be a type select later : email, sms, chat
- type: FieldMetadataType.TEXT,
+ type: FieldMetadataType.SELECT,
label: 'Type',
- description: 'Type',
+ description: 'Channel Type',
icon: 'IconMessage',
+ options: [
+ { value: 'email', label: 'Email', position: 0, color: 'green' },
+ { value: 'sms', label: 'SMS', position: 1, color: 'blue' },
+ ],
+ defaultValue: { value: 'email' },
})
type: string;
@FieldMetadata({
type: FieldMetadataType.RELATION,
- label: 'Message Threads',
- description: 'Threads from the channel.',
+ label: 'Message Channel Syncs',
+ description: 'Messages from the channel.',
icon: 'IconMessage',
})
@RelationMetadata({
type: RelationMetadataType.ONE_TO_MANY,
- objectName: 'messageThread',
+ objectName: 'messageChannelMessage',
})
@IsNullable()
- messageThreads: MessageThreadObjectMetadata[];
+ messageChannelMessage: MessageChannelMessageObjectMetadata[];
}
diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message-thread.object-metadata.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message-thread.object-metadata.ts
index c5e1edc60..cb7e094bd 100644
--- a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message-thread.object-metadata.ts
+++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message-thread.object-metadata.ts
@@ -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 { 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 { MessageChannelObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-channel.object-metadata';
+import { MessageChannelMessageObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-channel-message.object-metadata';
import { MessageObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message.object-metadata';
@ObjectMetadata({
@@ -22,43 +22,6 @@ import { MessageObjectMetadata } from 'src/workspace/workspace-sync-metadata/sta
})
@IsSystem()
export class MessageThreadObjectMetadata extends BaseObjectMetadata {
- @FieldMetadata({
- // will be an array
- type: FieldMetadataType.TEXT,
- label: 'External Id',
- description: 'Thread id from the messaging provider',
- icon: 'IconMessage',
- })
- externalId: string;
-
- @FieldMetadata({
- type: FieldMetadataType.TEXT,
- label: 'Subject',
- description: 'Subject',
- icon: 'IconMessage',
- })
- subject: string;
-
- @FieldMetadata({
- type: FieldMetadataType.RELATION,
- label: 'Message Channel Id',
- description: 'Message Channel Id',
- icon: 'IconHash',
- joinColumn: 'messageChannelId',
- })
- @IsNullable()
- messageChannel: MessageChannelObjectMetadata;
-
- @FieldMetadata({
- // This will be a type select later: default, subject, share_everything
- type: FieldMetadataType.TEXT,
- label: 'Visibility',
- description: 'Visibility',
- icon: 'IconEyeglass',
- defaultValue: { value: 'default' },
- })
- visibility: string;
-
@FieldMetadata({
type: FieldMetadataType.RELATION,
label: 'Messages',
@@ -71,4 +34,17 @@ export class MessageThreadObjectMetadata extends BaseObjectMetadata {
})
@IsNullable()
messages: MessageObjectMetadata[];
+
+ @FieldMetadata({
+ type: FieldMetadataType.RELATION,
+ label: 'Message Channel Syncs',
+ description: 'Messages from the channel.',
+ icon: 'IconMessage',
+ })
+ @RelationMetadata({
+ type: RelationMetadataType.ONE_TO_MANY,
+ objectName: 'messageChannelMessage',
+ })
+ @IsNullable()
+ messageChannelMessage: MessageChannelMessageObjectMetadata[];
}
diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message.object-metadata.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message.object-metadata.ts
index 7bd755dc9..f1c65732e 100644
--- a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message.object-metadata.ts
+++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/message.object-metadata.ts
@@ -7,6 +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 { 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 { MessageChannelMessageObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-channel-message.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';
@@ -22,15 +23,6 @@ import { MessageThreadObjectMetadata } from 'src/workspace/workspace-sync-metada
})
@IsSystem()
export class MessageObjectMetadata extends BaseObjectMetadata {
- @FieldMetadata({
- // will be an array
- type: FieldMetadataType.TEXT,
- label: 'External Id',
- description: 'Message id from the messaging provider',
- icon: 'IconHash',
- })
- externalId: string;
-
@FieldMetadata({
type: FieldMetadataType.TEXT,
label: 'Header message Id',
@@ -50,11 +42,14 @@ export class MessageObjectMetadata extends BaseObjectMetadata {
messageThread: MessageThreadObjectMetadata;
@FieldMetadata({
- // will be a select later: incoming, outgoing
- type: FieldMetadataType.TEXT,
+ type: FieldMetadataType.SELECT,
label: 'Direction',
- description: 'Direction',
+ description: 'Message Direction',
icon: 'IconDirection',
+ options: [
+ { value: 'incoming', label: 'Incoming', position: 0, color: 'green' },
+ { value: 'outgoing', label: 'Outgoing', position: 1, color: 'blue' },
+ ],
defaultValue: { value: 'incoming' },
})
direction: string;
@@ -97,4 +92,17 @@ export class MessageObjectMetadata extends BaseObjectMetadata {
})
@IsNullable()
messageParticipants: MessageParticipantObjectMetadata[];
+
+ @FieldMetadata({
+ type: FieldMetadataType.RELATION,
+ label: 'Message Channel Syncs',
+ description: 'Messages from the channel.',
+ icon: 'IconMessage',
+ })
+ @RelationMetadata({
+ type: RelationMetadataType.ONE_TO_MANY,
+ objectName: 'messageChannelMessage',
+ })
+ @IsNullable()
+ messageChannelMessage: MessageChannelMessageObjectMetadata[];
}