message channel change 2 (#15269)
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
+50
@@ -65,6 +65,12 @@ export enum MessageFolderImportPolicy {
|
||||
SELECTED_FOLDERS = 'SELECTED_FOLDERS',
|
||||
}
|
||||
|
||||
export enum MessageChannelPendingGroupEmailsAction {
|
||||
GROUP_EMAILS_DELETION = 'GROUP_EMAILS_DELETION',
|
||||
GROUP_EMAILS_IMPORT = 'GROUP_EMAILS_IMPORT',
|
||||
NONE = 'NONE',
|
||||
}
|
||||
|
||||
registerEnumType(MessageChannelVisibility, {
|
||||
name: 'MessageChannelVisibility',
|
||||
});
|
||||
@@ -89,6 +95,10 @@ registerEnumType(MessageFolderImportPolicy, {
|
||||
name: 'MessageFolderImportPolicy',
|
||||
});
|
||||
|
||||
registerEnumType(MessageChannelPendingGroupEmailsAction, {
|
||||
name: 'MessageChannelPendingGroupEmailsAction',
|
||||
});
|
||||
|
||||
@WorkspaceEntity({
|
||||
standardId: STANDARD_OBJECT_IDS.messageChannel,
|
||||
namePlural: 'messageChannels',
|
||||
@@ -249,6 +259,46 @@ export class MessageChannelWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
})
|
||||
excludeGroupEmails: boolean;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: MESSAGE_CHANNEL_STANDARD_FIELD_IDS.syncAllFolders,
|
||||
type: FieldMetadataType.BOOLEAN,
|
||||
label: msg`Sync all folders`,
|
||||
description: msg`Sync all folders including new ones`,
|
||||
icon: 'IconFolders',
|
||||
defaultValue: true,
|
||||
})
|
||||
syncAllFolders: boolean;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: MESSAGE_CHANNEL_STANDARD_FIELD_IDS.pendingGroupEmailsAction,
|
||||
type: FieldMetadataType.SELECT,
|
||||
label: msg`Pending group emails action`,
|
||||
description: msg`Pending action for group emails`,
|
||||
icon: 'IconUsersGroup',
|
||||
options: [
|
||||
{
|
||||
value: MessageChannelPendingGroupEmailsAction.GROUP_EMAILS_DELETION,
|
||||
label: 'Group emails deletion',
|
||||
position: 0,
|
||||
color: 'red',
|
||||
},
|
||||
{
|
||||
value: MessageChannelPendingGroupEmailsAction.GROUP_EMAILS_IMPORT,
|
||||
label: 'Group emails import',
|
||||
position: 1,
|
||||
color: 'green',
|
||||
},
|
||||
{
|
||||
value: MessageChannelPendingGroupEmailsAction.NONE,
|
||||
label: 'None',
|
||||
position: 2,
|
||||
color: 'blue',
|
||||
},
|
||||
],
|
||||
defaultValue: `'${MessageChannelPendingGroupEmailsAction.NONE}'`,
|
||||
})
|
||||
pendingGroupEmailsAction: MessageChannelPendingGroupEmailsAction;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: MESSAGE_CHANNEL_STANDARD_FIELD_IDS.isSyncEnabled,
|
||||
type: FieldMetadataType.BOOLEAN,
|
||||
|
||||
+42
@@ -1,3 +1,5 @@
|
||||
import { registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { Relation } from 'typeorm';
|
||||
@@ -18,6 +20,16 @@ import { STANDARD_OBJECT_ICONS } from 'src/engine/workspace-manager/workspace-sy
|
||||
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
||||
import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
|
||||
export enum MessageFolderPendingSyncAction {
|
||||
FOLDER_DELETION = 'FOLDER_DELETION',
|
||||
FOLDER_IMPORT = 'FOLDER_IMPORT',
|
||||
NONE = 'NONE',
|
||||
}
|
||||
|
||||
registerEnumType(MessageFolderPendingSyncAction, {
|
||||
name: 'MessageFolderPendingSyncAction',
|
||||
});
|
||||
|
||||
@WorkspaceEntity({
|
||||
standardId: STANDARD_OBJECT_IDS.messageFolder,
|
||||
namePlural: 'messageFolders',
|
||||
@@ -90,6 +102,36 @@ export class MessageFolderWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
@WorkspaceIsNullable()
|
||||
externalId: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: MESSAGE_FOLDER_STANDARD_FIELD_IDS.pendingSyncAction,
|
||||
type: FieldMetadataType.SELECT,
|
||||
label: msg`Pending Sync Action`,
|
||||
description: msg`Pending action for folder sync`,
|
||||
icon: 'IconReload',
|
||||
options: [
|
||||
{
|
||||
value: MessageFolderPendingSyncAction.FOLDER_DELETION,
|
||||
label: 'Folder deletion',
|
||||
position: 0,
|
||||
color: 'red',
|
||||
},
|
||||
{
|
||||
value: MessageFolderPendingSyncAction.FOLDER_IMPORT,
|
||||
label: 'Folder import',
|
||||
position: 1,
|
||||
color: 'green',
|
||||
},
|
||||
{
|
||||
value: MessageFolderPendingSyncAction.NONE,
|
||||
label: 'None',
|
||||
position: 2,
|
||||
color: 'blue',
|
||||
},
|
||||
],
|
||||
defaultValue: `'${MessageFolderPendingSyncAction.NONE}'`,
|
||||
})
|
||||
pendingSyncAction: MessageFolderPendingSyncAction;
|
||||
|
||||
@WorkspaceJoinColumn('messageChannel')
|
||||
messageChannelId: string;
|
||||
}
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { MessageChannelUpdateOnePreQueryHook } from 'src/modules/messaging/message-channel-manager/query-hooks/message-channel-update-one.pre-query.hook';
|
||||
|
||||
@Module({
|
||||
providers: [MessageChannelUpdateOnePreQueryHook],
|
||||
exports: [MessageChannelUpdateOnePreQueryHook],
|
||||
})
|
||||
export class MessageChannelQueryHookModule {}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { assertIsDefinedOrThrow } from 'twenty-shared/utils';
|
||||
import { Not } from 'typeorm';
|
||||
|
||||
import { type WorkspacePreQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
|
||||
import { type UpdateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
|
||||
import {
|
||||
WorkspaceQueryRunnerException,
|
||||
WorkspaceQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception';
|
||||
import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import {
|
||||
MessageChannelPendingGroupEmailsAction,
|
||||
MessageChannelSyncStage,
|
||||
type MessageChannelWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import {
|
||||
MessageFolderPendingSyncAction,
|
||||
type MessageFolderWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
|
||||
const ONGOING_SYNC_STAGES = [
|
||||
MessageChannelSyncStage.MESSAGE_LIST_FETCH_ONGOING,
|
||||
MessageChannelSyncStage.MESSAGES_IMPORT_ONGOING,
|
||||
];
|
||||
|
||||
@WorkspaceQueryHook(`messageChannel.updateOne`)
|
||||
export class MessageChannelUpdateOnePreQueryHook
|
||||
implements WorkspacePreQueryHookInstance
|
||||
{
|
||||
constructor(
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {}
|
||||
|
||||
async execute(
|
||||
authContext: AuthContext,
|
||||
_objectName: string,
|
||||
payload: UpdateOneResolverArgs<MessageChannelWorkspaceEntity>,
|
||||
): Promise<UpdateOneResolverArgs<MessageChannelWorkspaceEntity>> {
|
||||
const workspace = authContext.workspace;
|
||||
|
||||
assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError);
|
||||
|
||||
const messageChannelRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageChannelWorkspaceEntity>(
|
||||
workspace.id,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
const messageChannel = await messageChannelRepository.findOne({
|
||||
where: { id: payload.id },
|
||||
});
|
||||
|
||||
if (!messageChannel) {
|
||||
throw new WorkspaceQueryRunnerException(
|
||||
'Message channel not found',
|
||||
WorkspaceQueryRunnerExceptionCode.DATA_NOT_FOUND,
|
||||
{
|
||||
userFriendlyMessage: msg`Message channel not found`,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const isSyncOngoing = ONGOING_SYNC_STAGES.includes(
|
||||
messageChannel.syncStage,
|
||||
);
|
||||
|
||||
const messageFolderRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<MessageFolderWorkspaceEntity>(
|
||||
workspace.id,
|
||||
'messageFolder',
|
||||
);
|
||||
|
||||
const folderWithPendingAction = await messageFolderRepository.findOne({
|
||||
where: {
|
||||
messageChannelId: messageChannel.id,
|
||||
pendingSyncAction: Not(MessageFolderPendingSyncAction.NONE),
|
||||
},
|
||||
});
|
||||
|
||||
const hasPendingFolderActions = !!folderWithPendingAction;
|
||||
|
||||
const hasPendingGroupEmailsAction =
|
||||
messageChannel.pendingGroupEmailsAction !==
|
||||
MessageChannelPendingGroupEmailsAction.NONE;
|
||||
|
||||
if (
|
||||
isSyncOngoing &&
|
||||
(hasPendingFolderActions || hasPendingGroupEmailsAction)
|
||||
) {
|
||||
throw new WorkspaceQueryRunnerException(
|
||||
'Cannot update message channel while sync is ongoing with pending actions',
|
||||
WorkspaceQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
|
||||
{
|
||||
userFriendlyMessage: msg`Cannot update message channel while sync is ongoing. Please wait for the sync to complete.`,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
}
|
||||
+28
-10
@@ -81,13 +81,19 @@ export class ImapGetAllFoldersService implements MessageFolderDriver {
|
||||
for (const mailbox of validMailboxes) {
|
||||
const isInbox = await this.isInboxFolder(mailbox);
|
||||
const uidValidity = await this.getUidValidity(client, mailbox);
|
||||
const standardFolder = getStandardFolderByRegex(mailbox.path);
|
||||
const isSynced = this.shouldSyncByDefault(
|
||||
mailbox,
|
||||
standardFolder,
|
||||
isInbox,
|
||||
);
|
||||
|
||||
folders.push({
|
||||
externalId: uidValidity
|
||||
? `${mailbox.path}:${uidValidity}`
|
||||
: mailbox.path,
|
||||
name: mailbox.path,
|
||||
isSynced: isInbox,
|
||||
isSynced,
|
||||
isSentFolder: false,
|
||||
});
|
||||
}
|
||||
@@ -126,23 +132,35 @@ export class ImapGetAllFoldersService implements MessageFolderDriver {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private shouldSyncByDefault(
|
||||
mailbox: ListResponse,
|
||||
standardFolder: StandardFolder | null,
|
||||
isInbox: boolean,
|
||||
): boolean {
|
||||
if (
|
||||
mailbox.specialUse === '\\Drafts' ||
|
||||
mailbox.specialUse === '\\Trash' ||
|
||||
mailbox.specialUse === '\\Junk'
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
const standardFolder = getStandardFolderByRegex(mailbox.path);
|
||||
|
||||
if (!standardFolder) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
standardFolder !== StandardFolder.SENT &&
|
||||
standardFolder !== StandardFolder.INBOX
|
||||
);
|
||||
if (
|
||||
standardFolder === StandardFolder.DRAFTS ||
|
||||
standardFolder === StandardFolder.TRASH ||
|
||||
standardFolder === StandardFolder.JUNK
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isInbox) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async getUidValidity(
|
||||
|
||||
+12
-17
@@ -67,18 +67,13 @@ export class MicrosoftGetAllFoldersService implements MessageFolderDriver {
|
||||
}
|
||||
|
||||
const standardFolder = getStandardFolderByRegex(folder.displayName);
|
||||
|
||||
if (this.shouldExcludeFolder(standardFolder)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const isInbox = this.isInboxFolder(standardFolder);
|
||||
const isSentFolder = this.isSentFolder(standardFolder);
|
||||
const isSynced = this.shouldSyncByDefault(standardFolder);
|
||||
|
||||
folderInfos.push({
|
||||
externalId: folder.id,
|
||||
name: folder.displayName,
|
||||
isSynced: isInbox || isSentFolder,
|
||||
isSynced,
|
||||
isSentFolder,
|
||||
});
|
||||
}
|
||||
@@ -98,19 +93,19 @@ export class MicrosoftGetAllFoldersService implements MessageFolderDriver {
|
||||
}
|
||||
}
|
||||
|
||||
private isInboxFolder(standardFolder: StandardFolder | null): boolean {
|
||||
return standardFolder === StandardFolder.INBOX;
|
||||
}
|
||||
|
||||
private isSentFolder(standardFolder: StandardFolder | null): boolean {
|
||||
return standardFolder === StandardFolder.SENT;
|
||||
}
|
||||
|
||||
private shouldExcludeFolder(standardFolder: StandardFolder | null): boolean {
|
||||
return (
|
||||
standardFolder !== null &&
|
||||
standardFolder !== StandardFolder.SENT &&
|
||||
standardFolder !== StandardFolder.INBOX
|
||||
);
|
||||
private shouldSyncByDefault(standardFolder: StandardFolder | null): boolean {
|
||||
if (
|
||||
standardFolder === StandardFolder.JUNK ||
|
||||
standardFolder === StandardFolder.DRAFTS ||
|
||||
standardFolder === StandardFolder.TRASH
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { MessagingBlocklistManagerModule } from 'src/modules/messaging/blocklist-manager/messaging-blocklist-manager.module';
|
||||
import { MessageChannelQueryHookModule } from 'src/modules/messaging/message-channel-manager/query-hooks/message-channel-query-hook.module';
|
||||
import { MessagingMessageCleanerModule } from 'src/modules/messaging/message-cleaner/messaging-message-cleaner.module';
|
||||
import { MessagingImportManagerModule } from 'src/modules/messaging/message-import-manager/messaging-import-manager.module';
|
||||
import { MessageParticipantManagerModule } from 'src/modules/messaging/message-participant-manager/message-participant-manager.module';
|
||||
@@ -13,6 +14,7 @@ import { MessagingMonitoringModule } from 'src/modules/messaging/monitoring/mess
|
||||
MessageParticipantManagerModule,
|
||||
MessagingBlocklistManagerModule,
|
||||
MessagingMonitoringModule,
|
||||
MessageChannelQueryHookModule,
|
||||
],
|
||||
providers: [],
|
||||
exports: [MessagingImportManagerModule],
|
||||
|
||||
Reference in New Issue
Block a user