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:
neo773
2026-04-06 13:43:41 +05:30
committed by GitHub
parent 3a1e112b86
commit d3f0162cf5
246 changed files with 5216 additions and 6424 deletions
@@ -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],
@@ -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
@@ -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;
}
}
}
@@ -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,
);
}
}
@@ -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 {}
@@ -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) {
@@ -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;
@@ -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;