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
@@ -58,10 +58,9 @@ import { UserModule } from 'src/engine/core-modules/user/user.module';
import { WorkspaceInvitationModule } from 'src/engine/core-modules/workspace-invitation/workspace-invitation.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { CoreEntityCacheModule } from 'src/engine/core-entity-cache/core-entity-cache.module';
import { CalendarChannelDataAccessModule } from 'src/engine/metadata-modules/calendar-channel/data-access/calendar-channel-data-access.module';
import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity';
import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
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 { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
@@ -94,15 +93,14 @@ import { JwtAuthStrategy } from './strategies/jwt.auth.strategy';
TwoFactorAuthenticationMethodEntity,
ObjectMetadataEntity,
ConnectedAccountEntity,
MessageChannelEntity,
CalendarChannelEntity,
]),
UserWorkspaceModule,
OnboardingModule,
ConnectedAccountModule,
MessagingCommonModule,
MessagingFolderSyncManagerModule,
CalendarChannelDataAccessModule,
ConnectedAccountDataAccessModule,
MessageChannelDataAccessModule,
WorkspaceSSOModule,
FeatureFlagModule,
WorkspaceInvitationModule,
@@ -101,8 +101,9 @@ export class GoogleAPIsAuthController {
const connectedAccountId =
await this.googleAPIsService.refreshGoogleRefreshToken({
handle,
workspaceMemberId: workspaceMemberId,
workspaceId: workspaceId,
userId,
workspaceMemberId,
workspaceId,
accessToken,
refreshToken,
calendarVisibility,
@@ -108,7 +108,8 @@ export class MicrosoftAPIsAuthController {
const connectedAccountId =
await this.microsoftAPIsService.refreshMicrosoftRefreshToken({
handle,
workspaceMemberId: workspaceMemberId,
userId,
workspaceMemberId,
workspaceId: workspaceId,
accessToken,
refreshToken,
@@ -9,11 +9,7 @@ import { addMilliseconds } from 'date-fns';
import ms from 'ms';
import { PasswordUpdateNotifyEmail } from 'twenty-emails';
import { PermissionFlagType } from 'twenty-shared/constants';
import {
AppPath,
ConnectedAccountProvider,
FeatureFlagKey,
} from 'twenty-shared/types';
import { AppPath, ConnectedAccountProvider } from 'twenty-shared/types';
import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils';
import { IsNull, Repository } from 'typeorm';
@@ -1081,19 +1077,6 @@ export class AuthService {
oidcTokenClaims?: Record<string, unknown>;
connectedAccountProvider?: ConnectedAccountProvider;
}): Promise<void> {
const isConnectedAccountMigrated =
await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED,
input.workspaceId,
);
// const willBeEnabledByDefault = DEFAULT_FEATURE_FLAGS.includes(FeatureFlagKey.IS_CONNECTED_ACCOUNT_MIGRATED);
const willBeEnabledByDefault = false;
if (!isConnectedAccountMigrated && !willBeEnabledByDefault) {
return;
}
const provider =
input.connectedAccountProvider ??
this.mapAuthProviderToConnectedAccountProvider(input.authProvider);
@@ -1,31 +1,30 @@
import { Injectable } from '@nestjs/common';
import { v4 } from 'uuid';
import { EntityManager } from 'typeorm';
import { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.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 {
CalendarChannelSyncStage,
CalendarChannelSyncStatus,
CalendarChannelVisibility,
} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
} from 'twenty-shared/types';
import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-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 CreateCalendarChannelInput = {
workspaceId: string;
connectedAccountId: string;
handle: string;
calendarVisibility?: CalendarChannelVisibility;
manager: WorkspaceEntityManager;
skipMessageChannelConfiguration?: boolean;
transactionManager: EntityManager;
};
@Injectable()
export class CreateCalendarChannelService {
constructor(
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService,
) {}
async createCalendarChannel(
@@ -36,8 +35,8 @@ export class CreateCalendarChannelService {
connectedAccountId,
handle,
calendarVisibility,
manager,
skipMessageChannelConfiguration,
transactionManager,
} = input;
const authContext = buildSystemAuthContext(workspaceId);
@@ -46,23 +45,20 @@ export class CreateCalendarChannelService {
async () => {
const newCalendarChannelId = v4();
await this.calendarChannelDataAccessService.save(
await transactionManager.getRepository(CalendarChannelEntity).save({
id: newCalendarChannelId,
connectedAccountId,
handle,
visibility:
calendarVisibility || CalendarChannelVisibility.SHARE_EVERYTHING,
syncStatus: skipMessageChannelConfiguration
? CalendarChannelSyncStatus.ONGOING
: CalendarChannelSyncStatus.NOT_SYNCED,
syncStage: skipMessageChannelConfiguration
? CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING
: CalendarChannelSyncStage.PENDING_CONFIGURATION,
workspaceId,
{
id: newCalendarChannelId,
connectedAccountId,
handle,
visibility:
calendarVisibility || CalendarChannelVisibility.SHARE_EVERYTHING,
syncStatus: skipMessageChannelConfiguration
? CalendarChannelSyncStatus.ONGOING
: CalendarChannelSyncStatus.NOT_SYNCED,
syncStage: skipMessageChannelConfiguration
? CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_PENDING
: CalendarChannelSyncStage.PENDING_CONFIGURATION,
},
manager,
);
} as CalendarChannelEntity);
return newCalendarChannelId;
},
@@ -1,11 +1,14 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { type ConnectedAccountProvider } from 'twenty-shared/types';
import { EntityManager, Repository } from 'typeorm';
import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service';
import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
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 { 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 WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
export type CreateConnectedAccountInput = {
workspaceId: string;
@@ -16,14 +19,15 @@ export type CreateConnectedAccountInput = {
refreshToken: string;
accountOwnerId: string;
scopes: string[];
manager: WorkspaceEntityManager;
transactionManager: EntityManager;
};
@Injectable()
export class CreateConnectedAccountService {
constructor(
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService,
@InjectRepository(UserWorkspaceEntity)
private readonly userWorkspaceRepository: Repository<UserWorkspaceEntity>,
) {}
async createConnectedAccount(
@@ -38,25 +42,51 @@ export class CreateConnectedAccountService {
refreshToken,
accountOwnerId,
scopes,
manager,
} = input;
const authContext = buildSystemAuthContext(workspaceId);
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
await this.connectedAccountDataAccessService.save(
workspaceId,
{
const workspaceMemberRepo =
await this.globalWorkspaceOrmManager.getRepository<WorkspaceMemberWorkspaceEntity>(
workspaceId,
'workspaceMember',
);
const member = await workspaceMemberRepo.findOne({
where: { id: accountOwnerId },
});
if (!member) {
throw new Error(
`Workspace member not found for accountOwnerId ${accountOwnerId}`,
);
}
const userWorkspace = await this.userWorkspaceRepository.findOne({
where: { userId: member.userId, workspaceId },
});
if (!userWorkspace) {
throw new Error(
`User workspace not found for user ${member.userId} in workspace ${workspaceId}`,
);
}
const userWorkspaceId = userWorkspace.id;
await input.transactionManager
.getRepository(ConnectedAccountEntity)
.save({
id: connectedAccountId,
handle,
provider,
accessToken,
refreshToken,
accountOwnerId,
userWorkspaceId,
scopes,
},
manager,
);
workspaceId,
} as ConnectedAccountEntity);
}, authContext);
}
}
@@ -1,37 +1,32 @@
import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { EntityManager } from 'typeorm';
import { MessageChannelDataAccessService } from 'src/engine/metadata-modules/message-channel/data-access/services/message-channel-data-access.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 {
MessageChannelPendingGroupEmailsAction,
MessageChannelSyncStage,
MessageChannelSyncStatus,
MessageChannelType,
MessageChannelVisibility,
type MessageChannelWorkspaceEntity,
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
import { SyncMessageFoldersService } from 'src/modules/messaging/message-folder-manager/services/sync-message-folders.service';
} from 'twenty-shared/types';
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 { MessageChannelEntity } from 'src/engine/metadata-modules/message-channel/entities/message-channel.entity';
export type CreateMessageChannelInput = {
workspaceId: string;
connectedAccountId: string;
handle: string;
messageVisibility?: MessageChannelVisibility;
manager: WorkspaceEntityManager;
skipMessageChannelConfiguration?: boolean;
transactionManager: EntityManager;
};
@Injectable()
export class CreateMessageChannelService {
constructor(
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
private readonly syncMessageFoldersService: SyncMessageFoldersService,
) {}
async createMessageChannel(
@@ -42,50 +37,32 @@ export class CreateMessageChannelService {
connectedAccountId,
handle,
messageVisibility,
manager,
skipMessageChannelConfiguration,
transactionManager,
} = input;
const authContext = buildSystemAuthContext(workspaceId);
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
async () => {
const messageChannelRepo =
transactionManager.getRepository(MessageChannelEntity);
const newMessageChannelId = v4();
await this.messageChannelDataAccessService.save(
workspaceId,
{
id: newMessageChannelId,
connectedAccountId,
type: MessageChannelType.EMAIL,
handle,
visibility:
messageVisibility || MessageChannelVisibility.SHARE_EVERYTHING,
syncStatus: skipMessageChannelConfiguration
? MessageChannelSyncStatus.ONGOING
: MessageChannelSyncStatus.NOT_SYNCED,
syncStage: skipMessageChannelConfiguration
? MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING
: MessageChannelSyncStage.PENDING_CONFIGURATION,
pendingGroupEmailsAction:
MessageChannelPendingGroupEmailsAction.NONE,
},
manager,
);
const createdMessageChannel =
await this.messageChannelDataAccessService.findOne(workspaceId, {
where: { id: newMessageChannelId },
relations: ['connectedAccount', 'messageFolders'],
});
if (!isDefined(createdMessageChannel)) {
throw new Error('Message channel not found');
}
await this.syncMessageFoldersService.syncMessageFolders({
messageChannel:
createdMessageChannel as unknown as MessageChannelWorkspaceEntity,
await messageChannelRepo.save({
id: newMessageChannelId,
connectedAccountId,
type: MessageChannelType.EMAIL,
handle,
visibility:
messageVisibility || MessageChannelVisibility.SHARE_EVERYTHING,
syncStatus: skipMessageChannelConfiguration
? MessageChannelSyncStatus.ONGOING
: MessageChannelSyncStatus.NOT_SYNCED,
syncStage: skipMessageChannelConfiguration
? MessageChannelSyncStage.MESSAGE_LIST_FETCH_PENDING
: MessageChannelSyncStage.PENDING_CONFIGURATION,
pendingGroupEmailsAction: MessageChannelPendingGroupEmailsAction.NONE,
workspaceId,
});
@@ -1,7 +1,12 @@
import { Test, type TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { ConnectedAccountProvider } from 'twenty-shared/types';
import {
CalendarChannelSyncStage,
CalendarChannelVisibility,
ConnectedAccountProvider,
MessageChannelVisibility,
} from 'twenty-shared/types';
import { CreateCalendarChannelService } from 'src/engine/core-modules/auth/services/create-calendar-channel.service';
import { CreateConnectedAccountService } from 'src/engine/core-modules/auth/services/create-connected-account.service';
@@ -11,23 +16,19 @@ import { GoogleApisServiceAvailabilityService } from 'src/engine/core-modules/au
import { GoogleAPIsService } from 'src/engine/core-modules/auth/services/google-apis.service';
import { UpdateConnectedAccountOnReconnectService } from 'src/engine/core-modules/auth/services/update-connected-account-on-reconnect.service';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service';
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 { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
import { getQueueToken } from 'src/engine/core-modules/message-queue/utils/get-queue-token.util';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.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 { 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 { CalendarChannelSyncStatusService } from 'src/modules/calendar/common/services/calendar-channel-sync-status.service';
import {
CalendarChannelSyncStage,
CalendarChannelVisibility,
} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service';
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
import { SyncMessageFoldersService } from 'src/modules/messaging/message-folder-manager/services/sync-message-folders.service';
jest.mock('uuid', () => ({
v4: jest.fn(() => 'mocked-uuid'),
@@ -39,24 +40,36 @@ describe('GoogleAPIsService', () => {
let messagingChannelSyncStatusService: MessageChannelSyncStatusService;
let createMessageChannelService: CreateMessageChannelService;
const mockConnectedAccountDataAccessService = {
const mockConnectedAccountRepository = {
findOne: jest.fn(),
};
const mockMessageChannelDataAccessService = {
const mockTransactionManager = {
getRepository: jest.fn().mockReturnValue({ save: jest.fn() }),
};
const mockMessageChannelRepository = {
find: jest.fn(),
findOne: jest.fn().mockResolvedValue(null),
manager: {
transaction: jest.fn((callback) => callback(mockTransactionManager)),
},
};
const mockCalendarChannelRepository = {
find: jest.fn(),
};
const mockCalendarChannelDataAccessService = {
find: jest.fn(),
const mockUserWorkspaceRepository = {
findOne: jest.fn().mockResolvedValue({ id: 'user-workspace-id' }),
};
const mockWorkspaceMemberRepository = {
findOneOrFail: jest.fn(),
};
const mockWorkspaceDataSource = {
transaction: jest.fn((callback) => callback({})),
findOne: jest.fn().mockResolvedValue({
id: 'workspace-member-id',
userId: 'user-id',
}),
};
const mockTwentyConfigService = {
@@ -86,9 +99,6 @@ describe('GoogleAPIsService', () => {
return {};
}),
getGlobalWorkspaceDataSource: jest
.fn()
.mockResolvedValue(mockWorkspaceDataSource),
executeInWorkspaceContext: jest
.fn()
.mockImplementation((fn: () => any, _authContext?: any) => fn()),
@@ -180,16 +190,26 @@ describe('GoogleAPIsService', () => {
},
},
{
provide: ConnectedAccountDataAccessService,
useValue: mockConnectedAccountDataAccessService,
provide: SyncMessageFoldersService,
useValue: {
syncMessageFolders: jest.fn().mockResolvedValue([]),
},
},
{
provide: MessageChannelDataAccessService,
useValue: mockMessageChannelDataAccessService,
provide: getRepositoryToken(ConnectedAccountEntity),
useValue: mockConnectedAccountRepository,
},
{
provide: CalendarChannelDataAccessService,
useValue: mockCalendarChannelDataAccessService,
provide: getRepositoryToken(UserWorkspaceEntity),
useValue: mockUserWorkspaceRepository,
},
{
provide: getRepositoryToken(MessageChannelEntity),
useValue: mockMessageChannelRepository,
},
{
provide: getRepositoryToken(CalendarChannelEntity),
useValue: mockCalendarChannelRepository,
},
],
}).compile();
@@ -220,15 +240,15 @@ describe('GoogleAPIsService', () => {
const existingConnectedAccount = {
id: 'existing-account-id',
handle: 'test@example.com',
accountOwnerId: 'workspace-member-id',
userWorkspaceId: 'user-workspace-id',
provider: ConnectedAccountProvider.GOOGLE,
} as ConnectedAccountWorkspaceEntity;
} as ConnectedAccountEntity;
mockConnectedAccountDataAccessService.findOne.mockResolvedValue(
mockConnectedAccountRepository.findOne.mockResolvedValue(
existingConnectedAccount,
);
mockWorkspaceMemberRepository.findOneOrFail.mockResolvedValue({
mockWorkspaceMemberRepository.findOne.mockResolvedValue({
id: 'workspace-member-id',
userId: 'user-id',
});
@@ -240,14 +260,15 @@ describe('GoogleAPIsService', () => {
syncStage: CalendarChannelSyncStage.FAILED,
};
mockCalendarChannelDataAccessService.find.mockResolvedValue([
mockCalendarChannelRepository.find.mockResolvedValue([
failedCalendarChannel,
]);
mockMessageChannelDataAccessService.find.mockResolvedValue([]);
mockMessageChannelRepository.find.mockResolvedValue([]);
await service.refreshGoogleRefreshToken({
handle: 'test@example.com',
userId: 'user-id',
workspaceMemberId: 'workspace-member-id',
workspaceId: 'workspace-id',
accessToken: 'new-access-token',
@@ -1,7 +1,17 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { ConnectedAccountProvider, FeatureFlagKey } from 'twenty-shared/types';
import {
CalendarChannelSyncStage,
type CalendarChannelVisibility,
ConnectedAccountProvider,
FeatureFlagKey,
MessageChannelSyncStage,
MessageChannelSyncStatus,
type MessageChannelVisibility,
} from 'twenty-shared/types';
import { v4 } from 'uuid';
import { EntityManager, Repository } from 'typeorm';
import {
AuthException,
@@ -14,14 +24,15 @@ import { GoogleAPIScopesService } from 'src/engine/core-modules/auth/services/go
import { GoogleApisServiceAvailabilityService } from 'src/engine/core-modules/auth/services/google-apis-service-availability.service';
import { UpdateConnectedAccountOnReconnectService } from 'src/engine/core-modules/auth/services/update-connected-account-on-reconnect.service';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { SyncMessageFoldersService } from 'src/modules/messaging/message-folder-manager/services/sync-message-folders.service';
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 { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service';
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 { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.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 {
@@ -29,23 +40,14 @@ import {
type CalendarEventListFetchJobData,
} from 'src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job';
import { CalendarChannelSyncStatusService } from 'src/modules/calendar/common/services/calendar-channel-sync-status.service';
import {
CalendarChannelSyncStage,
type CalendarChannelVisibility,
} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service';
import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
import {
MessageChannelSyncStage,
MessageChannelSyncStatus,
type MessageChannelVisibility,
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
import {
MessagingMessageListFetchJob,
type MessagingMessageListFetchJobData,
} from 'src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
import { isDefined } from 'twenty-shared/utils';
@Injectable()
export class GoogleAPIsService {
@@ -66,13 +68,20 @@ export class GoogleAPIsService {
private readonly googleAPIScopesService: GoogleAPIScopesService,
private readonly googleApisServiceAvailabilityService: GoogleApisServiceAvailabilityService,
private readonly featureFlagService: FeatureFlagService,
private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService,
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService,
private readonly syncMessageFoldersService: SyncMessageFoldersService,
@InjectRepository(ConnectedAccountEntity)
private readonly connectedAccountRepository: Repository<ConnectedAccountEntity>,
@InjectRepository(UserWorkspaceEntity)
private readonly userWorkspaceRepository: Repository<UserWorkspaceEntity>,
@InjectRepository(MessageChannelEntity)
private readonly messageChannelRepository: Repository<MessageChannelEntity>,
@InjectRepository(CalendarChannelEntity)
private readonly calendarChannelRepository: Repository<CalendarChannelEntity>,
) {}
async refreshGoogleRefreshToken(input: {
handle: string;
userId: string;
workspaceMemberId: string;
workspaceId: string;
accessToken: string;
@@ -84,6 +93,7 @@ export class GoogleAPIsService {
const {
handle,
workspaceId,
userId,
workspaceMemberId,
calendarVisibility,
messageVisibility,
@@ -132,19 +142,48 @@ export class GoogleAPIsService {
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
async () => {
const connectedAccount =
await this.connectedAccountDataAccessService.findOne(workspaceId, {
where: { handle, accountOwnerId: workspaceMemberId },
});
const userWorkspace = await this.userWorkspaceRepository.findOne({
where: { userId, workspaceId },
});
if (!isDefined(userWorkspace)) {
throw new AuthException(
`User workspace not found for user ${userId} in workspace ${workspaceId}`,
AuthExceptionCode.INVALID_INPUT,
);
}
const userWorkspaceId = userWorkspace.id;
const connectedAccount = await this.connectedAccountRepository.findOne({
where: {
handle,
userWorkspaceId,
workspaceId,
},
});
const existingAccountId = connectedAccount?.id;
const newOrExistingConnectedAccountId = existingAccountId ?? v4();
const workspaceDataSource =
await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSource();
const existingMessageChannels =
await this.messageChannelRepository.find({
where: {
connectedAccountId: newOrExistingConnectedAccountId,
workspaceId,
},
});
await workspaceDataSource.transaction(
async (manager: WorkspaceEntityManager) => {
const existingCalendarChannels =
await this.calendarChannelRepository.find({
where: {
connectedAccountId: newOrExistingConnectedAccountId,
workspaceId,
},
});
await this.messageChannelRepository.manager.transaction(
async (transactionManager: EntityManager) => {
await this.createConnectedAccountService.createConnectedAccount({
workspaceId,
connectedAccountId: newOrExistingConnectedAccountId,
@@ -154,7 +193,7 @@ export class GoogleAPIsService {
refreshToken: input.refreshToken,
accountOwnerId: workspaceMemberId,
scopes,
manager,
transactionManager,
});
if (existingAccountId) {
@@ -165,24 +204,10 @@ export class GoogleAPIsService {
accessToken: input.accessToken,
refreshToken: input.refreshToken,
scopes,
manager,
transactionManager,
},
);
const workspaceMemberRepository =
await this.globalWorkspaceOrmManager.getRepository<WorkspaceMemberWorkspaceEntity>(
workspaceId,
'workspaceMember',
{ shouldBypassPermissionChecks: true },
);
const workspaceMember =
await workspaceMemberRepository.findOneOrFail({
where: { id: workspaceMemberId },
});
const userId = workspaceMember.userId;
await this.accountsToReconnectService.removeAccountToReconnect(
userId,
workspaceId,
@@ -190,11 +215,6 @@ export class GoogleAPIsService {
);
}
const existingMessageChannels =
await this.messageChannelDataAccessService.find(workspaceId, {
connectedAccountId: newOrExistingConnectedAccountId,
});
if (
isMessagingEnabled &&
isMessagingAvailable &&
@@ -205,18 +225,11 @@ export class GoogleAPIsService {
connectedAccountId: newOrExistingConnectedAccountId,
handle,
messageVisibility,
manager,
skipMessageChannelConfiguration,
transactionManager,
});
}
const existingCalendarChannels =
await this.calendarChannelDataAccessService.find(workspaceId, {
where: {
connectedAccountId: newOrExistingConnectedAccountId,
},
});
if (
isCalendarEnabled &&
isCalendarAvailable &&
@@ -227,18 +240,43 @@ export class GoogleAPIsService {
connectedAccountId: newOrExistingConnectedAccountId,
handle,
calendarVisibility,
manager,
skipMessageChannelConfiguration,
transactionManager,
});
}
},
);
if (isMessagingEnabled) {
const messageChannels =
await this.messageChannelDataAccessService.find(workspaceId, {
connectedAccountId: newOrExistingConnectedAccountId,
if (
isMessagingEnabled &&
isMessagingAvailable &&
existingMessageChannels.length === 0
) {
const newMessageChannel = await this.messageChannelRepository.findOne(
{
where: {
connectedAccountId: newOrExistingConnectedAccountId,
workspaceId,
},
relations: ['connectedAccount', 'messageFolders'],
},
);
if (isDefined(newMessageChannel)) {
await this.syncMessageFoldersService.syncMessageFolders({
messageChannel: newMessageChannel,
workspaceId,
});
}
}
if (isMessagingEnabled) {
const messageChannels = await this.messageChannelRepository.find({
where: {
connectedAccountId: newOrExistingConnectedAccountId,
workspaceId,
},
});
if (!isMessagingAvailable && messageChannels.length > 0) {
await this.messagingChannelSyncStatusService.markAsFailed(
@@ -268,12 +306,12 @@ export class GoogleAPIsService {
}
if (isCalendarEnabled) {
const calendarChannels =
await this.calendarChannelDataAccessService.find(workspaceId, {
where: {
connectedAccountId: newOrExistingConnectedAccountId,
},
});
const calendarChannels = await this.calendarChannelRepository.find({
where: {
connectedAccountId: newOrExistingConnectedAccountId,
workspaceId,
},
});
if (!isCalendarAvailable && calendarChannels.length > 0) {
await this.calendarChannelSyncStatusService.markAsFailedInsufficientPermissionsAndFlushCalendarEventsToImport(
@@ -1,30 +1,31 @@
import { Test, type TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { ConnectedAccountProvider } from 'twenty-shared/types';
import {
CalendarChannelSyncStage,
CalendarChannelVisibility,
ConnectedAccountProvider,
MessageChannelVisibility,
} from 'twenty-shared/types';
import { CreateCalendarChannelService } from 'src/engine/core-modules/auth/services/create-calendar-channel.service';
import { CreateConnectedAccountService } from 'src/engine/core-modules/auth/services/create-connected-account.service';
import { CreateMessageChannelService } from 'src/engine/core-modules/auth/services/create-message-channel.service';
import { MicrosoftAPIsService } from 'src/engine/core-modules/auth/services/microsoft-apis.service';
import { UpdateConnectedAccountOnReconnectService } from 'src/engine/core-modules/auth/services/update-connected-account-on-reconnect.service';
import { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service';
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 { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
import { getQueueToken } from 'src/engine/core-modules/message-queue/utils/get-queue-token.util';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.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 { 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 { CalendarChannelSyncStatusService } from 'src/modules/calendar/common/services/calendar-channel-sync-status.service';
import {
CalendarChannelSyncStage,
CalendarChannelVisibility,
} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service';
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
import { SyncMessageFoldersService } from 'src/modules/messaging/message-folder-manager/services/sync-message-folders.service';
jest.mock('uuid', () => ({
v4: jest.fn(() => 'mocked-uuid'),
@@ -36,24 +37,36 @@ describe('MicrosoftAPIsService', () => {
let calendarChannelSyncStatusService: CalendarChannelSyncStatusService;
let createMessageChannelService: CreateMessageChannelService;
const mockConnectedAccountDataAccessService = {
const mockConnectedAccountRepository = {
findOne: jest.fn(),
};
const mockMessageChannelDataAccessService = {
const mockTransactionManager = {
getRepository: jest.fn().mockReturnValue({ save: jest.fn() }),
};
const mockMessageChannelRepository = {
find: jest.fn(),
findOne: jest.fn().mockResolvedValue(null),
manager: {
transaction: jest.fn((callback) => callback(mockTransactionManager)),
},
};
const mockCalendarChannelRepository = {
find: jest.fn(),
};
const mockCalendarChannelDataAccessService = {
find: jest.fn(),
const mockUserWorkspaceRepository = {
findOne: jest.fn().mockResolvedValue({ id: 'user-workspace-id' }),
};
const mockWorkspaceMemberRepository = {
findOneOrFail: jest.fn(),
};
const mockWorkspaceDataSource = {
transaction: jest.fn((callback) => callback({})),
findOne: jest.fn().mockResolvedValue({
id: 'workspace-member-id',
userId: 'user-id',
}),
};
const mockTwentyConfigService = {
@@ -83,9 +96,6 @@ describe('MicrosoftAPIsService', () => {
return {};
}),
getGlobalWorkspaceDataSource: jest
.fn()
.mockResolvedValue(mockWorkspaceDataSource),
executeInWorkspaceContext: jest
.fn()
.mockImplementation((fn: () => any, _authContext?: any) => fn()),
@@ -154,16 +164,26 @@ describe('MicrosoftAPIsService', () => {
useValue: mockCalendarQueueService,
},
{
provide: ConnectedAccountDataAccessService,
useValue: mockConnectedAccountDataAccessService,
provide: getRepositoryToken(ConnectedAccountEntity),
useValue: mockConnectedAccountRepository,
},
{
provide: MessageChannelDataAccessService,
useValue: mockMessageChannelDataAccessService,
provide: getRepositoryToken(UserWorkspaceEntity),
useValue: mockUserWorkspaceRepository,
},
{
provide: CalendarChannelDataAccessService,
useValue: mockCalendarChannelDataAccessService,
provide: getRepositoryToken(MessageChannelEntity),
useValue: mockMessageChannelRepository,
},
{
provide: getRepositoryToken(CalendarChannelEntity),
useValue: mockCalendarChannelRepository,
},
{
provide: SyncMessageFoldersService,
useValue: {
syncMessageFolders: jest.fn().mockResolvedValue([]),
},
},
],
}).compile();
@@ -194,15 +214,15 @@ describe('MicrosoftAPIsService', () => {
const existingConnectedAccount = {
id: 'existing-account-id',
handle: 'test@example.com',
accountOwnerId: 'workspace-member-id',
userWorkspaceId: 'user-workspace-id',
provider: ConnectedAccountProvider.MICROSOFT,
} as ConnectedAccountWorkspaceEntity;
} as ConnectedAccountEntity;
mockConnectedAccountDataAccessService.findOne.mockResolvedValue(
mockConnectedAccountRepository.findOne.mockResolvedValue(
existingConnectedAccount,
);
mockWorkspaceMemberRepository.findOneOrFail.mockResolvedValue({
mockWorkspaceMemberRepository.findOne.mockResolvedValue({
id: 'workspace-member-id',
userId: 'user-id',
});
@@ -214,11 +234,11 @@ describe('MicrosoftAPIsService', () => {
syncStage: CalendarChannelSyncStage.FAILED,
};
mockCalendarChannelDataAccessService.find.mockResolvedValue([
mockCalendarChannelRepository.find.mockResolvedValue([
failedCalendarChannel,
]);
mockMessageChannelDataAccessService.find.mockResolvedValue([
mockMessageChannelRepository.find.mockResolvedValue([
{
id: 'message-channel-id',
connectedAccountId: 'existing-account-id',
@@ -227,6 +247,7 @@ describe('MicrosoftAPIsService', () => {
await service.refreshMicrosoftRefreshToken({
handle: 'test@example.com',
userId: 'user-id',
workspaceMemberId: 'workspace-member-id',
workspaceId: 'workspace-id',
accessToken: 'new-access-token',
@@ -1,21 +1,34 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { ConnectedAccountProvider } from 'twenty-shared/types';
import {
CalendarChannelSyncStage,
type CalendarChannelVisibility,
ConnectedAccountProvider,
MessageChannelSyncStage,
type MessageChannelVisibility,
} from 'twenty-shared/types';
import { v4 } from 'uuid';
import { EntityManager, Repository } from 'typeorm';
import {
AuthException,
AuthExceptionCode,
} from 'src/engine/core-modules/auth/auth.exception';
import { CreateCalendarChannelService } from 'src/engine/core-modules/auth/services/create-calendar-channel.service';
import { CreateConnectedAccountService } from 'src/engine/core-modules/auth/services/create-connected-account.service';
import { CreateMessageChannelService } from 'src/engine/core-modules/auth/services/create-message-channel.service';
import { UpdateConnectedAccountOnReconnectService } from 'src/engine/core-modules/auth/services/update-connected-account-on-reconnect.service';
import { getMicrosoftApisOauthScopes } from 'src/engine/core-modules/auth/utils/get-microsoft-apis-oauth-scopes';
import { SyncMessageFoldersService } from 'src/modules/messaging/message-folder-manager/services/sync-message-folders.service';
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 { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service';
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 { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.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 {
@@ -23,22 +36,14 @@ import {
type CalendarEventListFetchJobData,
} from 'src/modules/calendar/calendar-event-import-manager/jobs/calendar-event-list-fetch.job';
import { CalendarChannelSyncStatusService } from 'src/modules/calendar/common/services/calendar-channel-sync-status.service';
import {
CalendarChannelSyncStage,
type CalendarChannelVisibility,
} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service';
import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service';
import {
MessageChannelSyncStage,
type MessageChannelVisibility,
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
import {
MessagingMessageListFetchJob,
type MessagingMessageListFetchJobData,
} from 'src/modules/messaging/message-import-manager/jobs/messaging-message-list-fetch.job';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
import { isDefined } from 'twenty-shared/utils';
@Injectable()
export class MicrosoftAPIsService {
@@ -56,13 +61,20 @@ export class MicrosoftAPIsService {
private readonly createConnectedAccountService: CreateConnectedAccountService,
private readonly updateConnectedAccountOnReconnectService: UpdateConnectedAccountOnReconnectService,
private readonly twentyConfigService: TwentyConfigService,
private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService,
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService,
private readonly syncMessageFoldersService: SyncMessageFoldersService,
@InjectRepository(ConnectedAccountEntity)
private readonly connectedAccountRepository: Repository<ConnectedAccountEntity>,
@InjectRepository(UserWorkspaceEntity)
private readonly userWorkspaceRepository: Repository<UserWorkspaceEntity>,
@InjectRepository(MessageChannelEntity)
private readonly messageChannelRepository: Repository<MessageChannelEntity>,
@InjectRepository(CalendarChannelEntity)
private readonly calendarChannelRepository: Repository<CalendarChannelEntity>,
) {}
async refreshMicrosoftRefreshToken(input: {
handle: string;
userId: string;
workspaceMemberId: string;
workspaceId: string;
accessToken: string;
@@ -74,6 +86,7 @@ export class MicrosoftAPIsService {
const {
handle,
workspaceId,
userId,
workspaceMemberId,
calendarVisibility,
messageVisibility,
@@ -86,19 +99,48 @@ export class MicrosoftAPIsService {
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
async () => {
const connectedAccount =
await this.connectedAccountDataAccessService.findOne(workspaceId, {
where: { handle, accountOwnerId: workspaceMemberId },
});
const userWorkspace = await this.userWorkspaceRepository.findOne({
where: { userId, workspaceId },
});
if (!isDefined(userWorkspace)) {
throw new AuthException(
`User workspace not found for user ${userId} in workspace ${workspaceId}`,
AuthExceptionCode.INVALID_INPUT,
);
}
const userWorkspaceId = userWorkspace.id;
const connectedAccount = await this.connectedAccountRepository.findOne({
where: {
handle,
userWorkspaceId: userWorkspaceId,
workspaceId,
},
});
const existingAccountId = connectedAccount?.id;
const newOrExistingConnectedAccountId = existingAccountId ?? v4();
const workspaceDataSource =
await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSource();
const existingMessageChannels =
await this.messageChannelRepository.find({
where: {
connectedAccountId: newOrExistingConnectedAccountId,
workspaceId,
},
});
await workspaceDataSource.transaction(
async (manager: WorkspaceEntityManager) => {
const existingCalendarChannels =
await this.calendarChannelRepository.find({
where: {
connectedAccountId: newOrExistingConnectedAccountId,
workspaceId,
},
});
await this.messageChannelRepository.manager.transaction(
async (transactionManager: EntityManager) => {
await this.createConnectedAccountService.createConnectedAccount({
workspaceId,
connectedAccountId: newOrExistingConnectedAccountId,
@@ -108,7 +150,7 @@ export class MicrosoftAPIsService {
refreshToken: input.refreshToken,
accountOwnerId: workspaceMemberId,
scopes,
manager,
transactionManager,
});
if (existingAccountId) {
@@ -119,24 +161,10 @@ export class MicrosoftAPIsService {
accessToken: input.accessToken,
refreshToken: input.refreshToken,
scopes,
manager,
transactionManager,
},
);
const workspaceMemberRepository =
await this.globalWorkspaceOrmManager.getRepository<WorkspaceMemberWorkspaceEntity>(
workspaceId,
'workspaceMember',
{ shouldBypassPermissionChecks: true },
);
const workspaceMember =
await workspaceMemberRepository.findOneOrFail({
where: { id: workspaceMemberId },
});
const userId = workspaceMember.userId;
await this.accountsToReconnectService.removeAccountToReconnect(
userId,
workspaceId,
@@ -154,11 +182,6 @@ export class MicrosoftAPIsService {
);
}
const existingMessageChannels =
await this.messageChannelDataAccessService.find(workspaceId, {
connectedAccountId: newOrExistingConnectedAccountId,
});
if (
this.twentyConfigService.get(
'MESSAGING_PROVIDER_MICROSOFT_ENABLED',
@@ -170,18 +193,11 @@ export class MicrosoftAPIsService {
connectedAccountId: newOrExistingConnectedAccountId,
handle,
messageVisibility,
manager,
skipMessageChannelConfiguration,
transactionManager,
});
}
const existingCalendarChannels =
await this.calendarChannelDataAccessService.find(workspaceId, {
where: {
connectedAccountId: newOrExistingConnectedAccountId,
},
});
if (
this.twentyConfigService.get(
'CALENDAR_PROVIDER_MICROSOFT_ENABLED',
@@ -193,20 +209,46 @@ export class MicrosoftAPIsService {
connectedAccountId: newOrExistingConnectedAccountId,
handle,
calendarVisibility,
manager,
skipMessageChannelConfiguration,
transactionManager,
});
}
},
);
if (
this.twentyConfigService.get(
'MESSAGING_PROVIDER_MICROSOFT_ENABLED',
) &&
existingMessageChannels.length === 0
) {
const newMessageChannel = await this.messageChannelRepository.findOne(
{
where: {
connectedAccountId: newOrExistingConnectedAccountId,
workspaceId,
},
relations: ['connectedAccount', 'messageFolders'],
},
);
if (isDefined(newMessageChannel)) {
await this.syncMessageFoldersService.syncMessageFolders({
messageChannel: newMessageChannel,
workspaceId,
});
}
}
if (
this.twentyConfigService.get('MESSAGING_PROVIDER_MICROSOFT_ENABLED')
) {
const messageChannels =
await this.messageChannelDataAccessService.find(workspaceId, {
const messageChannels = await this.messageChannelRepository.find({
where: {
connectedAccountId: newOrExistingConnectedAccountId,
});
workspaceId,
},
});
for (const messageChannel of messageChannels) {
if (
@@ -227,12 +269,12 @@ export class MicrosoftAPIsService {
if (
this.twentyConfigService.get('CALENDAR_PROVIDER_MICROSOFT_ENABLED')
) {
const calendarChannels =
await this.calendarChannelDataAccessService.find(workspaceId, {
where: {
connectedAccountId: newOrExistingConnectedAccountId,
},
});
const calendarChannels = await this.calendarChannelRepository.find({
where: {
connectedAccountId: newOrExistingConnectedAccountId,
workspaceId,
},
});
for (const calendarChannel of calendarChannels) {
if (
@@ -1,7 +1,8 @@
import { Injectable } from '@nestjs/common';
import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service';
import { type WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { EntityManager } from 'typeorm';
import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.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';
@@ -11,14 +12,13 @@ export type UpdateConnectedAccountOnReconnectInput = {
accessToken: string;
refreshToken: string;
scopes: string[];
manager: WorkspaceEntityManager;
transactionManager: EntityManager;
};
@Injectable()
export class UpdateConnectedAccountOnReconnectService {
constructor(
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService,
) {}
async updateConnectedAccountOnReconnect(
@@ -35,18 +35,20 @@ export class UpdateConnectedAccountOnReconnectService {
const authContext = buildSystemAuthContext(workspaceId);
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
await this.connectedAccountDataAccessService.update(
workspaceId,
{
id: connectedAccountId,
},
{
accessToken,
refreshToken,
scopes,
authFailedAt: null,
},
);
await input.transactionManager
.getRepository(ConnectedAccountEntity)
.update(
{
id: connectedAccountId,
workspaceId,
},
{
accessToken,
refreshToken,
scopes,
authFailedAt: null,
},
);
}, authContext);
}
}
@@ -1,7 +1,9 @@
import { type Request } from 'express';
import { type CalendarChannelVisibility } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
import { type MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
import {
type CalendarChannelVisibility,
type MessageChannelVisibility,
} from 'twenty-shared/types';
export type APIsOAuthRequest = Omit<
Request,
@@ -1,5 +1,7 @@
import { type CalendarChannelVisibility } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
import { type MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
import {
CalendarChannelVisibility,
MessageChannelVisibility,
} from 'twenty-shared/types';
export type APIsOAuthState = {
transientToken?: string;
@@ -1,7 +1,9 @@
import {
CalendarChannelVisibility,
MessageChannelVisibility,
} from 'twenty-shared/types';
import { type APIsOAuthRequest } from 'src/engine/core-modules/auth/types/apis-oauth-request.type';
import { setRequestExtraParams } from 'src/engine/core-modules/auth/utils/google-apis-set-request-extra-params.util';
import { CalendarChannelVisibility } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
import { MessageChannelVisibility } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
describe('googleApisSetRequestExtraParams', () => {
it('should set request extra params', () => {