Remove connected account feature flag (#19286)
Co-authored-by: martmull <martmull@hotmail.fr> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions <github-actions@twenty.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
+57
-30
@@ -1,31 +1,24 @@
|
||||
import { Scope } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { type ObjectRecordCreateEvent } from 'twenty-shared/database-events';
|
||||
import { MessageParticipantRole } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
And,
|
||||
Any,
|
||||
type FindManyOptions,
|
||||
ILike,
|
||||
In,
|
||||
IsNull,
|
||||
Not,
|
||||
Or,
|
||||
} from 'typeorm';
|
||||
|
||||
import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { And, Any, ILike, In, Not, Or, Repository } from 'typeorm';
|
||||
|
||||
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
|
||||
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
|
||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { type WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event-batch.type';
|
||||
import { type BlocklistWorkspaceEntity } from 'src/modules/blocklist/standard-objects/blocklist.workspace-entity';
|
||||
import { type MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity';
|
||||
import { MessagingMessageCleanerService } from 'src/modules/messaging/message-cleaner/services/messaging-message-cleaner.service';
|
||||
import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
|
||||
export type BlocklistItemDeleteMessagesJobData = WorkspaceEventBatch<
|
||||
ObjectRecordCreateEvent<BlocklistWorkspaceEntity>
|
||||
@@ -39,7 +32,12 @@ export class BlocklistItemDeleteMessagesJob {
|
||||
constructor(
|
||||
private readonly threadCleanerService: MessagingMessageCleanerService,
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
@InjectRepository(MessageChannelEntity)
|
||||
private readonly messageChannelRepository: Repository<MessageChannelEntity>,
|
||||
@InjectRepository(ConnectedAccountEntity)
|
||||
private readonly connectedAccountRepository: Repository<ConnectedAccountEntity>,
|
||||
@InjectRepository(UserWorkspaceEntity)
|
||||
private readonly userWorkspaceRepository: Repository<UserWorkspaceEntity>,
|
||||
) {}
|
||||
|
||||
@Process(BlocklistItemDeleteMessagesJob.name)
|
||||
@@ -90,6 +88,12 @@ export class BlocklistItemDeleteMessagesJob {
|
||||
'messageChannelMessageAssociation',
|
||||
);
|
||||
|
||||
const workspaceMemberRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkspaceMemberWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workspaceMember',
|
||||
);
|
||||
|
||||
for (const workspaceMemberId of handlesToDeleteByWorkspaceMemberIdMap.keys()) {
|
||||
const handles =
|
||||
handlesToDeleteByWorkspaceMemberIdMap.get(workspaceMemberId);
|
||||
@@ -103,23 +107,46 @@ export class BlocklistItemDeleteMessagesJob {
|
||||
MessageParticipantRole.TO,
|
||||
] as const;
|
||||
|
||||
const messageChannels =
|
||||
await this.messageChannelDataAccessService.findMany(workspaceId, {
|
||||
select: {
|
||||
id: true,
|
||||
handle: true,
|
||||
connectedAccount: {
|
||||
handleAliases: true,
|
||||
},
|
||||
const workspaceMember = await workspaceMemberRepository.findOne({
|
||||
where: { id: workspaceMemberId },
|
||||
});
|
||||
|
||||
if (!workspaceMember) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const userWorkspace = await this.userWorkspaceRepository.findOne({
|
||||
where: { userId: workspaceMember.userId, workspaceId },
|
||||
});
|
||||
|
||||
if (!userWorkspace) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const connectedAccounts = await this.connectedAccountRepository.find({
|
||||
where: { userWorkspaceId: userWorkspace.id, workspaceId },
|
||||
});
|
||||
|
||||
const connectedAccountIds = connectedAccounts.map((ca) => ca.id);
|
||||
|
||||
if (connectedAccountIds.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const messageChannels = await this.messageChannelRepository.find({
|
||||
select: {
|
||||
id: true,
|
||||
handle: true,
|
||||
connectedAccount: {
|
||||
handleAliases: true,
|
||||
},
|
||||
where: {
|
||||
connectedAccount: {
|
||||
accountOwnerId: workspaceMemberId,
|
||||
deletedAt: IsNull(),
|
||||
},
|
||||
},
|
||||
relations: ['connectedAccount'],
|
||||
} as FindManyOptions<MessageChannelWorkspaceEntity>);
|
||||
},
|
||||
where: {
|
||||
connectedAccountId: In(connectedAccountIds),
|
||||
workspaceId,
|
||||
},
|
||||
relations: { connectedAccount: true },
|
||||
});
|
||||
|
||||
for (const messageChannel of messageChannels) {
|
||||
const messageChannelHandles = [messageChannel.handle];
|
||||
|
||||
+50
-11
@@ -1,18 +1,22 @@
|
||||
import { Scope } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Not } from 'typeorm';
|
||||
import { In, Not, Repository } from 'typeorm';
|
||||
import { type ObjectRecordDeleteEvent } from 'twenty-shared/database-events';
|
||||
|
||||
import { MessageChannelSyncStage } from 'twenty-shared/types';
|
||||
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
|
||||
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
|
||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { type WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event-batch.type';
|
||||
import { type BlocklistWorkspaceEntity } from 'src/modules/blocklist/standard-objects/blocklist.workspace-entity';
|
||||
import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
|
||||
import { MessageChannelSyncStage } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
|
||||
export type BlocklistReimportMessagesJobData = WorkspaceEventBatch<
|
||||
ObjectRecordDeleteEvent<BlocklistWorkspaceEntity>
|
||||
@@ -25,7 +29,12 @@ export type BlocklistReimportMessagesJobData = WorkspaceEventBatch<
|
||||
export class BlocklistReimportMessagesJob {
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
@InjectRepository(MessageChannelEntity)
|
||||
private readonly messageChannelRepository: Repository<MessageChannelEntity>,
|
||||
@InjectRepository(ConnectedAccountEntity)
|
||||
private readonly connectedAccountRepository: Repository<ConnectedAccountEntity>,
|
||||
@InjectRepository(UserWorkspaceEntity)
|
||||
private readonly userWorkspaceRepository: Repository<UserWorkspaceEntity>,
|
||||
private readonly messagingChannelSyncStatusService: MessageChannelSyncStatusService,
|
||||
) {}
|
||||
|
||||
@@ -36,19 +45,49 @@ export class BlocklistReimportMessagesJob {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const workspaceMemberRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkspaceMemberWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workspaceMember',
|
||||
);
|
||||
|
||||
for (const eventPayload of data.events) {
|
||||
const workspaceMemberId =
|
||||
eventPayload.properties.before.workspaceMemberId;
|
||||
|
||||
const messageChannels = await this.messageChannelDataAccessService.find(
|
||||
workspaceId,
|
||||
{
|
||||
connectedAccount: {
|
||||
accountOwnerId: workspaceMemberId,
|
||||
},
|
||||
const workspaceMember = await workspaceMemberRepository.findOne({
|
||||
where: { id: workspaceMemberId },
|
||||
});
|
||||
|
||||
if (!workspaceMember) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const userWorkspace = await this.userWorkspaceRepository.findOne({
|
||||
where: { userId: workspaceMember.userId, workspaceId },
|
||||
});
|
||||
|
||||
if (!userWorkspace) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const connectedAccounts = await this.connectedAccountRepository.find({
|
||||
where: { userWorkspaceId: userWorkspace.id, workspaceId },
|
||||
});
|
||||
|
||||
const connectedAccountIds = connectedAccounts.map((ca) => ca.id);
|
||||
|
||||
if (connectedAccountIds.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const messageChannels = await this.messageChannelRepository.find({
|
||||
where: {
|
||||
connectedAccountId: In(connectedAccountIds),
|
||||
syncStage: Not(MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING),
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
await this.messagingChannelSyncStatusService.resetAndMarkAsMessagesListFetchPending(
|
||||
messageChannels.map((messageChannel) => messageChannel.id),
|
||||
|
||||
+9
-2
@@ -1,6 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { MessageChannelDataAccessModule } from 'src/engine/metadata-modules/message-channel/data-access/message-channel-data-access.module';
|
||||
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { BlocklistItemDeleteMessagesJob } from 'src/modules/messaging/blocklist-manager/jobs/messaging-blocklist-item-delete-messages.job';
|
||||
import { BlocklistReimportMessagesJob } from 'src/modules/messaging/blocklist-manager/jobs/messaging-blocklist-reimport-messages.job';
|
||||
import { MessagingBlocklistListener } from 'src/modules/messaging/blocklist-manager/listeners/messaging-blocklist.listener';
|
||||
@@ -11,7 +14,11 @@ import { MessagingMessageCleanerModule } from 'src/modules/messaging/message-cle
|
||||
imports: [
|
||||
MessagingCommonModule,
|
||||
MessagingMessageCleanerModule,
|
||||
MessageChannelDataAccessModule,
|
||||
TypeOrmModule.forFeature([
|
||||
MessageChannelEntity,
|
||||
ConnectedAccountEntity,
|
||||
UserWorkspaceEntity,
|
||||
]),
|
||||
],
|
||||
providers: [
|
||||
MessagingBlocklistListener,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||
import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module';
|
||||
import { ConnectedAccountDataAccessModule } from 'src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module';
|
||||
import { MessageChannelDataAccessModule } from 'src/engine/metadata-modules/message-channel/data-access/message-channel-data-access.module';
|
||||
import { MessageFolderDataAccessModule } from 'src/engine/metadata-modules/message-folder/data-access/message-folder-data-access.module';
|
||||
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity';
|
||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||
import { ConnectedAccountModule } from 'src/modules/connected-account/connected-account.module';
|
||||
import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
|
||||
@@ -13,11 +13,13 @@ import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/se
|
||||
@Module({
|
||||
imports: [
|
||||
WorkspaceDataSourceModule,
|
||||
TypeOrmModule.forFeature([FeatureFlagEntity]),
|
||||
TypeOrmModule.forFeature([
|
||||
MessageChannelEntity,
|
||||
MessageFolderEntity,
|
||||
ConnectedAccountEntity,
|
||||
UserWorkspaceEntity,
|
||||
]),
|
||||
ConnectedAccountModule,
|
||||
ConnectedAccountDataAccessModule,
|
||||
MessageChannelDataAccessModule,
|
||||
MessageFolderDataAccessModule,
|
||||
MetricsModule,
|
||||
],
|
||||
providers: [MessageChannelSyncStatusService],
|
||||
|
||||
+118
-73
@@ -1,10 +1,13 @@
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/constants';
|
||||
|
||||
import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service';
|
||||
import { MessageChannelVisibility } from 'twenty-shared/types';
|
||||
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { type MessageWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message.workspace-entity';
|
||||
|
||||
import { ApplyMessagesVisibilityRestrictionsService } from './apply-messages-visibility-restrictions.service';
|
||||
@@ -39,7 +42,15 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => {
|
||||
findOneByOrFail: jest.fn(),
|
||||
};
|
||||
|
||||
const mockConnectedAccountDataAccessService = {
|
||||
const mockConnectedAccountRepository = {
|
||||
find: jest.fn(),
|
||||
};
|
||||
|
||||
const mockUserWorkspaceRepository = {
|
||||
findOne: jest.fn(),
|
||||
};
|
||||
|
||||
const mockMessageChannelRepository = {
|
||||
find: jest.fn(),
|
||||
};
|
||||
|
||||
@@ -66,8 +77,16 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => {
|
||||
useValue: mockGlobalWorkspaceOrmManager,
|
||||
},
|
||||
{
|
||||
provide: ConnectedAccountDataAccessService,
|
||||
useValue: mockConnectedAccountDataAccessService,
|
||||
provide: getRepositoryToken(ConnectedAccountEntity),
|
||||
useValue: mockConnectedAccountRepository,
|
||||
},
|
||||
{
|
||||
provide: getRepositoryToken(UserWorkspaceEntity),
|
||||
useValue: mockUserWorkspaceRepository,
|
||||
},
|
||||
{
|
||||
provide: getRepositoryToken(MessageChannelEntity),
|
||||
useValue: mockMessageChannelRepository,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
@@ -87,10 +106,14 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => {
|
||||
mockMessageChannelMessageAssociationRepository.find.mockResolvedValue([
|
||||
{
|
||||
messageId: 'messageId',
|
||||
messageChannel: {
|
||||
id: 'messageChannelId',
|
||||
visibility: MessageChannelVisibility.SHARE_EVERYTHING,
|
||||
},
|
||||
messageChannelId: 'messageChannelId',
|
||||
},
|
||||
]);
|
||||
|
||||
mockMessageChannelRepository.find.mockResolvedValue([
|
||||
{
|
||||
id: 'messageChannelId',
|
||||
visibility: MessageChannelVisibility.SHARE_EVERYTHING,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -107,7 +130,7 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => {
|
||||
item.subject === 'Test Subject' && item.text === 'Test Message',
|
||||
),
|
||||
).toBe(true);
|
||||
expect(mockConnectedAccountDataAccessService.find).not.toHaveBeenCalled();
|
||||
expect(mockConnectedAccountRepository.find).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return message without obfuscated subject and with obfuscated text if the visibility is SUBJECT', async () => {
|
||||
@@ -118,17 +141,27 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => {
|
||||
mockMessageChannelMessageAssociationRepository.find.mockResolvedValue([
|
||||
{
|
||||
messageId: 'messageId',
|
||||
messageChannel: {
|
||||
id: 'messageChannelId',
|
||||
visibility: MessageChannelVisibility.SUBJECT,
|
||||
},
|
||||
messageChannelId: 'messageChannelId',
|
||||
},
|
||||
]);
|
||||
|
||||
mockConnectedAccountDataAccessService.find.mockResolvedValue([]);
|
||||
mockMessageChannelRepository.find.mockResolvedValue([
|
||||
{
|
||||
id: 'messageChannelId',
|
||||
visibility: MessageChannelVisibility.SUBJECT,
|
||||
},
|
||||
]);
|
||||
|
||||
mockConnectedAccountRepository.find.mockResolvedValue([]);
|
||||
|
||||
mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({
|
||||
id: 'workspace-member-id',
|
||||
userId: 'user-id',
|
||||
});
|
||||
|
||||
mockUserWorkspaceRepository.findOne.mockResolvedValue({
|
||||
id: 'user-workspace-id',
|
||||
userId: 'user-id',
|
||||
});
|
||||
|
||||
const result = await service.applyMessagesVisibilityRestrictions(
|
||||
@@ -153,17 +186,27 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => {
|
||||
mockMessageChannelMessageAssociationRepository.find.mockResolvedValue([
|
||||
{
|
||||
messageId: 'messageId',
|
||||
messageChannel: {
|
||||
id: 'messageChannelId',
|
||||
visibility: MessageChannelVisibility.METADATA,
|
||||
},
|
||||
messageChannelId: 'messageChannelId',
|
||||
},
|
||||
]);
|
||||
|
||||
mockConnectedAccountDataAccessService.find.mockResolvedValue([]);
|
||||
mockMessageChannelRepository.find.mockResolvedValue([
|
||||
{
|
||||
id: 'messageChannelId',
|
||||
visibility: MessageChannelVisibility.METADATA,
|
||||
},
|
||||
]);
|
||||
|
||||
mockConnectedAccountRepository.find.mockResolvedValue([]);
|
||||
|
||||
mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({
|
||||
id: 'workspace-member-id',
|
||||
userId: 'user-id',
|
||||
});
|
||||
|
||||
mockUserWorkspaceRepository.findOne.mockResolvedValue({
|
||||
id: 'user-workspace-id',
|
||||
userId: 'user-id',
|
||||
});
|
||||
|
||||
const result = await service.applyMessagesVisibilityRestrictions(
|
||||
@@ -189,18 +232,28 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => {
|
||||
mockMessageChannelMessageAssociationRepository.find.mockResolvedValue([
|
||||
{
|
||||
messageId: 'messageId',
|
||||
messageChannel: {
|
||||
id: 'messageChannelId',
|
||||
visibility: MessageChannelVisibility.METADATA,
|
||||
},
|
||||
messageChannelId: 'messageChannelId',
|
||||
},
|
||||
]);
|
||||
|
||||
mockMessageChannelRepository.find.mockResolvedValue([
|
||||
{
|
||||
id: 'messageChannelId',
|
||||
visibility: MessageChannelVisibility.METADATA,
|
||||
},
|
||||
]);
|
||||
|
||||
mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({
|
||||
id: 'workspace-member-account-owner-id',
|
||||
userId: 'user-id',
|
||||
});
|
||||
|
||||
mockConnectedAccountDataAccessService.find.mockResolvedValue([{ id: '1' }]);
|
||||
mockUserWorkspaceRepository.findOne.mockResolvedValue({
|
||||
id: 'user-workspace-id',
|
||||
userId: 'user-id',
|
||||
});
|
||||
|
||||
mockConnectedAccountRepository.find.mockResolvedValue([{ id: '1' }]);
|
||||
|
||||
const result = await service.applyMessagesVisibilityRestrictions(
|
||||
messages,
|
||||
@@ -225,17 +278,27 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => {
|
||||
mockMessageChannelMessageAssociationRepository.find.mockResolvedValue([
|
||||
{
|
||||
messageId: 'messageId',
|
||||
messageChannel: {
|
||||
id: 'messageChannelId',
|
||||
},
|
||||
messageChannelId: 'messageChannelId',
|
||||
},
|
||||
]);
|
||||
|
||||
mockMessageChannelRepository.find.mockResolvedValue([
|
||||
{
|
||||
id: 'messageChannelId',
|
||||
},
|
||||
]);
|
||||
|
||||
mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({
|
||||
id: 'workspace-member-not-account-owner-id',
|
||||
userId: 'user-id',
|
||||
});
|
||||
|
||||
mockConnectedAccountDataAccessService.find.mockResolvedValue([]);
|
||||
mockUserWorkspaceRepository.findOne.mockResolvedValue({
|
||||
id: 'user-workspace-id',
|
||||
userId: 'user-id',
|
||||
});
|
||||
|
||||
mockConnectedAccountRepository.find.mockResolvedValue([]);
|
||||
|
||||
const result = await service.applyMessagesVisibilityRestrictions(
|
||||
messages,
|
||||
@@ -254,34 +317,28 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => {
|
||||
];
|
||||
|
||||
mockMessageChannelMessageAssociationRepository.find.mockResolvedValue([
|
||||
{
|
||||
messageId: '1',
|
||||
messageChannel: {
|
||||
id: '1',
|
||||
visibility: MessageChannelVisibility.SHARE_EVERYTHING,
|
||||
},
|
||||
},
|
||||
{
|
||||
messageId: '2',
|
||||
messageChannel: {
|
||||
id: '2',
|
||||
visibility: MessageChannelVisibility.SUBJECT,
|
||||
},
|
||||
},
|
||||
{
|
||||
messageId: '3',
|
||||
messageChannel: {
|
||||
id: '3',
|
||||
visibility: MessageChannelVisibility.METADATA,
|
||||
},
|
||||
},
|
||||
{ messageId: '1', messageChannelId: '1' },
|
||||
{ messageId: '2', messageChannelId: '2' },
|
||||
{ messageId: '3', messageChannelId: '3' },
|
||||
]);
|
||||
|
||||
mockMessageChannelRepository.find.mockResolvedValue([
|
||||
{ id: '1', visibility: MessageChannelVisibility.SHARE_EVERYTHING },
|
||||
{ id: '2', visibility: MessageChannelVisibility.SUBJECT },
|
||||
{ id: '3', visibility: MessageChannelVisibility.METADATA },
|
||||
]);
|
||||
|
||||
mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({
|
||||
id: 'workspace-member-id',
|
||||
userId: 'user-id',
|
||||
});
|
||||
|
||||
mockConnectedAccountDataAccessService.find
|
||||
mockUserWorkspaceRepository.findOne.mockResolvedValue({
|
||||
id: 'user-workspace-id',
|
||||
userId: 'user-id',
|
||||
});
|
||||
|
||||
mockConnectedAccountRepository.find
|
||||
.mockResolvedValueOnce([]) // request for message 3
|
||||
.mockResolvedValueOnce([]); // request for message 2
|
||||
|
||||
@@ -313,34 +370,22 @@ describe('ApplyMessagesVisibilityRestrictionsService', () => {
|
||||
];
|
||||
|
||||
mockMessageChannelMessageAssociationRepository.find.mockResolvedValue([
|
||||
{
|
||||
messageId: '1',
|
||||
messageChannel: {
|
||||
id: '1',
|
||||
visibility: MessageChannelVisibility.SHARE_EVERYTHING,
|
||||
},
|
||||
},
|
||||
{
|
||||
messageId: '2',
|
||||
messageChannel: {
|
||||
id: '2',
|
||||
visibility: MessageChannelVisibility.SUBJECT,
|
||||
},
|
||||
},
|
||||
{
|
||||
messageId: '3',
|
||||
messageChannel: {
|
||||
id: '3',
|
||||
visibility: MessageChannelVisibility.METADATA,
|
||||
},
|
||||
},
|
||||
{ messageId: '1', messageChannelId: '1' },
|
||||
{ messageId: '2', messageChannelId: '2' },
|
||||
{ messageId: '3', messageChannelId: '3' },
|
||||
]);
|
||||
|
||||
mockMessageChannelRepository.find.mockResolvedValue([
|
||||
{ id: '1', visibility: MessageChannelVisibility.SHARE_EVERYTHING },
|
||||
{ id: '2', visibility: MessageChannelVisibility.SUBJECT },
|
||||
{ id: '3', visibility: MessageChannelVisibility.METADATA },
|
||||
]);
|
||||
|
||||
mockWorkspaceMemberRepository.findOneByOrFail.mockResolvedValue({
|
||||
id: 'workspace-member-id',
|
||||
});
|
||||
|
||||
mockConnectedAccountDataAccessService.find
|
||||
mockConnectedAccountRepository.find
|
||||
.mockResolvedValueOnce([]) // request for message 3
|
||||
.mockResolvedValueOnce([]); // request for message 2
|
||||
|
||||
|
||||
+59
-24
@@ -1,16 +1,19 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import groupBy from 'lodash.groupby';
|
||||
import { FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED } from 'twenty-shared/constants';
|
||||
import { MessageChannelVisibility } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { In } from 'typeorm';
|
||||
import { In, Repository } from 'typeorm';
|
||||
|
||||
import { NotFoundError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||
import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service';
|
||||
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { type MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity';
|
||||
import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { type MessageWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message.workspace-entity';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
|
||||
@@ -18,7 +21,12 @@ import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/sta
|
||||
export class ApplyMessagesVisibilityRestrictionsService {
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService,
|
||||
@InjectRepository(ConnectedAccountEntity)
|
||||
private readonly connectedAccountRepository: Repository<ConnectedAccountEntity>,
|
||||
@InjectRepository(UserWorkspaceEntity)
|
||||
private readonly userWorkspaceRepository: Repository<UserWorkspaceEntity>,
|
||||
@InjectRepository(MessageChannelEntity)
|
||||
private readonly messageChannelRepository: Repository<MessageChannelEntity>,
|
||||
) {}
|
||||
|
||||
public async applyMessagesVisibilityRestrictions(
|
||||
@@ -41,9 +49,28 @@ export class ApplyMessagesVisibilityRestrictionsService {
|
||||
where: {
|
||||
messageId: In(messages.map((message) => message.id)),
|
||||
},
|
||||
relations: ['messageChannel'],
|
||||
});
|
||||
|
||||
const messageChannelIds = [
|
||||
...new Set(
|
||||
messageChannelMessagesAssociations.map((a) => a.messageChannelId),
|
||||
),
|
||||
];
|
||||
|
||||
const messageChannelsFromCore =
|
||||
messageChannelIds.length > 0
|
||||
? await this.messageChannelRepository.find({
|
||||
where: {
|
||||
id: In(messageChannelIds),
|
||||
workspaceId,
|
||||
},
|
||||
})
|
||||
: [];
|
||||
|
||||
const messageChannelMap = new Map(
|
||||
messageChannelsFromCore.map((ch) => [ch.id, ch]),
|
||||
);
|
||||
|
||||
const workspaceMemberRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkspaceMemberWorkspaceEntity>(
|
||||
workspaceId,
|
||||
@@ -51,17 +78,15 @@ export class ApplyMessagesVisibilityRestrictionsService {
|
||||
);
|
||||
|
||||
for (let i = messages.length - 1; i >= 0; i--) {
|
||||
const messageChannelMessageAssociations =
|
||||
messageChannelMessagesAssociations.filter(
|
||||
(association) => association.messageId === messages[i].id,
|
||||
);
|
||||
const associations = messageChannelMessagesAssociations.filter(
|
||||
(association) => association.messageId === messages[i].id,
|
||||
);
|
||||
|
||||
const messageChannels = messageChannelMessageAssociations
|
||||
.map((association) => association.messageChannel)
|
||||
.filter(
|
||||
(channel): channel is NonNullable<typeof channel> =>
|
||||
channel !== null,
|
||||
);
|
||||
const messageChannels = associations
|
||||
.map((association) =>
|
||||
messageChannelMap.get(association.messageChannelId),
|
||||
)
|
||||
.filter(isDefined);
|
||||
|
||||
if (messageChannels.length === 0) {
|
||||
throw new NotFoundError('Associated message channels not found');
|
||||
@@ -86,16 +111,26 @@ export class ApplyMessagesVisibilityRestrictionsService {
|
||||
userId,
|
||||
});
|
||||
|
||||
const connectedAccounts =
|
||||
await this.connectedAccountDataAccessService.find(workspaceId, {
|
||||
messageChannels: {
|
||||
id: In(messageChannels.map((channel) => channel.id)),
|
||||
},
|
||||
accountOwnerId: workspaceMember.id,
|
||||
});
|
||||
const userWorkspace = await this.userWorkspaceRepository.findOne({
|
||||
where: { userId: workspaceMember.userId, workspaceId },
|
||||
});
|
||||
|
||||
if (connectedAccounts.length > 0) {
|
||||
continue;
|
||||
if (userWorkspace) {
|
||||
const connectedAccounts =
|
||||
await this.connectedAccountRepository.find({
|
||||
where: {
|
||||
userWorkspaceId: userWorkspace.id,
|
||||
workspaceId,
|
||||
messageChannels: {
|
||||
id: In(messageChannels.map((channel) => channel.id)),
|
||||
},
|
||||
},
|
||||
relations: { messageChannels: true },
|
||||
});
|
||||
|
||||
if (connectedAccounts.length > 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-144
@@ -1,144 +0,0 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { assertIsDefinedOrThrow, isDefined } 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 WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type';
|
||||
import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception';
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { MessageFolderDataAccessService } from 'src/engine/metadata-modules/message-folder/data-access/services/message-folder-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import {
|
||||
MessageChannelPendingGroupEmailsAction,
|
||||
MessageChannelSyncStage,
|
||||
type MessageChannelWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { MessageFolderPendingSyncAction } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { MessagingProcessGroupEmailActionsService } from 'src/modules/messaging/message-import-manager/services/messaging-process-group-email-actions.service';
|
||||
|
||||
const ONGOING_SYNC_STAGES = [
|
||||
MessageChannelSyncStage.MESSAGE_LIST_FETCH_ONGOING,
|
||||
];
|
||||
|
||||
@WorkspaceQueryHook(`messageChannel.updateOne`)
|
||||
export class MessageChannelUpdateOnePreQueryHook
|
||||
implements WorkspacePreQueryHookInstance
|
||||
{
|
||||
private readonly logger = new Logger(
|
||||
MessageChannelUpdateOnePreQueryHook.name,
|
||||
);
|
||||
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
private readonly messageFolderDataAccessService: MessageFolderDataAccessService,
|
||||
private readonly messagingProcessGroupEmailActionsService: MessagingProcessGroupEmailActionsService,
|
||||
) {}
|
||||
|
||||
async execute(
|
||||
authContext: WorkspaceAuthContext,
|
||||
_objectName: string,
|
||||
payload: UpdateOneResolverArgs<MessageChannelWorkspaceEntity>,
|
||||
): Promise<UpdateOneResolverArgs<MessageChannelWorkspaceEntity>> {
|
||||
const workspace = authContext.workspace;
|
||||
|
||||
assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError);
|
||||
|
||||
const systemAuthContext = buildSystemAuthContext(workspace.id);
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
async () => {
|
||||
const messageChannel =
|
||||
await this.messageChannelDataAccessService.findOne(workspace.id, {
|
||||
where: { id: payload.id },
|
||||
});
|
||||
|
||||
if (!isDefined(messageChannel)) {
|
||||
throw new WorkspaceQueryRunnerException(
|
||||
'Message channel not found',
|
||||
WorkspaceQueryRunnerExceptionCode.DATA_NOT_FOUND,
|
||||
{
|
||||
userFriendlyMessage: msg`Message channel not found`,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const messageChannelWorkspace =
|
||||
messageChannel as unknown as MessageChannelWorkspaceEntity;
|
||||
|
||||
const isSyncOngoing = ONGOING_SYNC_STAGES.includes(
|
||||
messageChannelWorkspace.syncStage,
|
||||
);
|
||||
|
||||
const messageFoldersWithPendingAction =
|
||||
await this.messageFolderDataAccessService.find(workspace.id, {
|
||||
messageChannelId: messageChannel.id,
|
||||
pendingSyncAction: Not(MessageFolderPendingSyncAction.NONE),
|
||||
});
|
||||
|
||||
const messageFoldersWithPendingActionCount =
|
||||
messageFoldersWithPendingAction.length;
|
||||
|
||||
const hasPendingFolderActions =
|
||||
messageFoldersWithPendingActionCount > 0;
|
||||
|
||||
const hasPendingGroupEmailsAction =
|
||||
messageChannelWorkspace.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.`,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const hasCompletedConfiguration =
|
||||
messageChannelWorkspace.syncStage !==
|
||||
MessageChannelSyncStage.PENDING_CONFIGURATION;
|
||||
|
||||
if (!hasCompletedConfiguration) {
|
||||
this.logger.log(
|
||||
`MessageChannelId: ${messageChannelWorkspace.id} - Skipping pending action for message channel in PENDING_CONFIGURATION state`,
|
||||
);
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
const excludeGroupEmailsChanged =
|
||||
isDefined(payload.data.excludeGroupEmails) &&
|
||||
payload.data.excludeGroupEmails !==
|
||||
messageChannelWorkspace.excludeGroupEmails;
|
||||
|
||||
if (excludeGroupEmailsChanged) {
|
||||
await this.messagingProcessGroupEmailActionsService.markMessageChannelAsPendingGroupEmailsAction(
|
||||
messageChannelWorkspace,
|
||||
workspace.id,
|
||||
payload.data.excludeGroupEmails
|
||||
? MessageChannelPendingGroupEmailsAction.GROUP_EMAILS_DELETION
|
||||
: MessageChannelPendingGroupEmailsAction.GROUP_EMAILS_IMPORT,
|
||||
);
|
||||
}
|
||||
|
||||
return payload;
|
||||
},
|
||||
systemAuthContext,
|
||||
);
|
||||
}
|
||||
}
|
||||
+11
-8
@@ -1,10 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { ConnectedAccountDataAccessModule } from 'src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module';
|
||||
import { MessageChannelDataAccessModule } from 'src/engine/metadata-modules/message-channel/data-access/message-channel-data-access.module';
|
||||
import { MessageFolderDataAccessModule } from 'src/engine/metadata-modules/message-folder/data-access/message-folder-data-access.module';
|
||||
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity';
|
||||
import { ApplyMessagesVisibilityRestrictionsService } from 'src/modules/messaging/common/query-hooks/message/apply-messages-visibility-restrictions.service';
|
||||
import { MessageChannelUpdateOnePreQueryHook } from 'src/modules/messaging/common/query-hooks/message/message-channel-update-one.pre-query.hook';
|
||||
import { MessageFindManyPostQueryHook } from 'src/modules/messaging/common/query-hooks/message/message-find-many.post-query.hook';
|
||||
import { MessageFindOnePostQueryHook } from 'src/modules/messaging/common/query-hooks/message/message-find-one.post-query.hook';
|
||||
import { MessagingImportManagerModule } from 'src/modules/messaging/message-import-manager/messaging-import-manager.module';
|
||||
@@ -12,15 +13,17 @@ import { MessagingImportManagerModule } from 'src/modules/messaging/message-impo
|
||||
@Module({
|
||||
imports: [
|
||||
MessagingImportManagerModule,
|
||||
ConnectedAccountDataAccessModule,
|
||||
MessageChannelDataAccessModule,
|
||||
MessageFolderDataAccessModule,
|
||||
TypeOrmModule.forFeature([
|
||||
ConnectedAccountEntity,
|
||||
MessageChannelEntity,
|
||||
MessageFolderEntity,
|
||||
UserWorkspaceEntity,
|
||||
]),
|
||||
],
|
||||
providers: [
|
||||
ApplyMessagesVisibilityRestrictionsService,
|
||||
MessageFindOnePostQueryHook,
|
||||
MessageFindManyPostQueryHook,
|
||||
MessageChannelUpdateOnePreQueryHook,
|
||||
],
|
||||
})
|
||||
export class MessagingQueryHookModule {}
|
||||
|
||||
+62
-64
@@ -1,25 +1,27 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Any, In } from 'typeorm';
|
||||
import { Any, In, Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
MessageChannelPendingGroupEmailsAction,
|
||||
MessageChannelSyncStage,
|
||||
MessageChannelSyncStatus,
|
||||
MessageFolderPendingSyncAction,
|
||||
} from 'twenty-shared/types';
|
||||
import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator';
|
||||
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
|
||||
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
|
||||
import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service';
|
||||
import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys.type';
|
||||
import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service';
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { MessageFolderDataAccessService } from 'src/engine/metadata-modules/message-folder/data-access/services/message-folder-data-access.service';
|
||||
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service';
|
||||
import { AccountsToReconnectKeys } from 'src/modules/connected-account/types/accounts-to-reconnect-key-value.type';
|
||||
import {
|
||||
MessageChannelPendingGroupEmailsAction,
|
||||
MessageChannelSyncStage,
|
||||
MessageChannelSyncStatus,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { MessageFolderPendingSyncAction } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
|
||||
@Injectable()
|
||||
@@ -28,9 +30,14 @@ export class MessageChannelSyncStatusService {
|
||||
@InjectCacheStorage(CacheStorageNamespace.ModuleMessaging)
|
||||
private readonly cacheStorage: CacheStorageService,
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
private readonly messageFolderDataAccessService: MessageFolderDataAccessService,
|
||||
private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService,
|
||||
@InjectRepository(MessageChannelEntity)
|
||||
private readonly messageChannelRepository: Repository<MessageChannelEntity>,
|
||||
@InjectRepository(MessageFolderEntity)
|
||||
private readonly messageFolderRepository: Repository<MessageFolderEntity>,
|
||||
@InjectRepository(ConnectedAccountEntity)
|
||||
private readonly connectedAccountRepository: Repository<ConnectedAccountEntity>,
|
||||
@InjectRepository(UserWorkspaceEntity)
|
||||
private readonly userWorkspaceRepository: Repository<UserWorkspaceEntity>,
|
||||
private readonly accountsToReconnectService: AccountsToReconnectService,
|
||||
private readonly metricsService: MetricsService,
|
||||
) {}
|
||||
@@ -47,9 +54,8 @@ export class MessageChannelSyncStatusService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(messageChannelIds) },
|
||||
await this.messageChannelRepository.update(
|
||||
{ id: In(messageChannelIds), workspaceId },
|
||||
{
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING,
|
||||
...(!preserveSyncStageStartedAt ? { syncStageStartedAt: null } : {}),
|
||||
@@ -70,9 +76,8 @@ export class MessageChannelSyncStatusService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(messageChannelIds) },
|
||||
await this.messageChannelRepository.update(
|
||||
{ id: In(messageChannelIds), workspaceId },
|
||||
{
|
||||
syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_PENDING,
|
||||
...(!preserveSyncStageStartedAt ? { syncStageStartedAt: null } : {}),
|
||||
@@ -98,9 +103,8 @@ export class MessageChannelSyncStatusService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(messageChannelIds) },
|
||||
await this.messageChannelRepository.update(
|
||||
{ id: In(messageChannelIds), workspaceId },
|
||||
{
|
||||
syncCursor: '',
|
||||
syncStageStartedAt: null,
|
||||
@@ -110,9 +114,8 @@ export class MessageChannelSyncStatusService {
|
||||
},
|
||||
);
|
||||
|
||||
await this.messageFolderDataAccessService.update(
|
||||
workspaceId,
|
||||
{ messageChannelId: In(messageChannelIds) },
|
||||
await this.messageFolderRepository.update(
|
||||
{ messageChannelId: In(messageChannelIds), workspaceId },
|
||||
{
|
||||
syncCursor: '',
|
||||
pendingSyncAction: MessageFolderPendingSyncAction.NONE,
|
||||
@@ -134,9 +137,8 @@ export class MessageChannelSyncStatusService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(messageChannelIds) },
|
||||
await this.messageChannelRepository.update(
|
||||
{ id: In(messageChannelIds), workspaceId },
|
||||
{ syncStageStartedAt: null },
|
||||
);
|
||||
}, authContext);
|
||||
@@ -153,9 +155,8 @@ export class MessageChannelSyncStatusService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(messageChannelIds) },
|
||||
await this.messageChannelRepository.update(
|
||||
{ id: In(messageChannelIds), workspaceId },
|
||||
{
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED,
|
||||
syncStatus: MessageChannelSyncStatus.ONGOING,
|
||||
@@ -176,9 +177,8 @@ export class MessageChannelSyncStatusService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(messageChannelIds) },
|
||||
await this.messageChannelRepository.update(
|
||||
{ id: In(messageChannelIds), workspaceId },
|
||||
{
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_ONGOING,
|
||||
syncStatus: MessageChannelSyncStatus.ONGOING,
|
||||
@@ -199,9 +199,8 @@ export class MessageChannelSyncStatusService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(messageChannelIds) },
|
||||
await this.messageChannelRepository.update(
|
||||
{ id: In(messageChannelIds), workspaceId },
|
||||
{
|
||||
syncStatus: MessageChannelSyncStatus.ACTIVE,
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING,
|
||||
@@ -230,9 +229,8 @@ export class MessageChannelSyncStatusService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(messageChannelIds) },
|
||||
await this.messageChannelRepository.update(
|
||||
{ id: In(messageChannelIds), workspaceId },
|
||||
{
|
||||
syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED,
|
||||
},
|
||||
@@ -251,9 +249,8 @@ export class MessageChannelSyncStatusService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(messageChannelIds) },
|
||||
await this.messageChannelRepository.update(
|
||||
{ id: In(messageChannelIds), workspaceId },
|
||||
{
|
||||
syncStage: MessageChannelSyncStage.MESSAGES_IMPORT_ONGOING,
|
||||
syncStatus: MessageChannelSyncStatus.ONGOING,
|
||||
@@ -277,9 +274,8 @@ export class MessageChannelSyncStatusService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(messageChannelIds) },
|
||||
await this.messageChannelRepository.update(
|
||||
{ id: In(messageChannelIds), workspaceId },
|
||||
{
|
||||
syncStage: MessageChannelSyncStage.FAILED,
|
||||
syncStatus: syncStatus,
|
||||
@@ -300,18 +296,16 @@ export class MessageChannelSyncStatusService {
|
||||
if (
|
||||
syncStatus === MessageChannelSyncStatus.FAILED_INSUFFICIENT_PERMISSIONS
|
||||
) {
|
||||
const messageChannels = await this.messageChannelDataAccessService.find(
|
||||
workspaceId,
|
||||
{ id: In(messageChannelIds) },
|
||||
);
|
||||
const messageChannels = await this.messageChannelRepository.find({
|
||||
where: { id: In(messageChannelIds), workspaceId },
|
||||
});
|
||||
|
||||
const connectedAccountIds = messageChannels.map(
|
||||
(messageChannel) => messageChannel.connectedAccountId,
|
||||
);
|
||||
|
||||
await this.connectedAccountDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: Any(connectedAccountIds) },
|
||||
await this.connectedAccountRepository.update(
|
||||
{ id: Any(connectedAccountIds), workspaceId },
|
||||
{
|
||||
authFailedAt: new Date(),
|
||||
},
|
||||
@@ -333,12 +327,9 @@ export class MessageChannelSyncStatusService {
|
||||
return;
|
||||
}
|
||||
|
||||
const messageChannels = await this.messageChannelDataAccessService.findMany(
|
||||
workspaceId,
|
||||
{
|
||||
where: { id: In(messageChannelIds) },
|
||||
},
|
||||
);
|
||||
const messageChannels = await this.messageChannelRepository.find({
|
||||
where: { id: In(messageChannelIds), workspaceId },
|
||||
});
|
||||
|
||||
const workspaceMemberRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkspaceMemberWorkspaceEntity>(
|
||||
@@ -348,17 +339,24 @@ export class MessageChannelSyncStatusService {
|
||||
);
|
||||
|
||||
for (const messageChannel of messageChannels) {
|
||||
const connectedAccount =
|
||||
await this.connectedAccountDataAccessService.findOne(workspaceId, {
|
||||
where: { id: messageChannel.connectedAccountId },
|
||||
});
|
||||
const connectedAccount = await this.connectedAccountRepository.findOne({
|
||||
where: { id: messageChannel.connectedAccountId, workspaceId },
|
||||
});
|
||||
|
||||
if (!connectedAccount) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const userWorkspace = await this.userWorkspaceRepository.findOne({
|
||||
where: { id: connectedAccount.userWorkspaceId },
|
||||
});
|
||||
|
||||
if (!userWorkspace) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const workspaceMember = await workspaceMemberRepository.findOne({
|
||||
where: { id: connectedAccount.accountOwnerId },
|
||||
where: { userId: userWorkspace.userId },
|
||||
});
|
||||
|
||||
if (!workspaceMember) {
|
||||
|
||||
-2
@@ -5,7 +5,6 @@ import { type FieldTypeAndNameMetadata } from 'src/engine/workspace-manager/util
|
||||
import { type EntityRelation } from 'src/engine/workspace-manager/workspace-migration/types/entity-relation.interface';
|
||||
import { type MessageDirection } from 'src/modules/messaging/common/enums/message-direction.enum';
|
||||
import { type MessageChannelMessageAssociationMessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association-message-folder.workspace-entity';
|
||||
import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { type MessageWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message.workspace-entity';
|
||||
|
||||
const MESSAGE_EXTERNAL_ID_FIELD_NAME = 'messageExternalId';
|
||||
@@ -17,7 +16,6 @@ export class MessageChannelMessageAssociationWorkspaceEntity extends BaseWorkspa
|
||||
messageExternalId: string | null;
|
||||
messageThreadExternalId: string | null;
|
||||
direction: MessageDirection;
|
||||
messageChannel: EntityRelation<MessageChannelWorkspaceEntity> | null;
|
||||
messageChannelId: string;
|
||||
message: EntityRelation<MessageWorkspaceEntity> | null;
|
||||
messageId: string;
|
||||
|
||||
-2
@@ -9,7 +9,6 @@ import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity
|
||||
import { type FieldTypeAndNameMetadata } from 'src/engine/workspace-manager/utils/get-ts-vector-column-expression.util';
|
||||
import { type EntityRelation } from 'src/engine/workspace-manager/workspace-migration/types/entity-relation.interface';
|
||||
import { type MessageChannelMessageAssociationMessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association-message-folder.workspace-entity';
|
||||
import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
|
||||
export { MessageFolderPendingSyncAction };
|
||||
|
||||
@@ -25,7 +24,6 @@ export const SEARCH_FIELDS_FOR_MESSAGE_FOLDER: FieldTypeAndNameMetadata[] = [
|
||||
|
||||
export class MessageFolderWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
name: string | null;
|
||||
messageChannel: EntityRelation<MessageChannelWorkspaceEntity>;
|
||||
syncCursor: string | null;
|
||||
isSentFolder: boolean;
|
||||
isSynced: boolean;
|
||||
|
||||
+11
-6
@@ -1,9 +1,11 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command, CommandRunner, Option } from 'nest-commander';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
|
||||
@@ -24,7 +26,8 @@ export class MessagingResetChannelCommand extends CommandRunner {
|
||||
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
@InjectRepository(MessageChannelEntity)
|
||||
private readonly messageChannelRepository: Repository<MessageChannelEntity>,
|
||||
private readonly messagingChannelSyncStatusService: MessageChannelSyncStatusService,
|
||||
private readonly messagingMessageCleanerService: MessagingMessageCleanerService,
|
||||
) {
|
||||
@@ -44,10 +47,12 @@ export class MessagingResetChannelCommand extends CommandRunner {
|
||||
`No message channel ID provided, resetting all message channels in workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
const messageChannels = await this.messageChannelDataAccessService.find(
|
||||
workspaceId,
|
||||
isDefined(messageChannelId) ? { id: messageChannelId } : {},
|
||||
);
|
||||
const messageChannels = await this.messageChannelRepository.find({
|
||||
where: {
|
||||
...(isDefined(messageChannelId) ? { id: messageChannelId } : {}),
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
if (messageChannels.length === 0) {
|
||||
this.logger.log(
|
||||
|
||||
-13
@@ -1,8 +1,5 @@
|
||||
import { Logger, Scope } from '@nestjs/common';
|
||||
|
||||
import { FeatureFlagKey } from 'twenty-shared/types';
|
||||
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
|
||||
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
|
||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
@@ -24,22 +21,12 @@ export class MessagingMessageChannelDeletionCleanupJob {
|
||||
|
||||
constructor(
|
||||
private readonly messageCleanerService: MessagingMessageCleanerService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
) {}
|
||||
|
||||
@Process(MessagingMessageChannelDeletionCleanupJob.name)
|
||||
async handle(
|
||||
data: MessagingMessageChannelDeletionCleanupJobData,
|
||||
): Promise<void> {
|
||||
const isMigrated = await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED,
|
||||
data.workspaceId,
|
||||
);
|
||||
|
||||
if (!isMigrated) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.debug(
|
||||
`WorkspaceId: ${data.workspaceId} Cleaning up message channel message associations for channel ${data.messageChannelId}`,
|
||||
);
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@ import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decora
|
||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
|
||||
import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event-batch.type';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import {
|
||||
MessagingConnectedAccountDeletionCleanupJob,
|
||||
type MessagingConnectedAccountDeletionCleanupJobData,
|
||||
@@ -24,7 +24,7 @@ export class MessagingMessageCleanerConnectedAccountListener {
|
||||
@OnDatabaseBatchEvent('connectedAccount', DatabaseEventAction.DESTROYED)
|
||||
async handleDestroyedEvent(
|
||||
payload: WorkspaceEventBatch<
|
||||
ObjectRecordDeleteEvent<ConnectedAccountWorkspaceEntity>
|
||||
ObjectRecordDeleteEvent<ConnectedAccountEntity>
|
||||
>,
|
||||
) {
|
||||
await Promise.all(
|
||||
|
||||
+2
-4
@@ -6,7 +6,7 @@ import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decora
|
||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
|
||||
import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event-batch.type';
|
||||
import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { type MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import {
|
||||
MessagingMessageChannelDeletionCleanupJob,
|
||||
type MessagingMessageChannelDeletionCleanupJobData,
|
||||
@@ -23,9 +23,7 @@ export class MessagingMessageCleanerMessageChannelListener {
|
||||
|
||||
@OnDatabaseBatchEvent('messageChannel', DatabaseEventAction.DESTROYED)
|
||||
async handleDestroyedEvent(
|
||||
payload: WorkspaceEventBatch<
|
||||
ObjectRecordDeleteEvent<MessageChannelWorkspaceEntity>
|
||||
>,
|
||||
payload: WorkspaceEventBatch<ObjectRecordDeleteEvent<MessageChannelEntity>>,
|
||||
) {
|
||||
await Promise.all(
|
||||
payload.events.map((eventPayload) =>
|
||||
|
||||
+3
-2
@@ -5,7 +5,7 @@ import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/w
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||
import { MessageChannelDataAccessModule } from 'src/engine/metadata-modules/message-channel/data-access/message-channel-data-access.module';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { MessagingCommonModule } from 'src/modules/messaging/common/messaging-common.module';
|
||||
import { MessagingMessageCleanerRemoveOrphansCommand } from 'src/modules/messaging/message-cleaner/commands/messaging-message-clearner-remove-orphans.command';
|
||||
import { MessagingResetChannelCommand } from 'src/modules/messaging/message-cleaner/commands/messaging-reset-channel.command';
|
||||
@@ -13,6 +13,7 @@ import { MessagingConnectedAccountDeletionCleanupJob } from 'src/modules/messagi
|
||||
import { MessagingMessageChannelDeletionCleanupJob } from 'src/modules/messaging/message-cleaner/jobs/messaging-message-channel-deletion-cleanup.job';
|
||||
import { MessagingMessageCleanerConnectedAccountListener } from 'src/modules/messaging/message-cleaner/listeners/messaging-message-cleaner-connected-account.listener';
|
||||
import { MessagingMessageCleanerMessageChannelListener } from 'src/modules/messaging/message-cleaner/listeners/messaging-message-cleaner-message-channel.listener';
|
||||
|
||||
import { MessagingMessageCleanerService } from 'src/modules/messaging/message-cleaner/services/messaging-message-cleaner.service';
|
||||
|
||||
@Module({
|
||||
@@ -21,7 +22,7 @@ import { MessagingMessageCleanerService } from 'src/modules/messaging/message-cl
|
||||
DataSourceModule,
|
||||
FeatureFlagModule,
|
||||
MessagingCommonModule,
|
||||
MessageChannelDataAccessModule,
|
||||
TypeOrmModule.forFeature([MessageChannelEntity]),
|
||||
WorkspaceIteratorModule,
|
||||
],
|
||||
providers: [
|
||||
|
||||
+4
-7
@@ -7,9 +7,9 @@ import {
|
||||
MessageFolderDriver,
|
||||
} from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface';
|
||||
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { GmailFoldersErrorHandlerService } from 'src/modules/messaging/message-folder-manager/drivers/gmail/services/gmail-folders-error-handler.service';
|
||||
import { extractGmailFolderName } from 'src/modules/messaging/message-folder-manager/drivers/gmail/utils/extract-gmail-folder-name.util';
|
||||
import { getGmailFolderParentId } from 'src/modules/messaging/message-folder-manager/drivers/gmail/utils/get-gmail-folder-parent-id.util';
|
||||
@@ -27,13 +27,10 @@ export class GmailGetAllFoldersService implements MessageFolderDriver {
|
||||
|
||||
async getAllMessageFolders(
|
||||
connectedAccount: Pick<
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
ConnectedAccountEntity,
|
||||
'provider' | 'refreshToken' | 'accessToken' | 'id' | 'handle'
|
||||
>,
|
||||
messageChannel: Pick<
|
||||
MessageChannelWorkspaceEntity,
|
||||
'messageFolderImportPolicy'
|
||||
>,
|
||||
messageChannel: Pick<MessageChannelEntity, 'messageFolderImportPolicy'>,
|
||||
): Promise<DiscoveredMessageFolder[]> {
|
||||
try {
|
||||
const oAuth2Client =
|
||||
|
||||
+5
-11
@@ -8,8 +8,8 @@ import {
|
||||
MessageFolderDriver,
|
||||
} from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface';
|
||||
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { shouldCreateFolderByDefault } from 'src/modules/messaging/message-folder-manager/utils/should-create-folder-by-default.util';
|
||||
import { shouldSyncFolderByDefault } from 'src/modules/messaging/message-folder-manager/utils/should-sync-folder-by-default.util';
|
||||
import { ImapClientProvider } from 'src/modules/messaging/message-import-manager/drivers/imap/providers/imap-client.provider';
|
||||
@@ -27,13 +27,10 @@ export class ImapGetAllFoldersService implements MessageFolderDriver {
|
||||
|
||||
public async getAllMessageFolders(
|
||||
connectedAccount: Pick<
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
ConnectedAccountEntity,
|
||||
'id' | 'provider' | 'connectionParameters' | 'handle'
|
||||
>,
|
||||
messageChannel: Pick<
|
||||
MessageChannelWorkspaceEntity,
|
||||
'messageFolderImportPolicy'
|
||||
>,
|
||||
messageChannel: Pick<MessageChannelEntity, 'messageFolderImportPolicy'>,
|
||||
): Promise<DiscoveredMessageFolder[]> {
|
||||
try {
|
||||
const client = await this.imapClientProvider.getClient(connectedAccount);
|
||||
@@ -62,10 +59,7 @@ export class ImapGetAllFoldersService implements MessageFolderDriver {
|
||||
private async filterAndMapFolders(
|
||||
client: ImapFlow,
|
||||
mailboxList: ListResponse[],
|
||||
messageChannel: Pick<
|
||||
MessageChannelWorkspaceEntity,
|
||||
'messageFolderImportPolicy'
|
||||
>,
|
||||
messageChannel: Pick<MessageChannelEntity, 'messageFolderImportPolicy'>,
|
||||
): Promise<DiscoveredMessageFolder[]> {
|
||||
const folders: DiscoveredMessageFolder[] = [];
|
||||
const pathToExternalIdMap = new Map<string, string>();
|
||||
|
||||
+4
-7
@@ -7,9 +7,9 @@ import {
|
||||
MessageFolderDriver,
|
||||
} from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface';
|
||||
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { shouldCreateFolderByDefault } from 'src/modules/messaging/message-folder-manager/utils/should-create-folder-by-default.util';
|
||||
import { shouldSyncFolderByDefault } from 'src/modules/messaging/message-folder-manager/utils/should-sync-folder-by-default.util';
|
||||
import { MicrosoftMessageListFetchErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-message-list-fetch-error-handler.service';
|
||||
@@ -37,13 +37,10 @@ export class MicrosoftGetAllFoldersService implements MessageFolderDriver {
|
||||
|
||||
async getAllMessageFolders(
|
||||
connectedAccount: Pick<
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
ConnectedAccountEntity,
|
||||
'accessToken' | 'refreshToken' | 'id' | 'handle' | 'provider'
|
||||
>,
|
||||
messageChannel: Pick<
|
||||
MessageChannelWorkspaceEntity,
|
||||
'messageFolderImportPolicy'
|
||||
>,
|
||||
messageChannel: Pick<MessageChannelEntity, 'messageFolderImportPolicy'>,
|
||||
): Promise<DiscoveredMessageFolder[]> {
|
||||
try {
|
||||
const microsoftClient =
|
||||
|
||||
+7
-10
@@ -1,14 +1,14 @@
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { type MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { type MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { type MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity';
|
||||
|
||||
export type DiscoveredMessageFolder = Pick<
|
||||
MessageFolderWorkspaceEntity,
|
||||
MessageFolderEntity,
|
||||
'name' | 'isSynced' | 'isSentFolder' | 'externalId' | 'parentFolderId'
|
||||
>;
|
||||
|
||||
export type MessageFolder = Pick<
|
||||
MessageFolderWorkspaceEntity,
|
||||
MessageFolderEntity,
|
||||
| 'name'
|
||||
| 'isSynced'
|
||||
| 'isSentFolder'
|
||||
@@ -22,7 +22,7 @@ export type MessageFolder = Pick<
|
||||
export type MessageFolderDriver = {
|
||||
getAllMessageFolders(
|
||||
connectedAccount: Pick<
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
ConnectedAccountEntity,
|
||||
| 'provider'
|
||||
| 'accessToken'
|
||||
| 'refreshToken'
|
||||
@@ -30,9 +30,6 @@ export type MessageFolderDriver = {
|
||||
| 'handle'
|
||||
| 'connectionParameters'
|
||||
>,
|
||||
messageChannel: Pick<
|
||||
MessageChannelWorkspaceEntity,
|
||||
'messageFolderImportPolicy'
|
||||
>,
|
||||
messageChannel: Pick<MessageChannelEntity, 'messageFolderImportPolicy'>,
|
||||
): Promise<DiscoveredMessageFolder[]>;
|
||||
};
|
||||
|
||||
+2
-3
@@ -4,7 +4,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||
import { MessageFolderDataAccessModule } from 'src/engine/metadata-modules/message-folder/data-access/message-folder-data-access.module';
|
||||
import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity';
|
||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||
import { OAuth2ClientManagerModule } from 'src/modules/connected-account/oauth2-client-manager/oauth2-client-manager.module';
|
||||
import { GmailFoldersErrorHandlerService } from 'src/modules/messaging/message-folder-manager/drivers/gmail/services/gmail-folders-error-handler.service';
|
||||
@@ -21,8 +21,7 @@ import { MessagingMicrosoftDriverModule } from 'src/modules/messaging/message-im
|
||||
FeatureFlagModule,
|
||||
WorkspaceDataSourceModule,
|
||||
DataSourceModule,
|
||||
MessageFolderDataAccessModule,
|
||||
TypeOrmModule.forFeature([WorkspaceEntity]),
|
||||
TypeOrmModule.forFeature([WorkspaceEntity, MessageFolderEntity]),
|
||||
OAuth2ClientManagerModule,
|
||||
MessagingGmailDriverModule,
|
||||
MessagingMicrosoftDriverModule,
|
||||
|
||||
+55
-49
@@ -1,29 +1,27 @@
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import { In } from 'typeorm';
|
||||
|
||||
import { type DiscoveredMessageFolder } from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface';
|
||||
|
||||
import { MessageFolderDataAccessService } from 'src/engine/metadata-modules/message-folder/data-access/services/message-folder-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import {
|
||||
ConnectedAccountProvider,
|
||||
MessageChannelContactAutoCreationPolicy,
|
||||
MessageChannelType,
|
||||
MessageChannelVisibility,
|
||||
MessageFolderImportPolicy,
|
||||
} 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';
|
||||
} from 'twenty-shared/types';
|
||||
import { In } from 'typeorm';
|
||||
|
||||
import { type DiscoveredMessageFolder } from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface';
|
||||
|
||||
import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { GmailGetAllFoldersService } from 'src/modules/messaging/message-folder-manager/drivers/gmail/services/gmail-get-all-folders.service';
|
||||
import { ImapGetAllFoldersService } from 'src/modules/messaging/message-folder-manager/drivers/imap/services/imap-get-all-folders.service';
|
||||
import { MicrosoftGetAllFoldersService } from 'src/modules/messaging/message-folder-manager/drivers/microsoft/services/microsoft-get-all-folders.service';
|
||||
import { SyncMessageFoldersService } from 'src/modules/messaging/message-folder-manager/services/sync-message-folders.service';
|
||||
|
||||
type SyncedMessageFolder = Pick<
|
||||
MessageFolderWorkspaceEntity,
|
||||
MessageFolderEntity,
|
||||
| 'id'
|
||||
| 'name'
|
||||
| 'isSynced'
|
||||
@@ -111,7 +109,7 @@ describe('SyncMessageFoldersService', () => {
|
||||
let service: SyncMessageFoldersService;
|
||||
let gmailGetAllFoldersService: jest.Mocked<GmailGetAllFoldersService>;
|
||||
|
||||
let mockMessageFolderDataAccessService: {
|
||||
let mockMessageFolderRepository: {
|
||||
delete: jest.Mock;
|
||||
update: jest.Mock;
|
||||
save: jest.Mock;
|
||||
@@ -119,7 +117,7 @@ describe('SyncMessageFoldersService', () => {
|
||||
};
|
||||
|
||||
let createdFolderRecords: Array<
|
||||
Partial<MessageFolderWorkspaceEntity> & {
|
||||
Partial<MessageFolderEntity> & {
|
||||
id: string;
|
||||
externalId: string;
|
||||
}
|
||||
@@ -128,10 +126,10 @@ describe('SyncMessageFoldersService', () => {
|
||||
beforeEach(async () => {
|
||||
createdFolderRecords = [];
|
||||
|
||||
mockMessageFolderDataAccessService = {
|
||||
mockMessageFolderRepository = {
|
||||
delete: jest.fn(),
|
||||
update: jest.fn().mockResolvedValue(undefined),
|
||||
save: jest.fn().mockImplementation(async (_workspaceId, folder) => {
|
||||
save: jest.fn().mockImplementation(async (folder) => {
|
||||
createdFolderRecords.push({
|
||||
...folder,
|
||||
id: `new-folder-${createdFolderRecords.length}-${Math.random().toString(36).substring(7)}`,
|
||||
@@ -141,7 +139,7 @@ describe('SyncMessageFoldersService', () => {
|
||||
externalId: folder.externalId as string,
|
||||
});
|
||||
}),
|
||||
find: jest.fn().mockImplementation(async (_workspaceId, where) => {
|
||||
find: jest.fn().mockImplementation(async ({ where }) => {
|
||||
if (!where?.externalId) {
|
||||
return [];
|
||||
}
|
||||
@@ -171,8 +169,8 @@ describe('SyncMessageFoldersService', () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: MessageFolderDataAccessService,
|
||||
useValue: mockMessageFolderDataAccessService,
|
||||
provide: getRepositoryToken(MessageFolderEntity),
|
||||
useValue: mockMessageFolderRepository,
|
||||
},
|
||||
{
|
||||
provide: GmailGetAllFoldersService,
|
||||
@@ -228,18 +226,18 @@ describe('SyncMessageFoldersService', () => {
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
expect(mockMessageFolderDataAccessService.save).toHaveBeenCalledWith(
|
||||
workspaceId,
|
||||
expect(mockMessageFolderRepository.save).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
workspaceId,
|
||||
name: 'INBOX',
|
||||
externalId: 'inbox-ext',
|
||||
messageChannelId: 'channel-123',
|
||||
isSentFolder: false,
|
||||
}),
|
||||
);
|
||||
expect(mockMessageFolderDataAccessService.save).toHaveBeenCalledWith(
|
||||
workspaceId,
|
||||
expect(mockMessageFolderRepository.save).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
workspaceId,
|
||||
name: 'Sent',
|
||||
externalId: 'sent-ext',
|
||||
messageChannelId: 'channel-123',
|
||||
@@ -275,9 +273,9 @@ describe('SyncMessageFoldersService', () => {
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
expect(mockMessageFolderDataAccessService.save).toHaveBeenCalledWith(
|
||||
workspaceId,
|
||||
expect(mockMessageFolderRepository.save).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
workspaceId,
|
||||
name: 'Projects',
|
||||
parentFolderId: 'parent-folder-id',
|
||||
}),
|
||||
@@ -311,9 +309,8 @@ describe('SyncMessageFoldersService', () => {
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
expect(mockMessageFolderDataAccessService.update).toHaveBeenCalledWith(
|
||||
workspaceId,
|
||||
{ id: 'folder-1', messageChannelId: 'channel-123' },
|
||||
expect(mockMessageFolderRepository.update).toHaveBeenCalledWith(
|
||||
{ id: 'folder-1', messageChannelId: 'channel-123', workspaceId },
|
||||
expect.objectContaining({ name: 'Primary Inbox' }),
|
||||
);
|
||||
expect(result).toContainEqual(
|
||||
@@ -325,11 +322,16 @@ describe('SyncMessageFoldersService', () => {
|
||||
});
|
||||
|
||||
it('should update folder when parent folder changes', async () => {
|
||||
const newParentFolder = createMockExistingFolder({
|
||||
id: 'new-parent-uuid',
|
||||
externalId: 'new-parent-id',
|
||||
name: 'New Parent',
|
||||
});
|
||||
const existingFolder = createMockExistingFolder({
|
||||
id: 'folder-1',
|
||||
externalId: 'child-ext',
|
||||
name: 'Projects',
|
||||
parentFolderId: 'old-parent-id',
|
||||
parentFolderId: 'old-parent-uuid',
|
||||
});
|
||||
const discoveredFolders = [
|
||||
createMockDiscoveredFolder({
|
||||
@@ -337,9 +339,13 @@ describe('SyncMessageFoldersService', () => {
|
||||
name: 'Projects',
|
||||
parentFolderId: 'new-parent-id',
|
||||
}),
|
||||
createMockDiscoveredFolder({
|
||||
externalId: 'new-parent-id',
|
||||
name: 'New Parent',
|
||||
}),
|
||||
];
|
||||
const messageChannel = createMockMessageChannel({
|
||||
messageFolders: [existingFolder],
|
||||
messageFolders: [existingFolder, newParentFolder],
|
||||
});
|
||||
|
||||
gmailGetAllFoldersService.getAllMessageFolders.mockResolvedValue(
|
||||
@@ -351,11 +357,10 @@ describe('SyncMessageFoldersService', () => {
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
expect(mockMessageFolderDataAccessService.update).toHaveBeenCalledWith(
|
||||
workspaceId,
|
||||
{ id: 'folder-1', messageChannelId: 'channel-123' },
|
||||
expect(mockMessageFolderRepository.update).toHaveBeenCalledWith(
|
||||
{ id: 'folder-1', messageChannelId: 'channel-123', workspaceId },
|
||||
expect.objectContaining({
|
||||
parentFolderId: 'new-parent-id',
|
||||
parentFolderId: 'new-parent-uuid',
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -389,9 +394,7 @@ describe('SyncMessageFoldersService', () => {
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
expect(
|
||||
mockMessageFolderDataAccessService.update,
|
||||
).not.toHaveBeenCalled();
|
||||
expect(mockMessageFolderRepository.update).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -428,9 +431,8 @@ describe('SyncMessageFoldersService', () => {
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
expect(mockMessageFolderDataAccessService.update).toHaveBeenCalledWith(
|
||||
workspaceId,
|
||||
{ id: In(['folder-2']) },
|
||||
expect(mockMessageFolderRepository.update).toHaveBeenCalledWith(
|
||||
{ id: In(['folder-2']), workspaceId },
|
||||
expect.objectContaining({
|
||||
pendingSyncAction: 'FOLDER_DELETION',
|
||||
}),
|
||||
@@ -491,21 +493,25 @@ describe('SyncMessageFoldersService', () => {
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
expect(mockMessageFolderDataAccessService.update).toHaveBeenCalledWith(
|
||||
workspaceId,
|
||||
{ id: In(['folder-to-delete']) },
|
||||
expect(mockMessageFolderRepository.update).toHaveBeenCalledWith(
|
||||
{ id: In(['folder-to-delete']), workspaceId },
|
||||
expect.objectContaining({
|
||||
pendingSyncAction: 'FOLDER_DELETION',
|
||||
}),
|
||||
);
|
||||
expect(mockMessageFolderDataAccessService.update).toHaveBeenCalledWith(
|
||||
workspaceId,
|
||||
{ id: 'folder-to-update', messageChannelId: 'channel-123' },
|
||||
expect(mockMessageFolderRepository.update).toHaveBeenCalledWith(
|
||||
{
|
||||
id: 'folder-to-update',
|
||||
messageChannelId: 'channel-123',
|
||||
workspaceId,
|
||||
},
|
||||
expect.objectContaining({ name: 'New Name' }),
|
||||
);
|
||||
expect(mockMessageFolderDataAccessService.save).toHaveBeenCalledWith(
|
||||
workspaceId,
|
||||
expect.objectContaining({ externalId: 'new-ext' }),
|
||||
expect(mockMessageFolderRepository.save).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
workspaceId,
|
||||
externalId: 'new-ext',
|
||||
}),
|
||||
);
|
||||
expect(result).toHaveLength(4);
|
||||
expect(result).toContainEqual(
|
||||
|
||||
+41
-39
@@ -1,20 +1,23 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import {
|
||||
ConnectedAccountProvider,
|
||||
MessageFolderPendingSyncAction,
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { In } from 'typeorm';
|
||||
import { In, Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
DiscoveredMessageFolder,
|
||||
MessageFolder,
|
||||
} from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface';
|
||||
|
||||
import { MessageFolderDataAccessService } from 'src/engine/metadata-modules/message-folder/data-access/services/message-folder-data-access.service';
|
||||
import { type MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { MessageFolderPendingSyncAction } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { GmailGetAllFoldersService } from 'src/modules/messaging/message-folder-manager/drivers/gmail/services/gmail-get-all-folders.service';
|
||||
import { ImapGetAllFoldersService } from 'src/modules/messaging/message-folder-manager/drivers/imap/services/imap-get-all-folders.service';
|
||||
import { MicrosoftGetAllFoldersService } from 'src/modules/messaging/message-folder-manager/drivers/microsoft/services/microsoft-get-all-folders.service';
|
||||
@@ -27,7 +30,8 @@ import { computeUpdatedFolders } from 'src/modules/messaging/message-folder-mana
|
||||
export class SyncMessageFoldersService {
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageFolderDataAccessService: MessageFolderDataAccessService,
|
||||
@InjectRepository(MessageFolderEntity)
|
||||
private readonly messageFolderRepository: Repository<MessageFolderEntity>,
|
||||
private readonly gmailGetAllFoldersService: GmailGetAllFoldersService,
|
||||
private readonly microsoftGetAllFoldersService: MicrosoftGetAllFoldersService,
|
||||
private readonly imapGetAllFoldersService: ImapGetAllFoldersService,
|
||||
@@ -38,11 +42,11 @@ export class SyncMessageFoldersService {
|
||||
workspaceId,
|
||||
}: {
|
||||
messageChannel: Pick<
|
||||
MessageChannelWorkspaceEntity,
|
||||
MessageChannelEntity,
|
||||
'id' | 'messageFolderImportPolicy'
|
||||
> & {
|
||||
connectedAccount: Pick<
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
ConnectedAccountEntity,
|
||||
| 'provider'
|
||||
| 'accessToken'
|
||||
| 'refreshToken'
|
||||
@@ -72,7 +76,7 @@ export class SyncMessageFoldersService {
|
||||
|
||||
async discoverAllFolders(
|
||||
connectedAccount: Pick<
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
ConnectedAccountEntity,
|
||||
| 'accessToken'
|
||||
| 'refreshToken'
|
||||
| 'id'
|
||||
@@ -80,10 +84,7 @@ export class SyncMessageFoldersService {
|
||||
| 'provider'
|
||||
| 'connectionParameters'
|
||||
>,
|
||||
messageChannel: Pick<
|
||||
MessageChannelWorkspaceEntity,
|
||||
'messageFolderImportPolicy'
|
||||
>,
|
||||
messageChannel: Pick<MessageChannelEntity, 'messageFolderImportPolicy'>,
|
||||
): Promise<DiscoveredMessageFolder[]> {
|
||||
switch (connectedAccount.provider) {
|
||||
case ConnectedAccountProvider.GOOGLE:
|
||||
@@ -114,6 +115,12 @@ export class SyncMessageFoldersService {
|
||||
messageChannelId: string,
|
||||
workspaceId: string,
|
||||
): Promise<MessageFolder[]> {
|
||||
const externalIdToUuidMap = new Map(
|
||||
existingFolders
|
||||
.filter((folder) => isDefined(folder.externalId))
|
||||
.map((folder) => [folder.externalId as string, folder.id]),
|
||||
);
|
||||
|
||||
const foldersToCreate = computeFoldersToCreate({
|
||||
discoveredFolders,
|
||||
existingFolders,
|
||||
@@ -123,6 +130,7 @@ export class SyncMessageFoldersService {
|
||||
const foldersToUpdate = computeFoldersToUpdate({
|
||||
discoveredFolders,
|
||||
existingFolders,
|
||||
externalIdToUuidMap,
|
||||
});
|
||||
|
||||
const folderIdsToDelete = computeFolderIdsToDelete({
|
||||
@@ -132,16 +140,11 @@ export class SyncMessageFoldersService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
// TODO: Restore transaction wrapper once migration is complete — folder
|
||||
// sync operations (create/update/delete) are no longer atomic since
|
||||
// the data access layer routes writes across workspace and core schemas.
|
||||
// Acceptable during transition as sync is idempotent and self-corrects.
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
async () => {
|
||||
if (folderIdsToDelete.length > 0) {
|
||||
await this.messageFolderDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(folderIdsToDelete) },
|
||||
await this.messageFolderRepository.update(
|
||||
{ id: In(folderIdsToDelete), workspaceId },
|
||||
{
|
||||
pendingSyncAction: MessageFolderPendingSyncAction.FOLDER_DELETION,
|
||||
},
|
||||
@@ -150,32 +153,34 @@ export class SyncMessageFoldersService {
|
||||
|
||||
if (foldersToUpdate.size > 0) {
|
||||
for (const [id, data] of foldersToUpdate.entries()) {
|
||||
await this.messageFolderDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id, messageChannelId },
|
||||
data,
|
||||
await this.messageFolderRepository.update(
|
||||
{ id, messageChannelId, workspaceId },
|
||||
data as Record<string, unknown>,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (foldersToCreate.length > 0) {
|
||||
for (const folderToCreate of foldersToCreate) {
|
||||
await this.messageFolderDataAccessService.save(
|
||||
await this.messageFolderRepository.save({
|
||||
...folderToCreate,
|
||||
workspaceId,
|
||||
folderToCreate,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const createdFolders =
|
||||
foldersToCreate.length > 0
|
||||
? await this.messageFolderDataAccessService.find(workspaceId, {
|
||||
messageChannelId,
|
||||
externalId: In(
|
||||
foldersToCreate
|
||||
.map((folder) => folder.externalId)
|
||||
.filter(isDefined),
|
||||
),
|
||||
? await this.messageFolderRepository.find({
|
||||
where: {
|
||||
messageChannelId,
|
||||
externalId: In(
|
||||
foldersToCreate
|
||||
.map((folder) => folder.externalId)
|
||||
.filter(isDefined),
|
||||
),
|
||||
workspaceId,
|
||||
},
|
||||
})
|
||||
: [];
|
||||
|
||||
@@ -185,10 +190,7 @@ export class SyncMessageFoldersService {
|
||||
folderIdsToDelete,
|
||||
});
|
||||
|
||||
return [
|
||||
...updatedExistingFolders,
|
||||
...(createdFolders as MessageFolder[]),
|
||||
];
|
||||
return [...updatedExistingFolders, ...createdFolders];
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { MessageFolderPendingSyncAction } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { MessageFolderPendingSyncAction } from 'twenty-shared/types';
|
||||
import { computeFolderIdsToDelete } from 'src/modules/messaging/message-folder-manager/utils/compute-folder-ids-to-delete.util';
|
||||
|
||||
describe('computeFolderIdsToDelete', () => {
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { MessageFolderPendingSyncAction } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { MessageFolderPendingSyncAction } from 'twenty-shared/types';
|
||||
import { computeFoldersToCreate } from 'src/modules/messaging/message-folder-manager/utils/compute-folders-to-create.util';
|
||||
|
||||
describe('computeFoldersToCreate', () => {
|
||||
|
||||
+16
-4
@@ -1,7 +1,9 @@
|
||||
import { MessageFolderPendingSyncAction } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { MessageFolderPendingSyncAction } from 'twenty-shared/types';
|
||||
import { computeFoldersToUpdate } from 'src/modules/messaging/message-folder-manager/utils/compute-folders-to-update.util';
|
||||
|
||||
describe('computeFoldersToUpdate', () => {
|
||||
const emptyMap = new Map<string, string>();
|
||||
|
||||
it('should detect folder rename from provider', () => {
|
||||
const discoveredFolders = [
|
||||
{
|
||||
@@ -29,19 +31,22 @@ describe('computeFoldersToUpdate', () => {
|
||||
const result = computeFoldersToUpdate({
|
||||
discoveredFolders,
|
||||
existingFolders,
|
||||
externalIdToUuidMap: emptyMap,
|
||||
});
|
||||
|
||||
expect(result.get('folder-id')?.name).toBe('Work Emails');
|
||||
});
|
||||
|
||||
it('should detect folder moved to different parent', () => {
|
||||
const existingParentUuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee';
|
||||
|
||||
const discoveredFolders = [
|
||||
{
|
||||
name: 'Subfolder',
|
||||
externalId: 'sub-1',
|
||||
isSynced: true,
|
||||
isSentFolder: false,
|
||||
parentFolderId: 'new-parent-id',
|
||||
parentFolderId: 'new-parent-ext',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -52,18 +57,23 @@ describe('computeFoldersToUpdate', () => {
|
||||
externalId: 'sub-1',
|
||||
isSynced: true,
|
||||
isSentFolder: false,
|
||||
parentFolderId: 'old-parent-id',
|
||||
parentFolderId: 'old-parent-uuid',
|
||||
syncCursor: 'cursor',
|
||||
pendingSyncAction: MessageFolderPendingSyncAction.NONE,
|
||||
},
|
||||
];
|
||||
|
||||
const externalIdToUuidMap = new Map([
|
||||
['new-parent-ext', existingParentUuid],
|
||||
]);
|
||||
|
||||
const result = computeFoldersToUpdate({
|
||||
discoveredFolders,
|
||||
existingFolders,
|
||||
externalIdToUuidMap,
|
||||
});
|
||||
|
||||
expect(result.get('folder-id')?.parentFolderId).toBe('new-parent-id');
|
||||
expect(result.get('folder-id')?.parentFolderId).toBe(existingParentUuid);
|
||||
});
|
||||
|
||||
it('should not flag unchanged folders for update', () => {
|
||||
@@ -88,6 +98,7 @@ describe('computeFoldersToUpdate', () => {
|
||||
const result = computeFoldersToUpdate({
|
||||
discoveredFolders,
|
||||
existingFolders,
|
||||
externalIdToUuidMap: emptyMap,
|
||||
});
|
||||
|
||||
expect(result.size).toBe(0);
|
||||
@@ -120,6 +131,7 @@ describe('computeFoldersToUpdate', () => {
|
||||
const result = computeFoldersToUpdate({
|
||||
discoveredFolders,
|
||||
existingFolders,
|
||||
externalIdToUuidMap: emptyMap,
|
||||
});
|
||||
|
||||
expect(result.size).toBe(0);
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { MessageFolderPendingSyncAction } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { MessageFolderPendingSyncAction } from 'twenty-shared/types';
|
||||
import { computeUpdatedFolders } from 'src/modules/messaging/message-folder-manager/utils/compute-updated-folders.util';
|
||||
|
||||
describe('computeUpdatedFolders', () => {
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { MessageFolderImportPolicy } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { MessageFolderImportPolicy } from 'twenty-shared/types';
|
||||
import { shouldSyncFolderByDefault } from 'src/modules/messaging/message-folder-manager/utils/should-sync-folder-by-default.util';
|
||||
|
||||
describe('shouldSyncFolderByDefault', () => {
|
||||
|
||||
+2
-2
@@ -5,7 +5,7 @@ import {
|
||||
type MessageFolder,
|
||||
} from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface';
|
||||
|
||||
import { type MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { type MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity';
|
||||
|
||||
export const computeFoldersToCreate = ({
|
||||
discoveredFolders,
|
||||
@@ -15,7 +15,7 @@ export const computeFoldersToCreate = ({
|
||||
discoveredFolders: DiscoveredMessageFolder[];
|
||||
existingFolders: MessageFolder[];
|
||||
messageChannelId: string;
|
||||
}): Partial<MessageFolderWorkspaceEntity>[] => {
|
||||
}): Partial<MessageFolderEntity>[] => {
|
||||
const existingFoldersByExternalId = new Map(
|
||||
existingFolders.map((folder) => [folder.externalId, folder]),
|
||||
);
|
||||
|
||||
+12
-9
@@ -6,23 +6,22 @@ import {
|
||||
type MessageFolder,
|
||||
} from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface';
|
||||
|
||||
import { type MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { type MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity';
|
||||
|
||||
export const computeFoldersToUpdate = ({
|
||||
discoveredFolders,
|
||||
existingFolders,
|
||||
externalIdToUuidMap,
|
||||
}: {
|
||||
discoveredFolders: DiscoveredMessageFolder[];
|
||||
existingFolders: MessageFolder[];
|
||||
}): Map<string, Partial<MessageFolderWorkspaceEntity>> => {
|
||||
externalIdToUuidMap: Map<string, string>;
|
||||
}): Map<string, Partial<MessageFolderEntity>> => {
|
||||
const existingFoldersByExternalId = new Map(
|
||||
existingFolders.map((folder) => [folder.externalId, folder]),
|
||||
);
|
||||
|
||||
const foldersToUpdate = new Map<
|
||||
string,
|
||||
Partial<MessageFolderWorkspaceEntity>
|
||||
>();
|
||||
const foldersToUpdate = new Map<string, Partial<MessageFolderEntity>>();
|
||||
|
||||
for (const discoveredFolder of discoveredFolders) {
|
||||
const existingFolder = existingFoldersByExternalId.get(
|
||||
@@ -33,12 +32,16 @@ export const computeFoldersToUpdate = ({
|
||||
continue;
|
||||
}
|
||||
|
||||
const resolvedParentFolderId = isNonEmptyString(
|
||||
discoveredFolder.parentFolderId,
|
||||
)
|
||||
? (externalIdToUuidMap.get(discoveredFolder.parentFolderId) ?? null)
|
||||
: null;
|
||||
|
||||
const discoveredFolderData = {
|
||||
name: discoveredFolder.name,
|
||||
isSentFolder: discoveredFolder.isSentFolder,
|
||||
parentFolderId: isNonEmptyString(discoveredFolder.parentFolderId)
|
||||
? discoveredFolder.parentFolderId
|
||||
: null,
|
||||
parentFolderId: resolvedParentFolderId,
|
||||
};
|
||||
|
||||
const existingFolderData = {
|
||||
|
||||
+4
-5
@@ -1,9 +1,8 @@
|
||||
import { type MessageFolder } from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface';
|
||||
|
||||
import {
|
||||
MessageFolderPendingSyncAction,
|
||||
type MessageFolderWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { MessageFolderPendingSyncAction } from 'twenty-shared/types';
|
||||
|
||||
import { type MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity';
|
||||
|
||||
export const computeUpdatedFolders = ({
|
||||
existingFolders,
|
||||
@@ -11,7 +10,7 @@ export const computeUpdatedFolders = ({
|
||||
folderIdsToDelete,
|
||||
}: {
|
||||
existingFolders: MessageFolder[];
|
||||
foldersToUpdate: Map<string, Partial<MessageFolderWorkspaceEntity>>;
|
||||
foldersToUpdate: Map<string, Partial<MessageFolderEntity>>;
|
||||
folderIdsToDelete: string[];
|
||||
}): MessageFolder[] => {
|
||||
return existingFolders.map((existingFolder) => {
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { MessageFolderImportPolicy } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { MessageFolderImportPolicy } from 'twenty-shared/types';
|
||||
|
||||
export const shouldSyncFolderByDefault = (
|
||||
messageFolderImportPolicy: MessageFolderImportPolicy,
|
||||
|
||||
+12
-10
@@ -1,18 +1,20 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command, CommandRunner, Option } from 'nest-commander';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { MessageChannelSyncStage } from 'twenty-shared/types';
|
||||
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
|
||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import {
|
||||
MessagingMessageListFetchJob,
|
||||
type MessagingMessageListFetchJobData,
|
||||
} from 'src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job';
|
||||
import { MessageChannelSyncStage } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
|
||||
type MessagingTriggerMessageListFetchCommandOptions = {
|
||||
workspaceId: string;
|
||||
@@ -31,7 +33,8 @@ export class MessagingTriggerMessageListFetchCommand extends CommandRunner {
|
||||
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
@InjectRepository(MessageChannelEntity)
|
||||
private readonly messageChannelRepository: Repository<MessageChannelEntity>,
|
||||
@InjectMessageQueue(MessageQueue.messagingQueue)
|
||||
private readonly messageQueueService: MessageQueueService,
|
||||
) {
|
||||
@@ -51,14 +54,14 @@ export class MessagingTriggerMessageListFetchCommand extends CommandRunner {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannels = await this.messageChannelDataAccessService.find(
|
||||
workspaceId,
|
||||
{
|
||||
const messageChannels = await this.messageChannelRepository.find({
|
||||
where: {
|
||||
isSyncEnabled: true,
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING,
|
||||
...(messageChannelId ? { id: messageChannelId } : {}),
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
if (messageChannels.length === 0) {
|
||||
this.logger.warn(
|
||||
@@ -73,9 +76,8 @@ export class MessagingTriggerMessageListFetchCommand extends CommandRunner {
|
||||
);
|
||||
|
||||
for (const messageChannel of messageChannels) {
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: messageChannel.id },
|
||||
await this.messageChannelRepository.update(
|
||||
{ id: messageChannel.id, workspaceId },
|
||||
{
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED,
|
||||
syncStageStartedAt: new Date().toISOString(),
|
||||
|
||||
+4
-19
@@ -1,20 +1,17 @@
|
||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { FeatureFlagKey } from 'twenty-shared/types';
|
||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
|
||||
import { MessageChannelSyncStage } from 'twenty-shared/types';
|
||||
import { SentryCronMonitor } from 'src/engine/core-modules/cron/sentry-cron-monitor.decorator';
|
||||
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
|
||||
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
|
||||
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
|
||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util';
|
||||
import { MessageChannelSyncStage } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import {
|
||||
MessagingMessageListFetchJob,
|
||||
type MessagingMessageListFetchJobData,
|
||||
@@ -32,7 +29,6 @@ export class MessagingMessageListFetchCronJob {
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
private readonly exceptionHandlerService: ExceptionHandlerService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
) {}
|
||||
|
||||
@Process(MessagingMessageListFetchCronJob.name)
|
||||
@@ -51,22 +47,11 @@ export class MessagingMessageListFetchCronJob {
|
||||
try {
|
||||
const now = new Date().toISOString();
|
||||
|
||||
// TODO: remove workspace schema branch once IS_CONNECTED_ACCOUNT_MIGRATED feature flag is removed
|
||||
const isMigrated = await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED,
|
||||
activeWorkspace.id,
|
||||
const [messageChannels] = await this.coreDataSource.query(
|
||||
`UPDATE core."messageChannel" SET "syncStage" = '${MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED}', "syncStageStartedAt" = COALESCE("syncStageStartedAt", '${now}')
|
||||
WHERE "workspaceId" = '${activeWorkspace.id}' AND "isSyncEnabled" = true AND "syncStage" = '${MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING}' RETURNING *`,
|
||||
);
|
||||
|
||||
const [messageChannels] = isMigrated
|
||||
? await this.coreDataSource.query(
|
||||
`UPDATE core."messageChannel" SET "syncStage" = '${MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED}', "syncStageStartedAt" = COALESCE("syncStageStartedAt", '${now}')
|
||||
WHERE "workspaceId" = '${activeWorkspace.id}' AND "isSyncEnabled" = true AND "syncStage" = '${MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING}' RETURNING *`,
|
||||
)
|
||||
: await this.coreDataSource.query(
|
||||
`UPDATE ${getWorkspaceSchemaName(activeWorkspace.id)}."messageChannel" SET "syncStage" = '${MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED}', "syncStageStartedAt" = COALESCE("syncStageStartedAt", '${now}')
|
||||
WHERE "isSyncEnabled" = true AND "syncStage" = '${MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING}' RETURNING *`,
|
||||
);
|
||||
|
||||
for (const messageChannel of messageChannels) {
|
||||
await this.messageQueueService.add<MessagingMessageListFetchJobData>(
|
||||
MessagingMessageListFetchJob.name,
|
||||
|
||||
+4
-22
@@ -1,13 +1,12 @@
|
||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { FeatureFlagKey } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
|
||||
import { MessageChannelSyncStage } from 'twenty-shared/types';
|
||||
import { SentryCronMonitor } from 'src/engine/core-modules/cron/sentry-cron-monitor.decorator';
|
||||
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
|
||||
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
|
||||
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
|
||||
@@ -18,8 +17,6 @@ import {
|
||||
DataSourceException,
|
||||
DataSourceExceptionCode,
|
||||
} from 'src/engine/metadata-modules/data-source/data-source.exception';
|
||||
import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util';
|
||||
import { MessageChannelSyncStage } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import {
|
||||
MessagingMessagesImportJob,
|
||||
type MessagingMessagesImportJobData,
|
||||
@@ -37,7 +34,6 @@ export class MessagingMessagesImportCronJob {
|
||||
private readonly exceptionHandlerService: ExceptionHandlerService,
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
) {}
|
||||
|
||||
@Process(MessagingMessagesImportCronJob.name)
|
||||
@@ -56,22 +52,11 @@ export class MessagingMessagesImportCronJob {
|
||||
try {
|
||||
const now = new Date().toISOString();
|
||||
|
||||
// TODO: remove workspace schema branch once IS_CONNECTED_ACCOUNT_MIGRATED feature flag is removed
|
||||
const isMigrated = await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED,
|
||||
activeWorkspace.id,
|
||||
const [messageChannels] = await this.coreDataSource.query(
|
||||
`UPDATE core."messageChannel" SET "syncStage" = '${MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED}', "syncStageStartedAt" = COALESCE("syncStageStartedAt", '${now}')
|
||||
WHERE "workspaceId" = '${activeWorkspace.id}' AND "isSyncEnabled" = true AND "syncStage" = '${MessageChannelSyncStage.MESSAGES_IMPORT_PENDING}' RETURNING *`,
|
||||
);
|
||||
|
||||
const [messageChannels] = isMigrated
|
||||
? await this.coreDataSource.query(
|
||||
`UPDATE core."messageChannel" SET "syncStage" = '${MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED}', "syncStageStartedAt" = COALESCE("syncStageStartedAt", '${now}')
|
||||
WHERE "workspaceId" = '${activeWorkspace.id}' AND "isSyncEnabled" = true AND "syncStage" = '${MessageChannelSyncStage.MESSAGES_IMPORT_PENDING}' RETURNING *`,
|
||||
)
|
||||
: await this.coreDataSource.query(
|
||||
`UPDATE ${getWorkspaceSchemaName(activeWorkspace.id)}."messageChannel" SET "syncStage" = '${MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED}', "syncStageStartedAt" = COALESCE("syncStageStartedAt", '${now}')
|
||||
WHERE "isSyncEnabled" = true AND "syncStage" = '${MessageChannelSyncStage.MESSAGES_IMPORT_PENDING}' RETURNING *`,
|
||||
);
|
||||
|
||||
for (const messageChannel of messageChannels) {
|
||||
await this.messageQueueService.add<MessagingMessagesImportJobData>(
|
||||
MessagingMessagesImportJob.name,
|
||||
@@ -82,9 +67,6 @@ export class MessagingMessagesImportCronJob {
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
// We had issues with the workspace schema not being found, due
|
||||
// to users deleting their workspaces in the middle of the cron job
|
||||
// We only throw an error when the workspace is found & schema not found
|
||||
if (
|
||||
error.code === '42P01' &&
|
||||
error.message.includes('messageChannel" does not exist')
|
||||
|
||||
+4
-4
@@ -3,6 +3,10 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
MessageChannelSyncStage,
|
||||
MessageChannelSyncStatus,
|
||||
} from 'twenty-shared/types';
|
||||
import { SentryCronMonitor } from 'src/engine/core-modules/cron/sentry-cron-monitor.decorator';
|
||||
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
|
||||
@@ -12,10 +16,6 @@ import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queu
|
||||
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util';
|
||||
import {
|
||||
MessageChannelSyncStage,
|
||||
MessageChannelSyncStatus,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import {
|
||||
MessagingRelaunchFailedMessageChannelJob,
|
||||
type MessagingRelaunchFailedMessageChannelJobData,
|
||||
|
||||
+7
-5
@@ -1,14 +1,16 @@
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { google } from 'googleapis';
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import {
|
||||
ConnectedAccountProvider,
|
||||
MessageFolderImportPolicy,
|
||||
MessageFolderPendingSyncAction,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import { type MessageFolder } from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface';
|
||||
|
||||
import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { MessageFolderImportPolicy } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { MessageFolderPendingSyncAction } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { GmailGetHistoryService } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-history.service';
|
||||
import { GmailGetMessageListService } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-message-list.service';
|
||||
import { GmailMessageListFetchErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-message-list-fetch-error-handler.service';
|
||||
@@ -31,7 +33,7 @@ describe('GmailGetMessageListService', () => {
|
||||
let oAuth2ClientManagerService: OAuth2ClientManagerService;
|
||||
|
||||
const mockConnectedAccount: Pick<
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
ConnectedAccountEntity,
|
||||
| 'provider'
|
||||
| 'accessToken'
|
||||
| 'refreshToken'
|
||||
|
||||
+7
-12
@@ -3,13 +3,11 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { google } from 'googleapis';
|
||||
|
||||
import { MessageFolderImportPolicy } from 'twenty-shared/types';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import {
|
||||
MessageChannelWorkspaceEntity,
|
||||
MessageFolderImportPolicy,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { type MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { type MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity';
|
||||
import {
|
||||
MessageImportDriverException,
|
||||
MessageImportDriverExceptionCode,
|
||||
@@ -32,17 +30,14 @@ export class GmailGetMessageListService {
|
||||
|
||||
private async getMessageListWithoutCursor(
|
||||
connectedAccount: Pick<
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
ConnectedAccountEntity,
|
||||
'provider' | 'accessToken' | 'refreshToken' | 'id' | 'handle'
|
||||
>,
|
||||
messageFolders: Pick<
|
||||
MessageFolderWorkspaceEntity,
|
||||
MessageFolderEntity,
|
||||
'name' | 'externalId' | 'isSynced' | 'parentFolderId'
|
||||
>[],
|
||||
messageChannel: Pick<
|
||||
MessageChannelWorkspaceEntity,
|
||||
'messageFolderImportPolicy'
|
||||
>,
|
||||
messageChannel: Pick<MessageChannelEntity, 'messageFolderImportPolicy'>,
|
||||
): Promise<GetMessageListsResponse> {
|
||||
const oAuth2Client =
|
||||
await this.oAuth2ClientManagerService.getGoogleOAuth2Client(
|
||||
|
||||
+6
-11
@@ -5,12 +5,10 @@ import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { type gmail_v1 as gmailV1, google } from 'googleapis';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { MessageFolderImportPolicy } from 'twenty-shared/types';
|
||||
import { type MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import {
|
||||
type MessageChannelWorkspaceEntity,
|
||||
MessageFolderImportPolicy,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { GmailMessagesImportErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-messages-import-error-handler.service';
|
||||
import { filterGmailMessagesByFolderPolicy } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/filter-gmail-messages-by-folder-policy.util';
|
||||
import { parseAndFormatGmailMessage } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-and-format-gmail-message.util';
|
||||
@@ -28,7 +26,7 @@ export class GmailGetMessagesService {
|
||||
async getMessages(
|
||||
messageIds: string[],
|
||||
connectedAccount: Pick<
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
ConnectedAccountEntity,
|
||||
| 'provider'
|
||||
| 'accessToken'
|
||||
| 'refreshToken'
|
||||
@@ -37,7 +35,7 @@ export class GmailGetMessagesService {
|
||||
| 'handleAliases'
|
||||
>,
|
||||
messageChannel: Pick<
|
||||
MessageChannelWorkspaceEntity,
|
||||
MessageChannelEntity,
|
||||
'messageFolders' | 'messageFolderImportPolicy'
|
||||
>,
|
||||
): Promise<MessageWithParticipants[]> {
|
||||
@@ -169,10 +167,7 @@ export class GmailGetMessagesService {
|
||||
private async fetchMessages(
|
||||
gmailClient: gmailV1.Gmail,
|
||||
messageIds: string[],
|
||||
connectedAccount: Pick<
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
'handle' | 'handleAliases'
|
||||
>,
|
||||
connectedAccount: Pick<ConnectedAccountEntity, 'handle' | 'handleAliases'>,
|
||||
): Promise<MessageWithParticipants[]> {
|
||||
const results = await Promise.all(
|
||||
messageIds.map((messageId) =>
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { MessageFolderImportPolicy } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { MessageFolderImportPolicy } from 'twenty-shared/types';
|
||||
import { computeGmailExcludeSearchFilter } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/compute-gmail-exclude-search-filter.util';
|
||||
|
||||
describe('computeGmailExcludeSearchFilter', () => {
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
import { MessageFolderImportPolicy } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { type MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { MessageFolderImportPolicy } from 'twenty-shared/types';
|
||||
import { type MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity';
|
||||
import { filterGmailMessagesByFolderPolicy } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/filter-gmail-messages-by-folder-policy.util';
|
||||
import { type MessageWithParticipants } from 'src/modules/messaging/message-import-manager/types/message';
|
||||
|
||||
@@ -12,8 +12,8 @@ const createMessage = (
|
||||
const createFolder = (
|
||||
externalId: string,
|
||||
isSynced: boolean,
|
||||
): MessageFolderWorkspaceEntity =>
|
||||
({ externalId, isSynced }) as MessageFolderWorkspaceEntity;
|
||||
): MessageFolderEntity =>
|
||||
({ externalId, isSynced }) as unknown as MessageFolderEntity;
|
||||
|
||||
describe('filterGmailMessagesByFolderPolicy', () => {
|
||||
describe('ALL_FOLDERS policy', () => {
|
||||
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
import { type MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { type MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity';
|
||||
|
||||
const MAXIMUM_GMAIL_FOLDER_DEPTH = 50;
|
||||
|
||||
type FolderInput = Pick<
|
||||
MessageFolderWorkspaceEntity,
|
||||
MessageFolderEntity,
|
||||
'externalId' | 'name' | 'parentFolderId'
|
||||
>;
|
||||
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { MessageFolderImportPolicy } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { type MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { MessageFolderImportPolicy } from 'twenty-shared/types';
|
||||
import { type MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity';
|
||||
import { MESSAGING_GMAIL_DEFAULT_EXCLUDED_LABELS } from 'src/modules/messaging/message-import-manager/drivers/gmail/constants/messaging-gmail-default-excluded-labels.constant';
|
||||
import { MESSAGING_GMAIL_EXCLUDED_SYSTEM_LABELS } from 'src/modules/messaging/message-import-manager/drivers/gmail/constants/messaging-gmail-excluded-system-labels.constant';
|
||||
import { MESSAGING_GMAIL_FOLDERS_WITH_CATEGORY_EXCLUSIONS } from 'src/modules/messaging/message-import-manager/drivers/gmail/constants/messaging-gmail-folders-with-category-exclusions.constant';
|
||||
@@ -10,7 +10,7 @@ import { computeGmailDefaultNotSyncedLabelsSearchFilter } from 'src/modules/mess
|
||||
|
||||
export const computeGmailExcludeSearchFilter = (
|
||||
messageFolders: Pick<
|
||||
MessageFolderWorkspaceEntity,
|
||||
MessageFolderEntity,
|
||||
'externalId' | 'isSynced' | 'name' | 'parentFolderId'
|
||||
>[],
|
||||
messageFolderImportPolicy: MessageFolderImportPolicy,
|
||||
|
||||
+2
-5
@@ -1,12 +1,9 @@
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { MessageDirection } from 'src/modules/messaging/common/enums/message-direction.enum';
|
||||
|
||||
export const computeMessageDirection = (
|
||||
fromHandle: string,
|
||||
connectedAccount: Pick<
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
'handle' | 'handleAliases'
|
||||
>,
|
||||
connectedAccount: Pick<ConnectedAccountEntity, 'handle' | 'handleAliases'>,
|
||||
): MessageDirection =>
|
||||
connectedAccount.handle === fromHandle ||
|
||||
connectedAccount.handleAliases?.includes(fromHandle)
|
||||
|
||||
+3
-5
@@ -1,7 +1,5 @@
|
||||
import {
|
||||
type MessageChannelWorkspaceEntity,
|
||||
MessageFolderImportPolicy,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { MessageFolderImportPolicy } from 'twenty-shared/types';
|
||||
import { type MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { MESSAGING_GMAIL_EXCLUDED_CATEGORY_LABELS } from 'src/modules/messaging/message-import-manager/drivers/gmail/constants/messaging-gmail-excluded-category-labels.constant';
|
||||
import { MESSAGING_GMAIL_FOLDERS_WITH_CATEGORY_EXCLUSIONS } from 'src/modules/messaging/message-import-manager/drivers/gmail/constants/messaging-gmail-folders-with-category-exclusions.constant';
|
||||
import { type MessageWithParticipants } from 'src/modules/messaging/message-import-manager/types/message';
|
||||
@@ -9,7 +7,7 @@ import { type MessageWithParticipants } from 'src/modules/messaging/message-impo
|
||||
export const filterGmailMessagesByFolderPolicy = (
|
||||
messages: MessageWithParticipants[],
|
||||
messageChannel: Pick<
|
||||
MessageChannelWorkspaceEntity,
|
||||
MessageChannelEntity,
|
||||
'messageFolders' | 'messageFolderImportPolicy'
|
||||
>,
|
||||
): MessageWithParticipants[] => {
|
||||
|
||||
+2
-5
@@ -2,7 +2,7 @@ import { type gmail_v1 as gmailV1 } from 'googleapis';
|
||||
import planer from 'planer';
|
||||
import { MessageParticipantRole } from 'twenty-shared/types';
|
||||
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { computeMessageDirection } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/compute-message-direction.util';
|
||||
import { parseGmailMessage } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-gmail-message.util';
|
||||
import { type MessageWithParticipants } from 'src/modules/messaging/message-import-manager/types/message';
|
||||
@@ -11,10 +11,7 @@ import { sanitizeString } from 'src/modules/messaging/message-import-manager/uti
|
||||
|
||||
export const parseAndFormatGmailMessage = (
|
||||
message: gmailV1.Schema$Message,
|
||||
connectedAccount: Pick<
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
'handle' | 'handleAliases'
|
||||
>,
|
||||
connectedAccount: Pick<ConnectedAccountEntity, 'handle' | 'handleAliases'>,
|
||||
): MessageWithParticipants | null => {
|
||||
const {
|
||||
id,
|
||||
|
||||
+2
-2
@@ -6,12 +6,12 @@ import { CustomError, isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type ImapSmtpCaldavParams } from 'src/engine/core-modules/imap-smtp-caldav-connection/types/imap-smtp-caldav-connection.type';
|
||||
import { SecureHttpClientService } from 'src/engine/core-modules/secure-http-client/secure-http-client.service';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { MessageImportDriverExceptionCode } from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
|
||||
import { parseImapAuthenticationError } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/parse-imap-authentication-error.util';
|
||||
|
||||
type ConnectedAccountIdentifier = Pick<
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
ConnectedAccountEntity,
|
||||
'id' | 'provider' | 'connectionParameters' | 'handle'
|
||||
>;
|
||||
|
||||
|
||||
+7
-5
@@ -1,12 +1,14 @@
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import {
|
||||
ConnectedAccountProvider,
|
||||
MessageFolderImportPolicy,
|
||||
MessageFolderPendingSyncAction,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import { type MessageFolder } from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface';
|
||||
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { MessageFolderImportPolicy } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { MessageFolderPendingSyncAction } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { ImapClientProvider } from 'src/modules/messaging/message-import-manager/drivers/imap/providers/imap-client.provider';
|
||||
import { ImapGetMessageListService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-message-list.service';
|
||||
import { ImapMessageListFetchErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-list-fetch-error-handler.service';
|
||||
@@ -29,7 +31,7 @@ describe('ImapGetMessageListService', () => {
|
||||
let imapClientProvider: ImapClientProvider;
|
||||
|
||||
const mockConnectedAccount: Pick<
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
ConnectedAccountEntity,
|
||||
| 'provider'
|
||||
| 'accessToken'
|
||||
| 'refreshToken'
|
||||
|
||||
+1
-1
@@ -3,9 +3,9 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
import { type ImapFlow } from 'imapflow';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { MessageFolderImportPolicy } from 'twenty-shared/types';
|
||||
import { MessageFolder } from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface';
|
||||
|
||||
import { MessageFolderImportPolicy } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import {
|
||||
MessageImportDriverException,
|
||||
MessageImportDriverExceptionCode,
|
||||
|
||||
+3
-6
@@ -4,7 +4,7 @@ import { type ImapFlow } from 'imapflow';
|
||||
import { Address, type Email as ParsedMail } from 'postal-mime';
|
||||
import { MessageParticipantRole } from 'twenty-shared/types';
|
||||
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { computeMessageDirection } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/compute-message-direction.util';
|
||||
import { ImapClientProvider } from 'src/modules/messaging/message-import-manager/drivers/imap/providers/imap-client.provider';
|
||||
import { ImapMessageParserService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-parser.service';
|
||||
@@ -17,7 +17,7 @@ import { formatAddressObjectAsParticipants } from 'src/modules/messaging/message
|
||||
import { sanitizeString } from 'src/modules/messaging/message-import-manager/utils/sanitize-string.util';
|
||||
|
||||
type ConnectedAccount = Pick<
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
ConnectedAccountEntity,
|
||||
'id' | 'provider' | 'handle' | 'handleAliases' | 'connectionParameters'
|
||||
>;
|
||||
|
||||
@@ -162,10 +162,7 @@ export class ImapGetMessagesService {
|
||||
uid: number,
|
||||
folderPath: string,
|
||||
folderExternalId: string,
|
||||
connectedAccount: Pick<
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
'handle' | 'handleAliases'
|
||||
>,
|
||||
connectedAccount: Pick<ConnectedAccountEntity, 'handle' | 'handleAliases'>,
|
||||
): MessageWithParticipants {
|
||||
const fromAddresses = this.extractAddresses(parsed.from);
|
||||
const senderAddress = fromAddresses[0]?.address ?? '';
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { type MicrosoftGraphBatchResponse } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-messages.interface';
|
||||
|
||||
@Injectable()
|
||||
@@ -13,7 +13,7 @@ export class MicrosoftFetchByBatchService {
|
||||
async fetchAllByBatches(
|
||||
messageIds: string[],
|
||||
connectedAccount: Pick<
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
ConnectedAccountEntity,
|
||||
'accessToken' | 'refreshToken' | 'id' | 'provider'
|
||||
>,
|
||||
): Promise<{
|
||||
|
||||
+16
-18
@@ -1,20 +1,18 @@
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import {
|
||||
ConnectedAccountProvider,
|
||||
MessageFolderImportPolicy,
|
||||
MessageFolderPendingSyncAction,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty-config.module';
|
||||
import { MicrosoftOAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/drivers/microsoft/microsoft-oauth2-client-manager.service';
|
||||
import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import {
|
||||
MessageChannelWorkspaceEntity,
|
||||
MessageFolderImportPolicy,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import {
|
||||
MessageFolderPendingSyncAction,
|
||||
MessageFolderWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity';
|
||||
import { microsoftGraphWithMessagesDeltaLink } from 'src/modules/messaging/message-import-manager/drivers/microsoft/mocks/microsoft-api-examples';
|
||||
import { MessageFolderName } from 'src/modules/messaging/message-import-manager/drivers/microsoft/types/folders';
|
||||
|
||||
@@ -26,7 +24,7 @@ const accessToken = 'replace-with-your-access-token';
|
||||
const refreshToken = 'replace-with-your-refresh-token';
|
||||
const syncCursor = `replace-with-your-sync-cursor`;
|
||||
const mockConnectedAccount: Pick<
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
ConnectedAccountEntity,
|
||||
| 'provider'
|
||||
| 'accessToken'
|
||||
| 'refreshToken'
|
||||
@@ -43,7 +41,7 @@ const mockConnectedAccount: Pick<
|
||||
};
|
||||
|
||||
const mockMessageChannel: Pick<
|
||||
MessageChannelWorkspaceEntity,
|
||||
MessageChannelEntity,
|
||||
'id' | 'syncCursor' | 'messageFolderImportPolicy'
|
||||
> = {
|
||||
id: 'message-channel-id',
|
||||
@@ -178,7 +176,7 @@ xdescribe('Microsoft dev tests : get message list service', () => {
|
||||
xdescribe('Microsoft dev tests : get message list service for folders', () => {
|
||||
let service: MicrosoftGetMessageListService;
|
||||
|
||||
const inboxFolder = new MessageFolderWorkspaceEntity();
|
||||
const inboxFolder = new MessageFolderEntity();
|
||||
|
||||
inboxFolder.id = 'inbox-folder-id';
|
||||
inboxFolder.name = MessageFolderName.INBOX;
|
||||
@@ -186,7 +184,7 @@ xdescribe('Microsoft dev tests : get message list service for folders', () => {
|
||||
inboxFolder.messageChannelId = 'message-channel-1';
|
||||
inboxFolder.parentFolderId = null;
|
||||
|
||||
const sentFolder = new MessageFolderWorkspaceEntity();
|
||||
const sentFolder = new MessageFolderEntity();
|
||||
|
||||
sentFolder.id = 'sent-folder-id';
|
||||
sentFolder.name = MessageFolderName.SENT_ITEMS;
|
||||
@@ -194,7 +192,7 @@ xdescribe('Microsoft dev tests : get message list service for folders', () => {
|
||||
sentFolder.messageChannelId = 'message-channel-1';
|
||||
sentFolder.parentFolderId = null;
|
||||
|
||||
const otherFolder = new MessageFolderWorkspaceEntity();
|
||||
const otherFolder = new MessageFolderEntity();
|
||||
|
||||
otherFolder.id = 'other-folder-id';
|
||||
otherFolder.name = 'other';
|
||||
@@ -202,7 +200,7 @@ xdescribe('Microsoft dev tests : get message list service for folders', () => {
|
||||
otherFolder.messageChannelId = 'message-channel-2';
|
||||
otherFolder.parentFolderId = null;
|
||||
|
||||
const messageChannelNoFolders = new MessageChannelWorkspaceEntity();
|
||||
const messageChannelNoFolders = new MessageChannelEntity();
|
||||
|
||||
messageChannelNoFolders.id = 'message-channel-0';
|
||||
messageChannelNoFolders.messageFolders = [];
|
||||
@@ -210,7 +208,7 @@ xdescribe('Microsoft dev tests : get message list service for folders', () => {
|
||||
messageChannelNoFolders.messageFolderImportPolicy =
|
||||
MessageFolderImportPolicy.SELECTED_FOLDERS;
|
||||
|
||||
const messageChannelMicrosoftOneFolder = new MessageChannelWorkspaceEntity();
|
||||
const messageChannelMicrosoftOneFolder = new MessageChannelEntity();
|
||||
|
||||
messageChannelMicrosoftOneFolder.id = 'message-channel-1';
|
||||
messageChannelMicrosoftOneFolder.messageFolders = [inboxFolder];
|
||||
@@ -218,7 +216,7 @@ xdescribe('Microsoft dev tests : get message list service for folders', () => {
|
||||
messageChannelMicrosoftOneFolder.messageFolderImportPolicy =
|
||||
MessageFolderImportPolicy.SELECTED_FOLDERS;
|
||||
|
||||
const messageChannelMicrosoft = new MessageChannelWorkspaceEntity();
|
||||
const messageChannelMicrosoft = new MessageChannelEntity();
|
||||
|
||||
messageChannelMicrosoft.id = 'message-channel-2';
|
||||
messageChannelMicrosoft.messageFolders = [inboxFolder, sentFolder];
|
||||
|
||||
+7
-5
@@ -1,13 +1,15 @@
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import {
|
||||
ConnectedAccountProvider,
|
||||
MessageFolderImportPolicy,
|
||||
MessageFolderPendingSyncAction,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import { type MessageFolder } from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface';
|
||||
|
||||
import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { MessageFolderImportPolicy } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { MessageFolderPendingSyncAction } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { MicrosoftGetMessageListService } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-message-list.service';
|
||||
import { MicrosoftMessageListFetchErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-message-list-fetch-error-handler.service';
|
||||
|
||||
@@ -28,7 +30,7 @@ describe('MicrosoftGetMessageListService', () => {
|
||||
let oAuth2ClientManagerService: OAuth2ClientManagerService;
|
||||
|
||||
const mockConnectedAccount: Pick<
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
ConnectedAccountEntity,
|
||||
| 'provider'
|
||||
| 'accessToken'
|
||||
| 'refreshToken'
|
||||
|
||||
+5
-5
@@ -8,10 +8,10 @@ import {
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import pLimit from 'p-limit';
|
||||
|
||||
import { MessageFolderImportPolicy } from 'twenty-shared/types';
|
||||
import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { MessageFolderImportPolicy } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { type MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { type MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity';
|
||||
import { MicrosoftMessageListFetchErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-message-list-fetch-error-handler.service';
|
||||
import { type GetMessageListsArgs } from 'src/modules/messaging/message-import-manager/types/get-message-lists-args.type';
|
||||
import {
|
||||
@@ -72,11 +72,11 @@ export class MicrosoftGetMessageListService {
|
||||
|
||||
public async getMessageList(
|
||||
connectedAccount: Pick<
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
ConnectedAccountEntity,
|
||||
'provider' | 'accessToken' | 'id'
|
||||
>,
|
||||
messageFolder: Pick<
|
||||
MessageFolderWorkspaceEntity,
|
||||
MessageFolderEntity,
|
||||
'name' | 'syncCursor' | 'externalId'
|
||||
>,
|
||||
): Promise<GetOneMessageListResponse> {
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ xdescribe('Microsoft dev tests : get messages service', () => {
|
||||
id: 'connected-account-id',
|
||||
provider: ConnectedAccountProvider.MICROSOFT,
|
||||
handle: 'John.Walker@outlook.fr',
|
||||
handleAliases: '',
|
||||
handleAliases: [] as string[],
|
||||
accessToken: accessToken,
|
||||
refreshToken: refreshToken,
|
||||
};
|
||||
|
||||
+2
-2
@@ -69,7 +69,7 @@ describe('Microsoft get messages service', () => {
|
||||
accessToken: 'access-token',
|
||||
refreshToken: 'refresh-token',
|
||||
handle: 'John.l@outlook.fr',
|
||||
handleAliases: '',
|
||||
handleAliases: [] as string[],
|
||||
};
|
||||
const messages = service.formatBatchResponsesAsMessages(
|
||||
batchResponses,
|
||||
@@ -170,7 +170,7 @@ describe('Microsoft get messages service', () => {
|
||||
accessToken: 'access-token',
|
||||
refreshToken: 'refresh-token',
|
||||
handle: 'John.l@outlook.fr',
|
||||
handleAliases: '',
|
||||
handleAliases: [] as string[],
|
||||
};
|
||||
const messages = service.formatBatchResponsesAsMessages(
|
||||
batchResponses,
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ import { type EmailAddress } from 'addressparser';
|
||||
import { MessageParticipantRole } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { MessageDirection } from 'src/modules/messaging/common/enums/message-direction.enum';
|
||||
import { computeMessageDirection } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/compute-message-direction.util';
|
||||
import { MicrosoftImportDriverException } from 'src/modules/messaging/message-import-manager/drivers/microsoft/exceptions/microsoft-import-driver.exception';
|
||||
@@ -17,7 +17,7 @@ import { MicrosoftFetchByBatchService } from './microsoft-fetch-by-batch.service
|
||||
import { MicrosoftMessagesImportErrorHandler } from './microsoft-messages-import-error-handler.service';
|
||||
|
||||
type ConnectedAccountType = Pick<
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
ConnectedAccountEntity,
|
||||
| 'accessToken'
|
||||
| 'refreshToken'
|
||||
| 'id'
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
import type SMTPConnection from 'nodemailer/lib/smtp-connection';
|
||||
|
||||
import { SecureHttpClientService } from 'src/engine/core-modules/secure-http-client/secure-http-client.service';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
|
||||
@Injectable()
|
||||
export class SmtpClientProvider {
|
||||
@@ -16,7 +16,7 @@ export class SmtpClientProvider {
|
||||
|
||||
public async getSmtpClient(
|
||||
connectedAccount: Pick<
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
ConnectedAccountEntity,
|
||||
'connectionParameters' | 'handle'
|
||||
>,
|
||||
): Promise<Transporter> {
|
||||
|
||||
+7
-8
@@ -1,14 +1,14 @@
|
||||
import { type Provider } from '@nestjs/common';
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import {
|
||||
MessageChannelSyncStage,
|
||||
MessageChannelSyncStatus,
|
||||
type MessageChannelWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
} from 'twenty-shared/types';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { MessagingRelaunchFailedMessageChannelJob } from 'src/modules/messaging/message-import-manager/jobs/messaging-relaunch-failed-message-channel.job';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
|
||||
describe('MessagingRelaunchFailedMessageChannelJob', () => {
|
||||
let job: MessagingRelaunchFailedMessageChannelJob;
|
||||
@@ -33,7 +33,7 @@ describe('MessagingRelaunchFailedMessageChannelJob', () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: MessageChannelDataAccessService,
|
||||
provide: getRepositoryToken(MessageChannelEntity),
|
||||
useValue: {
|
||||
findOne: mockFindOne,
|
||||
update: mockUpdate,
|
||||
@@ -56,13 +56,12 @@ describe('MessagingRelaunchFailedMessageChannelJob', () => {
|
||||
throttleFailureCount: 5,
|
||||
throttleRetryAfter: '2026-03-19T06:49:34.295Z',
|
||||
syncStageStartedAt: '2026-03-19T06:34:34.000Z',
|
||||
} as Partial<MessageChannelWorkspaceEntity>);
|
||||
} as unknown as Partial<MessageChannelEntity>);
|
||||
|
||||
await job.handle({ workspaceId, messageChannelId });
|
||||
|
||||
expect(mockUpdate).toHaveBeenCalledWith(
|
||||
workspaceId,
|
||||
{ id: messageChannelId },
|
||||
{ id: messageChannelId, workspaceId },
|
||||
{
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING,
|
||||
syncStatus: MessageChannelSyncStatus.ACTIVE,
|
||||
|
||||
+15
-16
@@ -1,23 +1,23 @@
|
||||
import { Scope } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { MessageChannelSyncStage } from 'twenty-shared/types';
|
||||
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
|
||||
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
|
||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { isThrottled } from 'src/modules/connected-account/utils/is-throttled';
|
||||
import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
|
||||
import {
|
||||
MessageChannelSyncStage,
|
||||
type MessageChannelWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import {
|
||||
MessageImportExceptionHandlerService,
|
||||
MessageImportSyncStep,
|
||||
} from 'src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service';
|
||||
import { MessagingMessageListFetchService } from 'src/modules/messaging/message-import-manager/services/messaging-message-list-fetch.service';
|
||||
import { MessagingMonitoringService } from 'src/modules/messaging/monitoring/services/messaging-monitoring.service';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
|
||||
const toIsoStringOrNull = (
|
||||
value: string | Date | null | undefined,
|
||||
@@ -43,7 +43,8 @@ export class MessagingMessageListFetchJob {
|
||||
private readonly messagingMessageListFetchService: MessagingMessageListFetchService,
|
||||
private readonly messagingMonitoringService: MessagingMonitoringService,
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
@InjectRepository(MessageChannelEntity)
|
||||
private readonly messageChannelRepository: Repository<MessageChannelEntity>,
|
||||
private readonly messageImportErrorHandlerService: MessageImportExceptionHandlerService,
|
||||
private readonly messageChannelSyncStatusService: MessageChannelSyncStatusService,
|
||||
) {}
|
||||
@@ -61,15 +62,13 @@ export class MessagingMessageListFetchJob {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannel = await this.messageChannelDataAccessService.findOne(
|
||||
workspaceId,
|
||||
{
|
||||
where: {
|
||||
id: messageChannelId,
|
||||
},
|
||||
relations: ['connectedAccount', 'messageFolders'],
|
||||
const messageChannel = await this.messageChannelRepository.findOne({
|
||||
where: {
|
||||
id: messageChannelId,
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
relations: { connectedAccount: true, messageFolders: true },
|
||||
});
|
||||
|
||||
if (!messageChannel) {
|
||||
await this.messagingMonitoringService.track({
|
||||
@@ -113,7 +112,7 @@ export class MessagingMessageListFetchJob {
|
||||
});
|
||||
|
||||
await this.messagingMessageListFetchService.processMessageListFetch(
|
||||
messageChannel as unknown as MessageChannelWorkspaceEntity,
|
||||
messageChannel,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
@@ -127,7 +126,7 @@ export class MessagingMessageListFetchJob {
|
||||
await this.messageImportErrorHandlerService.handleDriverException(
|
||||
error,
|
||||
MessageImportSyncStep.MESSAGE_LIST_FETCH,
|
||||
messageChannel as unknown as MessageChannelWorkspaceEntity,
|
||||
messageChannel,
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
+15
-19
@@ -1,19 +1,19 @@
|
||||
import { Scope } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { MessageChannelSyncStage } from 'twenty-shared/types';
|
||||
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
|
||||
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
|
||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { isThrottled } from 'src/modules/connected-account/utils/is-throttled';
|
||||
import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
|
||||
import {
|
||||
MessageChannelSyncStage,
|
||||
type MessageChannelWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { MessagingMessagesImportService } from 'src/modules/messaging/message-import-manager/services/messaging-messages-import.service';
|
||||
import { MessagingMonitoringService } from 'src/modules/messaging/monitoring/services/messaging-monitoring.service';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
|
||||
const toIsoStringOrNull = (
|
||||
value: string | Date | null | undefined,
|
||||
@@ -40,7 +40,8 @@ export class MessagingMessagesImportJob {
|
||||
private readonly messagingMonitoringService: MessagingMonitoringService,
|
||||
private readonly messageChannelSyncStatusService: MessageChannelSyncStatusService,
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
@InjectRepository(MessageChannelEntity)
|
||||
private readonly messageChannelRepository: Repository<MessageChannelEntity>,
|
||||
) {}
|
||||
|
||||
@Process(MessagingMessagesImportJob.name)
|
||||
@@ -56,15 +57,13 @@ export class MessagingMessagesImportJob {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannel = await this.messageChannelDataAccessService.findOne(
|
||||
workspaceId,
|
||||
{
|
||||
where: {
|
||||
id: messageChannelId,
|
||||
},
|
||||
relations: ['connectedAccount', 'messageFolders'],
|
||||
const messageChannel = await this.messageChannelRepository.findOne({
|
||||
where: {
|
||||
id: messageChannelId,
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
relations: { connectedAccount: true, messageFolders: true },
|
||||
});
|
||||
|
||||
if (!messageChannel) {
|
||||
await this.messagingMonitoringService.track({
|
||||
@@ -103,12 +102,9 @@ export class MessagingMessagesImportJob {
|
||||
return;
|
||||
}
|
||||
|
||||
const messageChannelWorkspace =
|
||||
messageChannel as unknown as MessageChannelWorkspaceEntity;
|
||||
|
||||
await this.messagingMessagesImportService.processMessageBatchImport(
|
||||
messageChannelWorkspace,
|
||||
messageChannelWorkspace.connectedAccount,
|
||||
messageChannel,
|
||||
messageChannel.connectedAccount,
|
||||
workspaceId,
|
||||
);
|
||||
}, authContext);
|
||||
|
||||
+10
-8
@@ -1,15 +1,16 @@
|
||||
import { Logger, Scope } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { In } from 'typeorm';
|
||||
import { In, Repository } from 'typeorm';
|
||||
|
||||
import { MessageChannelSyncStage } from 'twenty-shared/types';
|
||||
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
|
||||
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
|
||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
|
||||
import { MessageChannelSyncStage } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { isSyncStale } from 'src/modules/messaging/message-import-manager/utils/is-sync-stale.util';
|
||||
|
||||
const toIsoStringOrNull = (
|
||||
@@ -34,7 +35,8 @@ export class MessagingOngoingStaleJob {
|
||||
private readonly logger = new Logger(MessagingOngoingStaleJob.name);
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
@InjectRepository(MessageChannelEntity)
|
||||
private readonly messageChannelRepository: Repository<MessageChannelEntity>,
|
||||
private readonly messageChannelSyncStatusService: MessageChannelSyncStatusService,
|
||||
) {}
|
||||
|
||||
@@ -45,17 +47,17 @@ export class MessagingOngoingStaleJob {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannels = await this.messageChannelDataAccessService.find(
|
||||
workspaceId,
|
||||
{
|
||||
const messageChannels = await this.messageChannelRepository.find({
|
||||
where: {
|
||||
syncStage: In([
|
||||
MessageChannelSyncStage.MESSAGES_IMPORT_ONGOING,
|
||||
MessageChannelSyncStage.MESSAGE_LIST_FETCH_ONGOING,
|
||||
MessageChannelSyncStage.MESSAGES_IMPORT_SCHEDULED,
|
||||
MessageChannelSyncStage.MESSAGE_LIST_FETCH_SCHEDULED,
|
||||
]),
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
for (const messageChannel of messageChannels) {
|
||||
if (isSyncStale(toIsoStringOrNull(messageChannel.syncStageStartedAt))) {
|
||||
|
||||
+19
-18
@@ -1,15 +1,18 @@
|
||||
import { Scope } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
|
||||
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
|
||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import {
|
||||
MessageChannelSyncStage,
|
||||
MessageChannelSyncStatus,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
} from 'twenty-shared/types';
|
||||
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
|
||||
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
|
||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
|
||||
export type MessagingRelaunchFailedMessageChannelJobData = {
|
||||
workspaceId: string;
|
||||
@@ -23,7 +26,8 @@ export type MessagingRelaunchFailedMessageChannelJobData = {
|
||||
export class MessagingRelaunchFailedMessageChannelJob {
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
@InjectRepository(MessageChannelEntity)
|
||||
private readonly messageChannelRepository: Repository<MessageChannelEntity>,
|
||||
) {}
|
||||
|
||||
@Process(MessagingRelaunchFailedMessageChannelJob.name)
|
||||
@@ -33,14 +37,12 @@ export class MessagingRelaunchFailedMessageChannelJob {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannel = await this.messageChannelDataAccessService.findOne(
|
||||
workspaceId,
|
||||
{
|
||||
where: {
|
||||
id: messageChannelId,
|
||||
},
|
||||
const messageChannel = await this.messageChannelRepository.findOne({
|
||||
where: {
|
||||
id: messageChannelId,
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
if (
|
||||
!messageChannel ||
|
||||
@@ -50,9 +52,8 @@ export class MessagingRelaunchFailedMessageChannelJob {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: messageChannelId },
|
||||
await this.messageChannelRepository.update(
|
||||
{ id: messageChannelId, workspaceId },
|
||||
{
|
||||
syncStage: MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING,
|
||||
syncStatus: MessageChannelSyncStatus.ACTIVE,
|
||||
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { type ObjectRecordDeleteEvent } from 'twenty-shared/database-events';
|
||||
|
||||
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
|
||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
|
||||
import { WorkspaceEventBatch } from 'src/engine/workspace-event-emitter/types/workspace-event-batch.type';
|
||||
import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import {
|
||||
MessagingCleanCacheJob,
|
||||
type MessagingCleanCacheJobData,
|
||||
} from 'src/modules/messaging/message-import-manager/jobs/messaging-clean-cache';
|
||||
import { OnDatabaseBatchEvent } from 'src/engine/api/graphql/graphql-query-runner/decorators/on-database-batch-event.decorator';
|
||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||
|
||||
@Injectable()
|
||||
export class MessagingMessageImportManagerMessageChannelListener {
|
||||
constructor(
|
||||
@InjectMessageQueue(MessageQueue.messagingQueue)
|
||||
private readonly messageQueueService: MessageQueueService,
|
||||
) {}
|
||||
|
||||
@OnDatabaseBatchEvent('messageChannel', DatabaseEventAction.DESTROYED)
|
||||
async handleDestroyedEvent(
|
||||
payload: WorkspaceEventBatch<
|
||||
ObjectRecordDeleteEvent<MessageChannelWorkspaceEntity>
|
||||
>,
|
||||
) {
|
||||
await Promise.all(
|
||||
payload.events.map((eventPayload) =>
|
||||
this.messageQueueService.add<MessagingCleanCacheJobData>(
|
||||
MessagingCleanCacheJob.name,
|
||||
{
|
||||
workspaceId: payload.workspaceId,
|
||||
messageChannelId: eventPayload.recordId,
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -2,10 +2,11 @@ import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
||||
import { MessageChannelDataAccessModule } from 'src/engine/metadata-modules/message-channel/data-access/message-channel-data-access.module';
|
||||
import { MessageFolderDataAccessModule } from 'src/engine/metadata-modules/message-folder/data-access/message-folder-data-access.module';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||
import { WorkspaceEventEmitterModule } from 'src/engine/workspace-event-emitter/workspace-event-emitter.module';
|
||||
@@ -36,7 +37,6 @@ import { MessagingMessageListFetchJob } from 'src/modules/messaging/message-impo
|
||||
import { MessagingMessagesImportJob } from 'src/modules/messaging/message-import-manager/jobs/messaging-messages-import.job';
|
||||
import { MessagingOngoingStaleJob } from 'src/modules/messaging/message-import-manager/jobs/messaging-ongoing-stale.job';
|
||||
import { MessagingRelaunchFailedMessageChannelJob } from 'src/modules/messaging/message-import-manager/jobs/messaging-relaunch-failed-message-channel.job';
|
||||
import { MessagingMessageImportManagerMessageChannelListener } from 'src/modules/messaging/message-import-manager/listeners/messaging-import-manager-message-channel.listener';
|
||||
import { MessagingAccountAuthenticationService } from 'src/modules/messaging/message-import-manager/services/messaging-account-authentication.service';
|
||||
import { MessagingCursorService } from 'src/modules/messaging/message-import-manager/services/messaging-cursor.service';
|
||||
import { MessagingDeleteFolderMessagesService } from 'src/modules/messaging/message-import-manager/services/messaging-delete-folder-messages.service';
|
||||
@@ -63,12 +63,13 @@ import { MessagingMonitoringModule } from 'src/modules/messaging/monitoring/mess
|
||||
MessagingIMAPDriverModule,
|
||||
MessagingSmtpDriverModule,
|
||||
MessagingCommonModule,
|
||||
MessageChannelDataAccessModule,
|
||||
MessageFolderDataAccessModule,
|
||||
TypeOrmModule.forFeature([
|
||||
WorkspaceEntity,
|
||||
DataSourceEntity,
|
||||
ObjectMetadataEntity,
|
||||
MessageChannelEntity,
|
||||
MessageFolderEntity,
|
||||
UserWorkspaceEntity,
|
||||
]),
|
||||
EmailAliasManagerModule,
|
||||
FeatureFlagModule,
|
||||
@@ -95,7 +96,6 @@ import { MessagingMonitoringModule } from 'src/modules/messaging/monitoring/mess
|
||||
MessagingOngoingStaleCronJob,
|
||||
MessagingRelaunchFailedMessageChannelsCronJob,
|
||||
MessagingAddSingleMessageToCacheForImportJob,
|
||||
MessagingMessageImportManagerMessageChannelListener,
|
||||
MessagingCleanCacheJob,
|
||||
MessagingMessageService,
|
||||
MessagingMessageFolderAssociationService,
|
||||
|
||||
+15
-18
@@ -1,19 +1,15 @@
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import {
|
||||
ConnectedAccountProvider,
|
||||
MessageFolderImportPolicy,
|
||||
MessageFolderPendingSyncAction,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
|
||||
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
|
||||
import {
|
||||
MessageFolderImportPolicy,
|
||||
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';
|
||||
import { MessagingMessageCleanerService } from 'src/modules/messaging/message-cleaner/services/messaging-message-cleaner.service';
|
||||
import { SyncMessageFoldersService } from 'src/modules/messaging/message-folder-manager/services/sync-message-folders.service';
|
||||
import { MessagingAccountAuthenticationService } from 'src/modules/messaging/message-import-manager/services/messaging-account-authentication.service';
|
||||
@@ -24,7 +20,8 @@ import { MessagingMessageListFetchService } from 'src/modules/messaging/message-
|
||||
import { MessagingMessagesImportService } from 'src/modules/messaging/message-import-manager/services/messaging-messages-import.service';
|
||||
import { MessagingProcessFolderActionsService } from 'src/modules/messaging/message-import-manager/services/messaging-process-folder-actions.service';
|
||||
import { MessagingProcessGroupEmailActionsService } from 'src/modules/messaging/message-import-manager/services/messaging-process-group-email-actions.service';
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
describe('MessagingMessageListFetchService', () => {
|
||||
let messagingMessageListFetchService: MessagingMessageListFetchService;
|
||||
@@ -34,8 +31,8 @@ describe('MessagingMessageListFetchService', () => {
|
||||
let globalWorkspaceOrmManager: GlobalWorkspaceOrmManager;
|
||||
let messagingCursorService: MessagingCursorService;
|
||||
|
||||
let mockMicrosoftMessageChannel: MessageChannelWorkspaceEntity;
|
||||
let mockGoogleMessageChannel: MessageChannelWorkspaceEntity;
|
||||
let mockMicrosoftMessageChannel: MessageChannelEntity;
|
||||
let mockGoogleMessageChannel: MessageChannelEntity;
|
||||
|
||||
const workspaceId = 'workspace-id';
|
||||
|
||||
@@ -48,7 +45,7 @@ describe('MessagingMessageListFetchService', () => {
|
||||
handle: 'test@microsoft.com',
|
||||
accessToken: 'old-microsoft-access-token',
|
||||
refreshToken: 'microsoft-refresh-token',
|
||||
handleAliases: '',
|
||||
handleAliases: [] as string[],
|
||||
},
|
||||
messageFolders: [
|
||||
{
|
||||
@@ -56,10 +53,10 @@ describe('MessagingMessageListFetchService', () => {
|
||||
name: 'inbox',
|
||||
syncCursor: 'inbox-sync-cursor',
|
||||
messageChannelId: 'microsoft-message-channel-id',
|
||||
} as MessageFolderWorkspaceEntity,
|
||||
},
|
||||
],
|
||||
messageFolderImportPolicy: MessageFolderImportPolicy.SELECTED_FOLDERS,
|
||||
} as MessageChannelWorkspaceEntity;
|
||||
} as unknown as MessageChannelEntity;
|
||||
|
||||
mockGoogleMessageChannel = {
|
||||
id: 'google-message-channel-id',
|
||||
@@ -69,12 +66,12 @@ describe('MessagingMessageListFetchService', () => {
|
||||
handle: 'test@gmail.com',
|
||||
accessToken: 'old-google-access-token',
|
||||
refreshToken: 'google-refresh-token',
|
||||
handleAliases: '',
|
||||
handleAliases: [] as string[],
|
||||
},
|
||||
syncCursor: 'google-sync-cursor',
|
||||
messageFolders: [],
|
||||
messageFolderImportPolicy: MessageFolderImportPolicy.SELECTED_FOLDERS,
|
||||
} as unknown as MessageChannelWorkspaceEntity;
|
||||
} as unknown as MessageChannelEntity;
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -231,7 +228,7 @@ describe('MessagingMessageListFetchService', () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: MessageChannelDataAccessService,
|
||||
provide: getRepositoryToken(MessageChannelEntity),
|
||||
useValue: {
|
||||
findOne: jest.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
|
||||
+32
-18
@@ -1,7 +1,11 @@
|
||||
import { Logger, type Provider } from '@nestjs/common';
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import {
|
||||
ConnectedAccountProvider,
|
||||
MessageChannelSyncStage,
|
||||
MessageFolderImportPolicy,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
|
||||
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
|
||||
@@ -9,21 +13,19 @@ import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspac
|
||||
import { BlocklistRepository } from 'src/modules/blocklist/repositories/blocklist.repository';
|
||||
import { EmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/services/email-alias-manager.service';
|
||||
import { ConnectedAccountRefreshTokensService } from 'src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
|
||||
import {
|
||||
MessageChannelSyncStage,
|
||||
MessageFolderImportPolicy,
|
||||
type MessageChannelWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { MESSAGING_GMAIL_USERS_MESSAGES_GET_BATCH_SIZE } from 'src/modules/messaging/message-import-manager/drivers/gmail/constants/messaging-gmail-users-messages-get-batch-size.constant';
|
||||
import { MessagingAccountAuthenticationService } from 'src/modules/messaging/message-import-manager/services/messaging-account-authentication.service';
|
||||
import { MessagingGetMessagesService } from 'src/modules/messaging/message-import-manager/services/messaging-get-messages.service';
|
||||
import { MessageImportExceptionHandlerService } from 'src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service';
|
||||
import { MessagingMessagesImportService } from 'src/modules/messaging/message-import-manager/services/messaging-messages-import.service';
|
||||
import { MessagingSaveMessagesAndEnqueueContactCreationService } from 'src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service';
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { MessagingMonitoringService } from 'src/modules/messaging/monitoring/services/messaging-monitoring.service';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
|
||||
describe('MessagingMessagesImportService', () => {
|
||||
let service: MessagingMessagesImportService;
|
||||
@@ -35,7 +37,7 @@ describe('MessagingMessagesImportService', () => {
|
||||
|
||||
const workspaceId = 'workspace-id';
|
||||
let mockMessageChannel: Pick<
|
||||
MessageChannelWorkspaceEntity,
|
||||
MessageChannelEntity,
|
||||
| 'id'
|
||||
| 'syncStage'
|
||||
| 'connectedAccountId'
|
||||
@@ -43,7 +45,7 @@ describe('MessagingMessagesImportService', () => {
|
||||
| 'messageFolders'
|
||||
| 'messageFolderImportPolicy'
|
||||
>;
|
||||
let mockConnectedAccount: ConnectedAccountWorkspaceEntity;
|
||||
let mockConnectedAccount: ConnectedAccountEntity;
|
||||
let providersBase: Provider[];
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -53,9 +55,9 @@ describe('MessagingMessagesImportService', () => {
|
||||
handle: 'test@gmail.com',
|
||||
refreshToken: 'refresh-token',
|
||||
accessToken: 'old-access-token',
|
||||
accountOwnerId: 'account-owner-id',
|
||||
handleAliases: 'alias1@gmail.com,alias2@gmail.com',
|
||||
} as ConnectedAccountWorkspaceEntity;
|
||||
userWorkspaceId: 'user-workspace-id',
|
||||
handleAliases: ['alias1@gmail.com', 'alias2@gmail.com'],
|
||||
} as ConnectedAccountEntity;
|
||||
|
||||
mockMessageChannel = {
|
||||
id: 'message-channel-id',
|
||||
@@ -114,7 +116,9 @@ describe('MessagingMessagesImportService', () => {
|
||||
{
|
||||
provide: EmailAliasManagerService,
|
||||
useValue: {
|
||||
refreshHandleAliases: jest.fn().mockResolvedValue(undefined),
|
||||
refreshHandleAliases: jest
|
||||
.fn()
|
||||
.mockResolvedValue(['alias1@gmail.com', 'alias2@gmail.com']),
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -122,6 +126,10 @@ describe('MessagingMessagesImportService', () => {
|
||||
useValue: {
|
||||
getRepository: jest.fn().mockResolvedValue({
|
||||
update: jest.fn().mockResolvedValue(undefined),
|
||||
findOne: jest.fn().mockResolvedValue({
|
||||
id: 'workspace-member-id',
|
||||
userId: 'user-id',
|
||||
}),
|
||||
}),
|
||||
executeInWorkspaceContext: jest
|
||||
.fn()
|
||||
@@ -129,7 +137,7 @@ describe('MessagingMessagesImportService', () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: MessageChannelDataAccessService,
|
||||
provide: getRepositoryToken(MessageChannelEntity),
|
||||
useValue: {
|
||||
update: jest.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
@@ -169,6 +177,12 @@ describe('MessagingMessagesImportService', () => {
|
||||
provide: MessagingAccountAuthenticationService,
|
||||
useClass: MessagingAccountAuthenticationService,
|
||||
},
|
||||
{
|
||||
provide: getRepositoryToken(UserWorkspaceEntity),
|
||||
useValue: {
|
||||
findOne: jest.fn().mockResolvedValue({ userId: 'user-id' }),
|
||||
},
|
||||
},
|
||||
];
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
@@ -219,7 +233,7 @@ describe('MessagingMessagesImportService', () => {
|
||||
|
||||
expect(
|
||||
service.processMessageBatchImport(
|
||||
mockMessageChannel as MessageChannelWorkspaceEntity,
|
||||
mockMessageChannel as MessageChannelEntity,
|
||||
mockConnectedAccount,
|
||||
workspaceId,
|
||||
),
|
||||
@@ -228,7 +242,7 @@ describe('MessagingMessagesImportService', () => {
|
||||
|
||||
it('should process message batch import successfully', async () => {
|
||||
await service.processMessageBatchImport(
|
||||
mockMessageChannel as MessageChannelWorkspaceEntity,
|
||||
mockMessageChannel as MessageChannelEntity,
|
||||
mockConnectedAccount,
|
||||
workspaceId,
|
||||
);
|
||||
@@ -312,7 +326,7 @@ describe('MessagingMessagesImportService', () => {
|
||||
);
|
||||
|
||||
await service.processMessageBatchImport(
|
||||
mockMessageChannel as MessageChannelWorkspaceEntity,
|
||||
mockMessageChannel as MessageChannelEntity,
|
||||
mockConnectedAccount,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
+2
-2
@@ -8,7 +8,7 @@ import {
|
||||
ConnectedAccountRefreshTokensService,
|
||||
type ConnectedAccountTokens,
|
||||
} from 'src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import {
|
||||
MessageImportDriverException,
|
||||
MessageImportDriverExceptionCode,
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
import { MessagingMonitoringService } from 'src/modules/messaging/monitoring/services/messaging-monitoring.service';
|
||||
|
||||
interface ValidateAndRefreshConnectedAccountAuthenticationParams {
|
||||
connectedAccount: ConnectedAccountWorkspaceEntity;
|
||||
connectedAccount: ConnectedAccountEntity;
|
||||
workspaceId: string;
|
||||
messageChannelId: string;
|
||||
}
|
||||
|
||||
+16
-21
@@ -1,21 +1,25 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { MessageFolderDataAccessService } from 'src/engine/metadata-modules/message-folder/data-access/services/message-folder-data-access.service';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
|
||||
@Injectable()
|
||||
export class MessagingCursorService {
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
private readonly messageFolderDataAccessService: MessageFolderDataAccessService,
|
||||
@InjectRepository(MessageChannelEntity)
|
||||
private readonly messageChannelRepository: Repository<MessageChannelEntity>,
|
||||
@InjectRepository(MessageFolderEntity)
|
||||
private readonly messageFolderRepository: Repository<MessageFolderEntity>,
|
||||
) {}
|
||||
|
||||
public async updateCursor(
|
||||
messageChannel: MessageChannelWorkspaceEntity,
|
||||
messageChannel: MessageChannelEntity,
|
||||
nextSyncCursor: string,
|
||||
workspaceId: string,
|
||||
folderId?: string,
|
||||
@@ -24,11 +28,8 @@ export class MessagingCursorService {
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
if (!folderId) {
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{
|
||||
id: messageChannel.id,
|
||||
},
|
||||
await this.messageChannelRepository.update(
|
||||
{ id: messageChannel.id, workspaceId },
|
||||
{
|
||||
throttleFailureCount: 0,
|
||||
throttleRetryAfter: null,
|
||||
@@ -41,20 +42,14 @@ export class MessagingCursorService {
|
||||
},
|
||||
);
|
||||
} else {
|
||||
await this.messageFolderDataAccessService.update(
|
||||
workspaceId,
|
||||
{
|
||||
id: folderId,
|
||||
},
|
||||
await this.messageFolderRepository.update(
|
||||
{ id: folderId, workspaceId },
|
||||
{
|
||||
syncCursor: nextSyncCursor,
|
||||
},
|
||||
);
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{
|
||||
id: messageChannel.id,
|
||||
},
|
||||
await this.messageChannelRepository.update(
|
||||
{ id: messageChannel.id, workspaceId },
|
||||
{
|
||||
throttleFailureCount: 0,
|
||||
throttleRetryAfter: null,
|
||||
|
||||
+4
-4
@@ -3,12 +3,12 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { In } from 'typeorm';
|
||||
|
||||
import { type MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { type MessageChannelMessageAssociationMessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association-message-folder.workspace-entity';
|
||||
import { type MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity';
|
||||
import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { type MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { type MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity';
|
||||
import { MessagingMessageCleanerService } from 'src/modules/messaging/message-cleaner/services/messaging-message-cleaner.service';
|
||||
|
||||
const BATCH_SIZE = 200;
|
||||
@@ -26,8 +26,8 @@ export class MessagingDeleteFolderMessagesService {
|
||||
|
||||
async deleteFolderMessages(
|
||||
workspaceId: string,
|
||||
messageChannel: MessageChannelWorkspaceEntity,
|
||||
messageFolder: MessageFolderWorkspaceEntity,
|
||||
messageChannel: MessageChannelEntity,
|
||||
messageFolder: MessageFolderEntity,
|
||||
): Promise<number> {
|
||||
this.logger.log(
|
||||
`WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id}, FolderId: ${messageFolder.id} - Deleting messages from folder: ${messageFolder.name}`,
|
||||
|
||||
+2
-2
@@ -2,9 +2,9 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
|
||||
import { type MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { type MessageFolder } from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface';
|
||||
|
||||
import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import {
|
||||
MessageImportDriverException,
|
||||
MessageImportDriverExceptionCode,
|
||||
@@ -23,7 +23,7 @@ export class MessagingGetMessageListService {
|
||||
) {}
|
||||
|
||||
public async getMessageLists(
|
||||
messageChannel: MessageChannelWorkspaceEntity,
|
||||
messageChannel: MessageChannelEntity,
|
||||
messageFolders: MessageFolder[],
|
||||
): Promise<GetMessageListsResponse> {
|
||||
switch (messageChannel.connectedAccount.provider) {
|
||||
|
||||
+5
-5
@@ -2,8 +2,8 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { type MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import {
|
||||
MessageImportDriverException,
|
||||
MessageImportDriverExceptionCode,
|
||||
@@ -26,18 +26,18 @@ export class MessagingGetMessagesService {
|
||||
public async getMessages(
|
||||
messageIds: string[],
|
||||
connectedAccount: Pick<
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
ConnectedAccountEntity,
|
||||
| 'provider'
|
||||
| 'accessToken'
|
||||
| 'refreshToken'
|
||||
| 'id'
|
||||
| 'handle'
|
||||
| 'handleAliases'
|
||||
| 'accountOwnerId'
|
||||
| 'userWorkspaceId'
|
||||
| 'connectionParameters'
|
||||
>,
|
||||
messageChannel: Pick<
|
||||
MessageChannelWorkspaceEntity,
|
||||
MessageChannelEntity,
|
||||
'messageFolders' | 'messageFolderImportPolicy'
|
||||
>,
|
||||
): Promise<GetMessagesResponse> {
|
||||
|
||||
+17
-25
@@ -1,24 +1,23 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { MessageChannelSyncStatus } from 'twenty-shared/types';
|
||||
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||
import {
|
||||
type TwentyORMException,
|
||||
TwentyORMExceptionCode,
|
||||
} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception';
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
|
||||
import {
|
||||
MessageChannelSyncStatus,
|
||||
type MessageChannelWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { MESSAGING_THROTTLE_MAX_ATTEMPTS } from 'src/modules/messaging/message-import-manager/constants/messaging-throttle-max-attempts';
|
||||
import {
|
||||
MessageImportDriverException,
|
||||
MessageImportDriverExceptionCode,
|
||||
} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
|
||||
import { MessageNetworkExceptionCode } from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-network.exception';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
|
||||
export enum MessageImportSyncStep {
|
||||
MESSAGE_LIST_FETCH = 'MESSAGE_LIST_FETCH',
|
||||
@@ -29,7 +28,8 @@ export enum MessageImportSyncStep {
|
||||
@Injectable()
|
||||
export class MessageImportExceptionHandlerService {
|
||||
constructor(
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
@InjectRepository(MessageChannelEntity)
|
||||
private readonly messageChannelRepository: Repository<MessageChannelEntity>,
|
||||
private readonly messageChannelSyncStatusService: MessageChannelSyncStatusService,
|
||||
private readonly exceptionHandlerService: ExceptionHandlerService,
|
||||
) {}
|
||||
@@ -37,10 +37,7 @@ export class MessageImportExceptionHandlerService {
|
||||
public async handleDriverException(
|
||||
exception: MessageImportDriverException | Error | TwentyORMException,
|
||||
syncStep: MessageImportSyncStep,
|
||||
messageChannel: Pick<
|
||||
MessageChannelWorkspaceEntity,
|
||||
'id' | 'throttleFailureCount'
|
||||
>,
|
||||
messageChannel: Pick<MessageChannelEntity, 'id' | 'throttleFailureCount'>,
|
||||
workspaceId: string,
|
||||
): Promise<void> {
|
||||
if (exception instanceof MessageImportDriverException) {
|
||||
@@ -105,7 +102,7 @@ export class MessageImportExceptionHandlerService {
|
||||
}
|
||||
|
||||
private async handleSyncCursorErrorException(
|
||||
messageChannel: Pick<MessageChannelWorkspaceEntity, 'id'>,
|
||||
messageChannel: Pick<MessageChannelEntity, 'id'>,
|
||||
workspaceId: string,
|
||||
): Promise<void> {
|
||||
await this.messageChannelSyncStatusService.resetAndMarkAsMessagesListFetchPending(
|
||||
@@ -116,10 +113,7 @@ export class MessageImportExceptionHandlerService {
|
||||
|
||||
private async handleTemporaryException(
|
||||
syncStep: MessageImportSyncStep,
|
||||
messageChannel: Pick<
|
||||
MessageChannelWorkspaceEntity,
|
||||
'id' | 'throttleFailureCount'
|
||||
>,
|
||||
messageChannel: Pick<MessageChannelEntity, 'id' | 'throttleFailureCount'>,
|
||||
workspaceId: string,
|
||||
exception: { message: string },
|
||||
): Promise<void> {
|
||||
@@ -151,9 +145,8 @@ export class MessageImportExceptionHandlerService {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.messageChannelDataAccessService.increment(
|
||||
workspaceId,
|
||||
{ id: messageChannel.id },
|
||||
await this.messageChannelRepository.increment(
|
||||
{ id: messageChannel.id, workspaceId },
|
||||
'throttleFailureCount',
|
||||
1,
|
||||
);
|
||||
@@ -163,9 +156,8 @@ export class MessageImportExceptionHandlerService {
|
||||
? exception.throttleRetryAfter
|
||||
: undefined;
|
||||
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: messageChannel.id },
|
||||
await this.messageChannelRepository.update(
|
||||
{ id: messageChannel.id, workspaceId },
|
||||
{
|
||||
throttleRetryAfter: isDefined(throttleRetryAfter)
|
||||
? throttleRetryAfter.toISOString()
|
||||
@@ -197,7 +189,7 @@ export class MessageImportExceptionHandlerService {
|
||||
}
|
||||
|
||||
private async handleInsufficientPermissionsException(
|
||||
messageChannel: Pick<MessageChannelWorkspaceEntity, 'id'>,
|
||||
messageChannel: Pick<MessageChannelEntity, 'id'>,
|
||||
workspaceId: string,
|
||||
): Promise<void> {
|
||||
await this.messageChannelSyncStatusService.markAsFailed(
|
||||
@@ -209,7 +201,7 @@ export class MessageImportExceptionHandlerService {
|
||||
|
||||
private async handleUnknownException(
|
||||
exception: Error,
|
||||
messageChannel: Pick<MessageChannelWorkspaceEntity, 'id'>,
|
||||
messageChannel: Pick<MessageChannelEntity, 'id'>,
|
||||
workspaceId: string,
|
||||
): Promise<void> {
|
||||
this.exceptionHandlerService.captureExceptions([exception], {
|
||||
@@ -223,7 +215,7 @@ export class MessageImportExceptionHandlerService {
|
||||
}
|
||||
|
||||
private async handlePermanentException(
|
||||
messageChannel: Pick<MessageChannelWorkspaceEntity, 'id'>,
|
||||
messageChannel: Pick<MessageChannelEntity, 'id'>,
|
||||
workspaceId: string,
|
||||
): Promise<void> {
|
||||
await this.messageChannelSyncStatusService.markAsFailed(
|
||||
@@ -235,7 +227,7 @@ export class MessageImportExceptionHandlerService {
|
||||
|
||||
private async handleNotFoundException(
|
||||
syncStep: MessageImportSyncStep,
|
||||
messageChannel: Pick<MessageChannelWorkspaceEntity, 'id'>,
|
||||
messageChannel: Pick<MessageChannelEntity, 'id'>,
|
||||
workspaceId: string,
|
||||
): Promise<void> {
|
||||
if (syncStep === MessageImportSyncStep.MESSAGE_LIST_FETCH) {
|
||||
|
||||
+17
-15
@@ -1,24 +1,23 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import chunk from 'lodash.chunk';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { In, MoreThanOrEqual } from 'typeorm';
|
||||
import { In, MoreThanOrEqual, Repository } from 'typeorm';
|
||||
|
||||
import {
|
||||
MessageChannelPendingGroupEmailsAction,
|
||||
MessageChannelSyncStage,
|
||||
MessageFolderPendingSyncAction,
|
||||
} from 'twenty-shared/types';
|
||||
import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator';
|
||||
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
|
||||
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
|
||||
import { type MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity';
|
||||
import {
|
||||
MessageChannelPendingGroupEmailsAction,
|
||||
MessageChannelSyncStage,
|
||||
MessageChannelWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { MessageFolderPendingSyncAction } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { MessagingMessageCleanerService } from 'src/modules/messaging/message-cleaner/services/messaging-message-cleaner.service';
|
||||
import { SyncMessageFoldersService } from 'src/modules/messaging/message-folder-manager/services/sync-message-folders.service';
|
||||
import { MessagingAccountAuthenticationService } from 'src/modules/messaging/message-import-manager/services/messaging-account-authentication.service';
|
||||
@@ -31,6 +30,7 @@ import {
|
||||
import { MessagingMessagesImportService } from 'src/modules/messaging/message-import-manager/services/messaging-messages-import.service';
|
||||
import { MessagingProcessFolderActionsService } from 'src/modules/messaging/message-import-manager/services/messaging-process-folder-actions.service';
|
||||
import { MessagingProcessGroupEmailActionsService } from 'src/modules/messaging/message-import-manager/services/messaging-process-group-email-actions.service';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
|
||||
const ONE_WEEK_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000;
|
||||
|
||||
@@ -42,7 +42,8 @@ export class MessagingMessageListFetchService {
|
||||
private readonly cacheStorage: CacheStorageService,
|
||||
private readonly messageChannelSyncStatusService: MessageChannelSyncStatusService,
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
@InjectRepository(MessageChannelEntity)
|
||||
private readonly messageChannelRepository: Repository<MessageChannelEntity>,
|
||||
private readonly messagingGetMessageListService: MessagingGetMessageListService,
|
||||
private readonly messageImportErrorHandlerService: MessageImportExceptionHandlerService,
|
||||
private readonly messagingMessageCleanerService: MessagingMessageCleanerService,
|
||||
@@ -55,7 +56,7 @@ export class MessagingMessageListFetchService {
|
||||
) {}
|
||||
|
||||
public async processMessageListFetch(
|
||||
messageChannel: MessageChannelWorkspaceEntity,
|
||||
messageChannel: MessageChannelEntity,
|
||||
workspaceId: string,
|
||||
) {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
@@ -82,11 +83,12 @@ export class MessagingMessageListFetchService {
|
||||
|
||||
const freshMessageChannel =
|
||||
pendingGroupEmailActionsProcessed || pendingFolderActionsProcessed
|
||||
? await this.messageChannelDataAccessService.findOne(workspaceId, {
|
||||
? await this.messageChannelRepository.findOne({
|
||||
where: {
|
||||
id: messageChannel.id,
|
||||
workspaceId,
|
||||
},
|
||||
relations: ['connectedAccount', 'messageFolders'],
|
||||
relations: { connectedAccount: true, messageFolders: true },
|
||||
})
|
||||
: messageChannel;
|
||||
|
||||
@@ -297,7 +299,7 @@ export class MessagingMessageListFetchService {
|
||||
}
|
||||
|
||||
private async processPendingGroupEmailActions(
|
||||
messageChannel: MessageChannelWorkspaceEntity,
|
||||
messageChannel: MessageChannelEntity,
|
||||
workspaceId: string,
|
||||
): Promise<boolean> {
|
||||
const hasPendingGroupEmailAction =
|
||||
@@ -323,7 +325,7 @@ export class MessagingMessageListFetchService {
|
||||
}
|
||||
|
||||
private async processPendingFolderActions(
|
||||
messageChannel: MessageChannelWorkspaceEntity,
|
||||
messageChannel: MessageChannelEntity,
|
||||
workspaceId: string,
|
||||
): Promise<boolean> {
|
||||
const foldersWithPendingActions = messageChannel.messageFolders.filter(
|
||||
@@ -350,7 +352,7 @@ export class MessagingMessageListFetchService {
|
||||
}
|
||||
|
||||
private async computeFullSyncMessageChannelMessageAssociationsToDelete(
|
||||
messageChannel: Pick<MessageChannelWorkspaceEntity, 'id'>,
|
||||
messageChannel: Pick<MessageChannelEntity, 'id'>,
|
||||
messageExternalIds: string[],
|
||||
workspaceId: string,
|
||||
) {
|
||||
|
||||
+48
-24
@@ -1,23 +1,23 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { MessageChannelSyncStage } from 'twenty-shared/types';
|
||||
import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator';
|
||||
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
|
||||
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { BlocklistRepository } from 'src/modules/blocklist/repositories/blocklist.repository';
|
||||
import { BlocklistWorkspaceEntity } from 'src/modules/blocklist/standard-objects/blocklist.workspace-entity';
|
||||
import { EmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/services/email-alias-manager.service';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
|
||||
import {
|
||||
MessageChannelSyncStage,
|
||||
type MessageChannelWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import {
|
||||
MessageImportDriverException,
|
||||
MessageImportDriverExceptionCode,
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
import { MessagingSaveMessagesAndEnqueueContactCreationService } from 'src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service';
|
||||
import { filterEmails } from 'src/modules/messaging/message-import-manager/utils/filter-emails.util';
|
||||
import { MessagingMonitoringService } from 'src/modules/messaging/monitoring/services/messaging-monitoring.service';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
|
||||
@Injectable()
|
||||
export class MessagingMessagesImportService {
|
||||
@@ -47,15 +48,18 @@ export class MessagingMessagesImportService {
|
||||
private readonly blocklistRepository: BlocklistRepository,
|
||||
private readonly emailAliasManagerService: EmailAliasManagerService,
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
@InjectRepository(MessageChannelEntity)
|
||||
private readonly messageChannelRepository: Repository<MessageChannelEntity>,
|
||||
private readonly messagingGetMessagesService: MessagingGetMessagesService,
|
||||
private readonly messageImportErrorHandlerService: MessageImportExceptionHandlerService,
|
||||
private readonly messagingAccountAuthenticationService: MessagingAccountAuthenticationService,
|
||||
@InjectRepository(UserWorkspaceEntity)
|
||||
private readonly userWorkspaceRepository: Repository<UserWorkspaceEntity>,
|
||||
) {}
|
||||
|
||||
async processMessageBatchImport(
|
||||
messageChannel: MessageChannelWorkspaceEntity,
|
||||
connectedAccount: ConnectedAccountWorkspaceEntity,
|
||||
messageChannel: MessageChannelEntity,
|
||||
connectedAccount: ConnectedAccountEntity,
|
||||
workspaceId: string,
|
||||
) {
|
||||
let messageIdsToFetch: string[] = [];
|
||||
@@ -98,10 +102,13 @@ export class MessagingMessagesImportService {
|
||||
refreshToken,
|
||||
};
|
||||
|
||||
await this.emailAliasManagerService.refreshHandleAliases(
|
||||
connectedAccountWithFreshTokens,
|
||||
workspaceId,
|
||||
);
|
||||
const refreshedHandleAliases =
|
||||
await this.emailAliasManagerService.refreshHandleAliases(
|
||||
connectedAccountWithFreshTokens,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
connectedAccountWithFreshTokens.handleAliases = refreshedHandleAliases;
|
||||
|
||||
messageIdsToFetch = await this.cacheStorage.setPop(
|
||||
`messages-to-import:${workspaceId}:${messageChannel.id}`,
|
||||
@@ -145,10 +152,30 @@ export class MessagingMessagesImportService {
|
||||
.filter(isDefined);
|
||||
}
|
||||
|
||||
const blocklist = await this.blocklistRepository.getByWorkspaceMemberId(
|
||||
connectedAccountWithFreshTokens.accountOwnerId,
|
||||
workspaceId,
|
||||
);
|
||||
const userWorkspace = await this.userWorkspaceRepository.findOne({
|
||||
where: {
|
||||
id: connectedAccountWithFreshTokens.userWorkspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
const workspaceMemberRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<WorkspaceMemberWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'workspaceMember',
|
||||
);
|
||||
|
||||
const workspaceMember = userWorkspace
|
||||
? await workspaceMemberRepository.findOne({
|
||||
where: { userId: userWorkspace.userId },
|
||||
})
|
||||
: null;
|
||||
|
||||
const blocklist = workspaceMember
|
||||
? await this.blocklistRepository.getByWorkspaceMemberId(
|
||||
workspaceMember.id,
|
||||
workspaceId,
|
||||
)
|
||||
: [];
|
||||
|
||||
if (!isDefined(messageChannel.handle)) {
|
||||
throw new MessageImportDriverException(
|
||||
@@ -166,7 +193,7 @@ export class MessagingMessagesImportService {
|
||||
|
||||
const messagesToSave = filterEmails(
|
||||
messageChannel.handle,
|
||||
[...connectedAccountWithFreshTokens.handleAliases.split(',')],
|
||||
[...connectedAccountWithFreshTokens.handleAliases],
|
||||
allMessages,
|
||||
blocklist
|
||||
.map((blocklistItem) => blocklistItem.handle)
|
||||
@@ -198,11 +225,8 @@ export class MessagingMessagesImportService {
|
||||
);
|
||||
}
|
||||
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{
|
||||
id: messageChannel.id,
|
||||
},
|
||||
await this.messageChannelRepository.update(
|
||||
{ id: messageChannel.id, workspaceId },
|
||||
{
|
||||
throttleFailureCount: 0,
|
||||
throttleRetryAfter: null,
|
||||
@@ -239,7 +263,7 @@ export class MessagingMessagesImportService {
|
||||
}
|
||||
|
||||
private async trackMessageImportCompleted(
|
||||
messageChannel: MessageChannelWorkspaceEntity,
|
||||
messageChannel: MessageChannelEntity,
|
||||
workspaceId: string,
|
||||
) {
|
||||
await this.messagingMonitoringService.track({
|
||||
|
||||
+13
-14
@@ -1,16 +1,14 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { In } from 'typeorm';
|
||||
import { In, Repository } from 'typeorm';
|
||||
|
||||
import { MessageFolderDataAccessService } from 'src/engine/metadata-modules/message-folder/data-access/services/message-folder-data-access.service';
|
||||
import { type MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import {
|
||||
MessageFolderPendingSyncAction,
|
||||
MessageFolderWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import { MessageFolderPendingSyncAction } from 'twenty-shared/types';
|
||||
import { MessagingDeleteFolderMessagesService } from 'src/modules/messaging/message-import-manager/services/messaging-delete-folder-messages.service';
|
||||
|
||||
@Injectable()
|
||||
@@ -21,13 +19,14 @@ export class MessagingProcessFolderActionsService {
|
||||
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageFolderDataAccessService: MessageFolderDataAccessService,
|
||||
@InjectRepository(MessageFolderEntity)
|
||||
private readonly messageFolderRepository: Repository<MessageFolderEntity>,
|
||||
private readonly messagingDeleteFolderMessagesService: MessagingDeleteFolderMessagesService,
|
||||
) {}
|
||||
|
||||
async processFolderActions(
|
||||
messageChannel: MessageChannelWorkspaceEntity,
|
||||
messageFolders: MessageFolderWorkspaceEntity[],
|
||||
messageChannel: MessageChannelEntity,
|
||||
messageFolders: MessageFolderEntity[],
|
||||
workspaceId: string,
|
||||
): Promise<void> {
|
||||
const foldersWithPendingActions = messageFolders.filter(
|
||||
@@ -93,9 +92,8 @@ export class MessagingProcessFolderActionsService {
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
async () => {
|
||||
if (processedFolderIds.length > 0) {
|
||||
await this.messageFolderDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: In(processedFolderIds) },
|
||||
await this.messageFolderRepository.update(
|
||||
{ id: In(processedFolderIds), workspaceId },
|
||||
{ pendingSyncAction: MessageFolderPendingSyncAction.NONE },
|
||||
);
|
||||
|
||||
@@ -105,8 +103,9 @@ export class MessagingProcessFolderActionsService {
|
||||
}
|
||||
|
||||
if (folderIdsToDelete.length > 0) {
|
||||
await this.messageFolderDataAccessService.delete(workspaceId, {
|
||||
await this.messageFolderRepository.delete({
|
||||
id: In(folderIdsToDelete),
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
this.logger.log(
|
||||
|
||||
+58
-96
@@ -1,17 +1,15 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { MessageFolderDataAccessService } from 'src/engine/metadata-modules/message-folder/data-access/services/message-folder-data-access.service';
|
||||
import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||
import { MessageChannelPendingGroupEmailsAction } from 'twenty-shared/types';
|
||||
import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import {
|
||||
MessageChannelPendingGroupEmailsAction,
|
||||
MessageChannelWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { MessagingDeleteGroupEmailMessagesService } from 'src/modules/messaging/message-import-manager/services/messaging-delete-group-email-messages.service';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
|
||||
@Injectable()
|
||||
export class MessagingProcessGroupEmailActionsService {
|
||||
@@ -21,25 +19,22 @@ export class MessagingProcessGroupEmailActionsService {
|
||||
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
private readonly messageFolderDataAccessService: MessageFolderDataAccessService,
|
||||
@InjectRepository(MessageChannelEntity)
|
||||
private readonly messageChannelRepository: Repository<MessageChannelEntity>,
|
||||
@InjectRepository(MessageFolderEntity)
|
||||
private readonly messageFolderRepository: Repository<MessageFolderEntity>,
|
||||
private readonly messagingDeleteGroupEmailMessagesService: MessagingDeleteGroupEmailMessagesService,
|
||||
) {}
|
||||
|
||||
async markMessageChannelAsPendingGroupEmailsAction(
|
||||
messageChannel: MessageChannelWorkspaceEntity,
|
||||
messageChannel: MessageChannelEntity,
|
||||
workspaceId: string,
|
||||
pendingGroupEmailsAction: MessageChannelPendingGroupEmailsAction,
|
||||
): Promise<void> {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: messageChannel.id },
|
||||
{ pendingGroupEmailsAction },
|
||||
);
|
||||
}, authContext);
|
||||
await this.messageChannelRepository.update(
|
||||
{ id: messageChannel.id, workspaceId },
|
||||
{ pendingGroupEmailsAction },
|
||||
);
|
||||
|
||||
this.logger.debug(
|
||||
`WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id} - Marked message channel as pending group emails action: ${pendingGroupEmailsAction}`,
|
||||
@@ -47,7 +42,7 @@ export class MessagingProcessGroupEmailActionsService {
|
||||
}
|
||||
|
||||
async processGroupEmailActions(
|
||||
messageChannel: MessageChannelWorkspaceEntity,
|
||||
messageChannel: MessageChannelEntity,
|
||||
workspaceId: string,
|
||||
): Promise<void> {
|
||||
const { pendingGroupEmailsAction } = messageChannel;
|
||||
@@ -65,70 +60,56 @@ export class MessagingProcessGroupEmailActionsService {
|
||||
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const workspaceDataSource =
|
||||
await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSource();
|
||||
|
||||
await workspaceDataSource?.transaction(
|
||||
async (transactionManager: WorkspaceEntityManager) => {
|
||||
try {
|
||||
switch (pendingGroupEmailsAction) {
|
||||
case MessageChannelPendingGroupEmailsAction.GROUP_EMAILS_DELETION:
|
||||
await this.handleGroupEmailsDeletion(
|
||||
workspaceId,
|
||||
messageChannel.id,
|
||||
transactionManager,
|
||||
);
|
||||
break;
|
||||
case MessageChannelPendingGroupEmailsAction.GROUP_EMAILS_IMPORT:
|
||||
await this.handleGroupEmailsImport(
|
||||
workspaceId,
|
||||
messageChannel.id,
|
||||
transactionManager,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: messageChannel.id },
|
||||
{
|
||||
pendingGroupEmailsAction:
|
||||
MessageChannelPendingGroupEmailsAction.NONE,
|
||||
},
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
this.logger.debug(
|
||||
`WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id} - Reset pendingGroupEmailsAction to NONE`,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id} - Error processing group email action: ${error.message}`,
|
||||
error.stack,
|
||||
);
|
||||
throw error;
|
||||
try {
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
async () => {
|
||||
switch (pendingGroupEmailsAction) {
|
||||
case MessageChannelPendingGroupEmailsAction.GROUP_EMAILS_DELETION:
|
||||
await this.handleGroupEmailsDeletion(
|
||||
workspaceId,
|
||||
messageChannel.id,
|
||||
);
|
||||
break;
|
||||
case MessageChannelPendingGroupEmailsAction.GROUP_EMAILS_IMPORT:
|
||||
await this.handleGroupEmailsImport(
|
||||
workspaceId,
|
||||
messageChannel.id,
|
||||
);
|
||||
break;
|
||||
}
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
}, authContext);
|
||||
|
||||
await this.messageChannelRepository.update(
|
||||
{ id: messageChannel.id, workspaceId },
|
||||
{
|
||||
pendingGroupEmailsAction: MessageChannelPendingGroupEmailsAction.NONE,
|
||||
},
|
||||
);
|
||||
|
||||
this.logger.debug(
|
||||
`WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id} - Reset pendingGroupEmailsAction to NONE`,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannel.id} - Error processing group email action: ${error.message}`,
|
||||
(error as Error).stack,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async handleGroupEmailsDeletion(
|
||||
workspaceId: string,
|
||||
messageChannelId: string,
|
||||
transactionManager: WorkspaceEntityManager,
|
||||
): Promise<void> {
|
||||
await this.messagingDeleteGroupEmailMessagesService.deleteGroupEmailMessages(
|
||||
workspaceId,
|
||||
messageChannelId,
|
||||
);
|
||||
|
||||
await this.resetCursors({
|
||||
workspaceId,
|
||||
messageChannelId,
|
||||
transactionManager,
|
||||
});
|
||||
await this.resetCursors(workspaceId, messageChannelId);
|
||||
|
||||
this.logger.debug(
|
||||
`WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannelId} - Completed GROUP_EMAILS_DELETION action`,
|
||||
@@ -138,42 +119,23 @@ export class MessagingProcessGroupEmailActionsService {
|
||||
private async handleGroupEmailsImport(
|
||||
workspaceId: string,
|
||||
messageChannelId: string,
|
||||
transactionManager: WorkspaceEntityManager,
|
||||
): Promise<void> {
|
||||
await this.resetCursors({
|
||||
workspaceId,
|
||||
messageChannelId,
|
||||
transactionManager,
|
||||
});
|
||||
await this.resetCursors(workspaceId, messageChannelId);
|
||||
|
||||
this.logger.debug(
|
||||
`WorkspaceId: ${workspaceId}, MessageChannelId: ${messageChannelId} - Completed GROUP_EMAILS_IMPORT action`,
|
||||
);
|
||||
}
|
||||
|
||||
private async resetCursors({
|
||||
workspaceId,
|
||||
messageChannelId,
|
||||
transactionManager,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
messageChannelId: string;
|
||||
transactionManager: WorkspaceEntityManager;
|
||||
}) {
|
||||
await this.messageChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: messageChannelId },
|
||||
{
|
||||
syncCursor: '',
|
||||
},
|
||||
transactionManager,
|
||||
private async resetCursors(workspaceId: string, messageChannelId: string) {
|
||||
await this.messageChannelRepository.update(
|
||||
{ id: messageChannelId, workspaceId },
|
||||
{ syncCursor: '' },
|
||||
);
|
||||
|
||||
await this.messageFolderDataAccessService.update(
|
||||
workspaceId,
|
||||
{ messageChannelId },
|
||||
await this.messageFolderRepository.update(
|
||||
{ messageChannelId, workspaceId },
|
||||
{ syncCursor: '' },
|
||||
transactionManager,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+12
-11
@@ -1,20 +1,21 @@
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { FieldActorSource, MessageParticipantRole } from 'twenty-shared/types';
|
||||
import {
|
||||
FieldActorSource,
|
||||
MessageChannelContactAutoCreationPolicy,
|
||||
MessageParticipantRole,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import { type MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
|
||||
import { getQueueToken } from 'src/engine/core-modules/message-queue/utils/get-queue-token.util';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { CreateCompanyAndContactJob } from 'src/modules/contact-creation-manager/jobs/create-company-and-contact.job';
|
||||
import { MessageDirection } from 'src/modules/messaging/common/enums/message-direction.enum';
|
||||
import {
|
||||
MessageChannelContactAutoCreationPolicy,
|
||||
type MessageChannelWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { MessagingMessageFolderAssociationService } from 'src/modules/messaging/message-import-manager/services/messaging-message-folder-association.service';
|
||||
import { MessagingMessageService } from 'src/modules/messaging/message-import-manager/services/messaging-message.service';
|
||||
import { MessagingSaveMessagesAndEnqueueContactCreationService } from 'src/modules/messaging/message-import-manager/services/messaging-save-messages-and-enqueue-contact-creation.service';
|
||||
@@ -31,20 +32,20 @@ describe('MessagingSaveMessagesAndEnqueueContactCreationService', () => {
|
||||
|
||||
const workspaceId = 'workspace-id';
|
||||
|
||||
const mockConnectedAccount: ConnectedAccountWorkspaceEntity = {
|
||||
const mockConnectedAccount: ConnectedAccountEntity = {
|
||||
id: 'connected-account-id',
|
||||
handle: 'test@example.com',
|
||||
handleAliases: 'alias1@example.com,alias2@example.com',
|
||||
} as ConnectedAccountWorkspaceEntity;
|
||||
handleAliases: ['alias1@example.com', 'alias2@example.com'],
|
||||
} as ConnectedAccountEntity;
|
||||
|
||||
const mockMessageChannel: MessageChannelWorkspaceEntity = {
|
||||
const mockMessageChannel: MessageChannelEntity = {
|
||||
id: 'message-channel-id',
|
||||
isContactAutoCreationEnabled: true,
|
||||
contactAutoCreationPolicy:
|
||||
MessageChannelContactAutoCreationPolicy.SENT_AND_RECEIVED,
|
||||
excludeNonProfessionalEmails: true,
|
||||
excludeGroupEmails: true,
|
||||
} as MessageChannelWorkspaceEntity;
|
||||
} as MessageChannelEntity;
|
||||
|
||||
const mockMessages: MessageWithParticipants[] = [
|
||||
{
|
||||
|
||||
+10
-9
@@ -1,23 +1,24 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { FieldActorSource, MessageParticipantRole } from 'twenty-shared/types';
|
||||
import {
|
||||
FieldActorSource,
|
||||
MessageChannelContactAutoCreationPolicy,
|
||||
MessageParticipantRole,
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
|
||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
|
||||
import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import {
|
||||
CreateCompanyAndContactJob,
|
||||
type CreateCompanyAndContactJobData,
|
||||
} from 'src/modules/contact-creation-manager/jobs/create-company-and-contact.job';
|
||||
import {
|
||||
MessageChannelContactAutoCreationPolicy,
|
||||
type MessageChannelWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import {
|
||||
type Participant,
|
||||
type ParticipantWithMessageId,
|
||||
@@ -44,11 +45,11 @@ export class MessagingSaveMessagesAndEnqueueContactCreationService {
|
||||
|
||||
async saveMessagesAndEnqueueContactCreation(
|
||||
messagesToSave: MessageWithParticipants[],
|
||||
messageChannel: MessageChannelWorkspaceEntity,
|
||||
connectedAccount: ConnectedAccountWorkspaceEntity,
|
||||
messageChannel: MessageChannelEntity,
|
||||
connectedAccount: ConnectedAccountEntity,
|
||||
workspaceId: string,
|
||||
) {
|
||||
const handleAliases = connectedAccount.handleAliases?.split(',') || [];
|
||||
const handleAliases = connectedAccount.handleAliases || [];
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
const participantsWithMessageId =
|
||||
|
||||
+4
-4
@@ -1,15 +1,15 @@
|
||||
import { type MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { type MessageFolder } from 'src/modules/messaging/message-folder-manager/interfaces/message-folder-driver.interface';
|
||||
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
|
||||
export type GetMessageListsArgs = {
|
||||
messageChannel: Pick<
|
||||
MessageChannelWorkspaceEntity,
|
||||
MessageChannelEntity,
|
||||
'syncCursor' | 'id' | 'messageFolderImportPolicy'
|
||||
>;
|
||||
connectedAccount: Pick<
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
ConnectedAccountEntity,
|
||||
| 'provider'
|
||||
| 'accessToken'
|
||||
| 'refreshToken'
|
||||
|
||||
+4
-4
@@ -7,7 +7,7 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
import { type MessageOutboundDriver } from 'src/modules/messaging/message-outbound-manager/interfaces/message-outbound-driver.interface';
|
||||
|
||||
import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { mimeEncode } from 'src/modules/messaging/message-import-manager/utils/mime-encode.util';
|
||||
import { type SendMessageInput } from 'src/modules/messaging/message-outbound-manager/types/send-message-input.type';
|
||||
import { toMailComposerOptions } from 'src/modules/messaging/message-outbound-manager/utils/to-mail-composer-options.util';
|
||||
@@ -20,7 +20,7 @@ export class GmailMessageOutboundService implements MessageOutboundDriver {
|
||||
|
||||
async sendMessage(
|
||||
sendMessageInput: SendMessageInput,
|
||||
connectedAccount: ConnectedAccountWorkspaceEntity,
|
||||
connectedAccount: ConnectedAccountEntity,
|
||||
): Promise<void> {
|
||||
const { gmailClient, encodedMessage } = await this.composeGmailMessage(
|
||||
connectedAccount,
|
||||
@@ -37,7 +37,7 @@ export class GmailMessageOutboundService implements MessageOutboundDriver {
|
||||
|
||||
async createDraft(
|
||||
sendMessageInput: SendMessageInput,
|
||||
connectedAccount: ConnectedAccountWorkspaceEntity,
|
||||
connectedAccount: ConnectedAccountEntity,
|
||||
): Promise<void> {
|
||||
const { gmailClient, encodedMessage } = await this.composeGmailMessage(
|
||||
connectedAccount,
|
||||
@@ -55,7 +55,7 @@ export class GmailMessageOutboundService implements MessageOutboundDriver {
|
||||
}
|
||||
|
||||
private async composeGmailMessage(
|
||||
connectedAccount: ConnectedAccountWorkspaceEntity,
|
||||
connectedAccount: ConnectedAccountEntity,
|
||||
sendMessageInput: SendMessageInput,
|
||||
): Promise<{
|
||||
gmailClient: gmail_v1.Gmail;
|
||||
|
||||
+28
-10
@@ -1,11 +1,15 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import MailComposer from 'nodemailer/lib/mail-composer';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { type MessageOutboundDriver } from 'src/modules/messaging/message-outbound-manager/interfaces/message-outbound-driver.interface';
|
||||
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity';
|
||||
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { ImapClientProvider } from 'src/modules/messaging/message-import-manager/drivers/imap/providers/imap-client.provider';
|
||||
import { ImapFindDraftsFolderService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-find-drafts-folder.service';
|
||||
import { SmtpClientProvider } from 'src/modules/messaging/message-import-manager/drivers/smtp/providers/smtp-client.provider';
|
||||
@@ -18,13 +22,17 @@ export class ImapSmtpMessageOutboundService implements MessageOutboundDriver {
|
||||
private readonly smtpClientProvider: SmtpClientProvider,
|
||||
private readonly imapClientProvider: ImapClientProvider,
|
||||
private readonly imapFindDraftsFolderService: ImapFindDraftsFolderService,
|
||||
@InjectRepository(MessageChannelEntity)
|
||||
private readonly messageChannelRepository: Repository<MessageChannelEntity>,
|
||||
@InjectRepository(MessageFolderEntity)
|
||||
private readonly messageFolderRepository: Repository<MessageFolderEntity>,
|
||||
) {}
|
||||
|
||||
async sendMessage(
|
||||
sendMessageInput: SendMessageInput,
|
||||
connectedAccount: ConnectedAccountWorkspaceEntity,
|
||||
connectedAccount: ConnectedAccountEntity,
|
||||
): Promise<void> {
|
||||
const { handle, connectionParameters, messageChannels } = connectedAccount;
|
||||
const { handle, connectionParameters } = connectedAccount;
|
||||
|
||||
const smtpClient =
|
||||
await this.smtpClientProvider.getSmtpClient(connectedAccount);
|
||||
@@ -48,13 +56,23 @@ export class ImapSmtpMessageOutboundService implements MessageOutboundDriver {
|
||||
const imapClient =
|
||||
await this.imapClientProvider.getClient(connectedAccount);
|
||||
|
||||
const messageChannel = messageChannels.find(
|
||||
(channel) => channel.handle === handle,
|
||||
);
|
||||
const messageChannel = await this.messageChannelRepository.findOne({
|
||||
where: {
|
||||
connectedAccountId: connectedAccount.id,
|
||||
handle: handle,
|
||||
},
|
||||
});
|
||||
|
||||
const sentFolder = messageChannel?.messageFolders.find(
|
||||
(messageFolder) => messageFolder.isSentFolder,
|
||||
);
|
||||
let sentFolder: MessageFolderEntity | null = null;
|
||||
|
||||
if (isDefined(messageChannel)) {
|
||||
sentFolder = await this.messageFolderRepository.findOne({
|
||||
where: {
|
||||
messageChannelId: messageChannel.id,
|
||||
isSentFolder: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (isDefined(sentFolder) && isDefined(sentFolder.name)) {
|
||||
await imapClient.append(sentFolder.name, messageBuffer);
|
||||
@@ -66,7 +84,7 @@ export class ImapSmtpMessageOutboundService implements MessageOutboundDriver {
|
||||
|
||||
async createDraft(
|
||||
sendMessageInput: SendMessageInput,
|
||||
connectedAccount: ConnectedAccountWorkspaceEntity,
|
||||
connectedAccount: ConnectedAccountEntity,
|
||||
): Promise<void> {
|
||||
const { handle, connectionParameters } = connectedAccount;
|
||||
|
||||
|
||||
+3
-3
@@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common';
|
||||
import { type MessageOutboundDriver } from 'src/modules/messaging/message-outbound-manager/interfaces/message-outbound-driver.interface';
|
||||
|
||||
import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { toMicrosoftRecipients } from 'src/modules/messaging/message-import-manager/utils/to-microsoft-recipients.util';
|
||||
import { type SendMessageInput } from 'src/modules/messaging/message-outbound-manager/types/send-message-input.type';
|
||||
import { type Client as MicrosoftGraphClient } from '@microsoft/microsoft-graph-client';
|
||||
@@ -17,7 +17,7 @@ export class MicrosoftMessageOutboundService implements MessageOutboundDriver {
|
||||
|
||||
async sendMessage(
|
||||
sendMessageInput: SendMessageInput,
|
||||
connectedAccount: ConnectedAccountWorkspaceEntity,
|
||||
connectedAccount: ConnectedAccountEntity,
|
||||
): Promise<void> {
|
||||
const microsoftClient =
|
||||
await this.oAuth2ClientManagerService.getMicrosoftOAuth2Client(
|
||||
@@ -34,7 +34,7 @@ export class MicrosoftMessageOutboundService implements MessageOutboundDriver {
|
||||
|
||||
async createDraft(
|
||||
sendMessageInput: SendMessageInput,
|
||||
connectedAccount: ConnectedAccountWorkspaceEntity,
|
||||
connectedAccount: ConnectedAccountEntity,
|
||||
): Promise<void> {
|
||||
const microsoftClient =
|
||||
await this.oAuth2ClientManagerService.getMicrosoftOAuth2Client(
|
||||
|
||||
+3
-3
@@ -1,14 +1,14 @@
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { type SendMessageInput } from 'src/modules/messaging/message-outbound-manager/types/send-message-input.type';
|
||||
|
||||
export type MessageOutboundDriver = {
|
||||
sendMessage(
|
||||
sendMessageInput: SendMessageInput,
|
||||
connectedAccount: ConnectedAccountWorkspaceEntity,
|
||||
connectedAccount: ConnectedAccountEntity,
|
||||
): Promise<void>;
|
||||
|
||||
createDraft(
|
||||
sendMessageInput: SendMessageInput,
|
||||
connectedAccount: ConnectedAccountWorkspaceEntity,
|
||||
connectedAccount: ConnectedAccountEntity,
|
||||
): Promise<void>;
|
||||
};
|
||||
|
||||
+4
@@ -1,5 +1,8 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity';
|
||||
import { OAuth2ClientManagerModule } from 'src/modules/connected-account/oauth2-client-manager/oauth2-client-manager.module';
|
||||
import { MessagingIMAPDriverModule } from 'src/modules/messaging/message-import-manager/drivers/imap/messaging-imap-driver.module';
|
||||
import { MessagingSmtpDriverModule } from 'src/modules/messaging/message-import-manager/drivers/smtp/messaging-smtp-driver.module';
|
||||
@@ -13,6 +16,7 @@ import { MessagingMessageOutboundService } from 'src/modules/messaging/message-o
|
||||
OAuth2ClientManagerModule,
|
||||
MessagingIMAPDriverModule,
|
||||
MessagingSmtpDriverModule,
|
||||
TypeOrmModule.forFeature([MessageChannelEntity, MessageFolderEntity]),
|
||||
],
|
||||
providers: [
|
||||
GmailMessageOutboundService,
|
||||
|
||||
+3
-3
@@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common';
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import { assertUnreachable } from 'twenty-shared/utils';
|
||||
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { type ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { GmailMessageOutboundService } from 'src/modules/messaging/message-outbound-manager/drivers/gmail/services/gmail-message-outbound.service';
|
||||
import { ImapSmtpMessageOutboundService } from 'src/modules/messaging/message-outbound-manager/drivers/imap/services/imap-smtp-message-outbound.service';
|
||||
import { MicrosoftMessageOutboundService } from 'src/modules/messaging/message-outbound-manager/drivers/microsoft/services/microsoft-message-outbound.service';
|
||||
@@ -19,7 +19,7 @@ export class MessagingMessageOutboundService {
|
||||
|
||||
public async sendMessage(
|
||||
sendMessageInput: SendMessageInput,
|
||||
connectedAccount: ConnectedAccountWorkspaceEntity,
|
||||
connectedAccount: ConnectedAccountEntity,
|
||||
): Promise<void> {
|
||||
switch (connectedAccount.provider) {
|
||||
case ConnectedAccountProvider.GOOGLE:
|
||||
@@ -52,7 +52,7 @@ export class MessagingMessageOutboundService {
|
||||
|
||||
public async createDraft(
|
||||
sendMessageInput: SendMessageInput,
|
||||
connectedAccount: ConnectedAccountWorkspaceEntity,
|
||||
connectedAccount: ConnectedAccountEntity,
|
||||
): Promise<void> {
|
||||
switch (connectedAccount.provider) {
|
||||
case ConnectedAccountProvider.GOOGLE:
|
||||
|
||||
+7
-9
@@ -10,7 +10,7 @@ import { Process } from 'src/engine/core-modules/message-queue/decorators/proces
|
||||
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
|
||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.service';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
|
||||
import { MessagingMonitoringService } from 'src/modules/messaging/monitoring/services/messaging-monitoring.service';
|
||||
@@ -25,7 +25,8 @@ export class MessagingMessageChannelSyncStatusMonitoringCronJob {
|
||||
private readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
private readonly messagingMonitoringService: MessagingMonitoringService,
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
@InjectRepository(MessageChannelEntity)
|
||||
private readonly messageChannelRepository: Repository<MessageChannelEntity>,
|
||||
private readonly exceptionHandlerService: ExceptionHandlerService,
|
||||
) {}
|
||||
|
||||
@@ -52,13 +53,10 @@ export class MessagingMessageChannelSyncStatusMonitoringCronJob {
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
async () => {
|
||||
const messageChannels =
|
||||
await this.messageChannelDataAccessService.findMany(
|
||||
activeWorkspace.id,
|
||||
{
|
||||
select: ['id', 'syncStatus', 'connectedAccountId'],
|
||||
},
|
||||
);
|
||||
const messageChannels = await this.messageChannelRepository.find({
|
||||
select: ['id', 'syncStatus', 'connectedAccountId'],
|
||||
where: { workspaceId: activeWorkspace.id },
|
||||
});
|
||||
|
||||
for (const messageChannel of messageChannels) {
|
||||
if (!messageChannel.syncStatus) {
|
||||
|
||||
+6
-4
@@ -4,7 +4,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { AuditModule } from 'src/engine/core-modules/audit/audit.module';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
||||
import { MessageChannelDataAccessModule } from 'src/engine/metadata-modules/message-channel/data-access/message-channel-data-access.module';
|
||||
import { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
|
||||
import { MessagingCommonModule } from 'src/modules/messaging/common/messaging-common.module';
|
||||
import { MessagingMessageChannelSyncStatusMonitoringCronCommand } from 'src/modules/messaging/monitoring/crons/commands/messaging-message-channel-sync-status-monitoring.cron.command';
|
||||
import { MessagingMessageChannelSyncStatusMonitoringCronJob } from 'src/modules/messaging/monitoring/crons/jobs/messaging-message-channel-sync-status-monitoring.cron.job';
|
||||
@@ -13,10 +13,12 @@ import { MessagingMonitoringService } from 'src/modules/messaging/monitoring/ser
|
||||
@Module({
|
||||
imports: [
|
||||
AuditModule,
|
||||
MessageChannelDataAccessModule,
|
||||
MessagingCommonModule,
|
||||
TypeOrmModule.forFeature([WorkspaceEntity]),
|
||||
TypeOrmModule.forFeature([DataSourceEntity]),
|
||||
TypeOrmModule.forFeature([
|
||||
WorkspaceEntity,
|
||||
DataSourceEntity,
|
||||
MessageChannelEntity,
|
||||
]),
|
||||
],
|
||||
providers: [
|
||||
MessagingMessageChannelSyncStatusMonitoringCronCommand,
|
||||
|
||||
Reference in New Issue
Block a user