feat: migrate ConnectedAccount infrastructure entities to metadata schema (#18784)
## Summary - Migrates 4 entities (`connectedAccount`, `messageChannel`, `calendarChannel`, `messageFolder`) from per-workspace schemas to the shared `core` metadata schema - Introduces a `IS_CONNECTED_ACCOUNT_MIGRATED` feature flag to control the migration: when enabled, reads come from core metadata and all writes are dual-written to both workspace and core - Extracts 12 enums from workspace entity files to `twenty-shared` for reuse across frontend and backend - Creates new TypeORM entities, metadata services, GraphQL resolvers/DTOs, and exception interceptors per entity - Each entity owns its own data access module (`ConnectedAccountDataAccessModule`, `MessageChannelDataAccessModule`, `CalendarChannelDataAccessModule`, `MessageFolderDataAccessModule`) — no umbrella infrastructure module - Adds a 1.20 upgrade command that backfills data from workspace schemas to core (preserving UUIDs) and enables the feature flag - Replaces direct repository access with data access service calls across ~50 files in messaging, calendar, and connected-account modules - Adds `lastSignedInAt` and `oidcTokenClaims` fields to the new `ConnectedAccountEntity` - Drops unused `lastSyncHistoryId` field from the migrated connected account entity ## Test plan - [x] Lint passes (`npx nx lint:diff-with-main twenty-server`) - [x] Typecheck passes (`npx nx typecheck twenty-server`) - [x] All unit tests pass (477 suites, 4267 tests, 0 failures) - [ ] Manual test: verify messaging sync works with feature flag disabled (existing behavior) - [ ] Manual test: run upgrade command on a workspace, verify data backfilled to core tables - [ ] Manual test: verify messaging/calendar sync works with feature flag enabled (dual-write path) - [ ] Manual test: verify GraphQL metadata resolvers return correct data when flag enabled
This commit is contained in:
+4
@@ -1,5 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { CalendarChannelDataAccessModule } from 'src/engine/metadata-modules/calendar-channel/data-access/calendar-channel-data-access.module';
|
||||
import { MessageChannelDataAccessModule } from 'src/engine/metadata-modules/message-channel/data-access/message-channel-data-access.module';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||
import { ChannelSyncResolver } from 'src/modules/connected-account/channel-sync/channel-sync.resolver';
|
||||
@@ -8,6 +10,8 @@ import { MessagingCommonModule } from 'src/modules/messaging/common/messaging-co
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
CalendarChannelDataAccessModule,
|
||||
MessageChannelDataAccessModule,
|
||||
PermissionsModule,
|
||||
WorkspaceDataSourceModule,
|
||||
MessagingCommonModule,
|
||||
|
||||
+26
-30
@@ -3,6 +3,8 @@ import { Injectable } from '@nestjs/common';
|
||||
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 { CalendarChannelDataAccessService } from 'src/engine/metadata-modules/calendar-channel/data-access/services/calendar-channel-data-access.service';
|
||||
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 {
|
||||
@@ -12,13 +14,9 @@ import {
|
||||
import {
|
||||
CalendarChannelSyncStage,
|
||||
CalendarChannelSyncStatus,
|
||||
type CalendarChannelWorkspaceEntity,
|
||||
} from 'src/modules/calendar/common/standard-objects/calendar-channel.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 { MessageChannelSyncStage } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import {
|
||||
MessagingMessageListFetchJob,
|
||||
type MessagingMessageListFetchJobData,
|
||||
@@ -37,7 +35,9 @@ export class ChannelSyncService {
|
||||
private readonly messageQueueService: MessageQueueService,
|
||||
@InjectMessageQueue(MessageQueue.calendarQueue)
|
||||
private readonly calendarQueueService: MessageQueueService,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
private readonly messageChannelSyncStatusService: MessageChannelSyncStatusService,
|
||||
private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService,
|
||||
) {}
|
||||
|
||||
async startChannelSync(input: StartChannelSyncInput): Promise<void> {
|
||||
@@ -54,18 +54,13 @@ export class ChannelSyncService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
const messageChannels = await messageChannelRepository.find({
|
||||
where: {
|
||||
const messageChannels = await this.messageChannelDataAccessService.find(
|
||||
workspaceId,
|
||||
{
|
||||
connectedAccountId,
|
||||
syncStage: MessageChannelSyncStage.PENDING_CONFIGURATION,
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
for (const messageChannel of messageChannels) {
|
||||
await this.messageChannelSyncStatusService.markAsMessagesListFetchScheduled(
|
||||
@@ -91,25 +86,26 @@ export class ChannelSyncService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
const calendarChannels = await calendarChannelRepository.find({
|
||||
where: {
|
||||
connectedAccountId,
|
||||
syncStage: CalendarChannelSyncStage.PENDING_CONFIGURATION,
|
||||
const calendarChannels = await this.calendarChannelDataAccessService.find(
|
||||
workspaceId,
|
||||
{
|
||||
where: {
|
||||
connectedAccountId,
|
||||
syncStage: CalendarChannelSyncStage.PENDING_CONFIGURATION,
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
for (const calendarChannel of calendarChannels) {
|
||||
await calendarChannelRepository.update(calendarChannel.id, {
|
||||
syncStage:
|
||||
CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED,
|
||||
syncStatus: CalendarChannelSyncStatus.ONGOING,
|
||||
});
|
||||
await this.calendarChannelDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: calendarChannel.id },
|
||||
{
|
||||
syncStage:
|
||||
CalendarChannelSyncStage.CALENDAR_EVENT_LIST_FETCH_SCHEDULED,
|
||||
syncStatus: CalendarChannelSyncStatus.ONGOING,
|
||||
},
|
||||
);
|
||||
|
||||
await this.calendarQueueService.add<CalendarEventListFetchJobData>(
|
||||
CalendarEventListFetchJob.name,
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { UserVarsModule } from 'src/engine/core-modules/user/user-vars/user-vars.module';
|
||||
import { ConnectedAccountDataAccessModule } from 'src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module';
|
||||
import { DeleteWorkspaceMemberConnectedAccountsCleanupJob } from 'src/modules/connected-account/jobs/delete-workspace-member-connected-accounts.job';
|
||||
import { ConnectedAccountWorkspaceMemberListener } from 'src/modules/connected-account/listeners/connected-account-workspace-member.listener';
|
||||
import { ConnectedAccountListener } from 'src/modules/connected-account/listeners/connected-account.listener';
|
||||
import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service';
|
||||
|
||||
@Module({
|
||||
imports: [UserVarsModule],
|
||||
imports: [ConnectedAccountDataAccessModule, UserVarsModule],
|
||||
providers: [
|
||||
AccountsToReconnectService,
|
||||
ConnectedAccountListener,
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { ConnectedAccountDataAccessModule } from 'src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module';
|
||||
import { GmailEmailAliasErrorHandlerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/services/google-email-alias-error-handler.service';
|
||||
import { GoogleEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/services/google-email-alias-manager.service';
|
||||
import { MicrosoftEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/microsoft/services/microsoft-email-alias-manager.service';
|
||||
@@ -7,7 +8,7 @@ import { EmailAliasManagerService } from 'src/modules/connected-account/email-al
|
||||
import { OAuth2ClientManagerModule } from 'src/modules/connected-account/oauth2-client-manager/oauth2-client-manager.module';
|
||||
|
||||
@Module({
|
||||
imports: [OAuth2ClientManagerModule],
|
||||
imports: [OAuth2ClientManagerModule, ConnectedAccountDataAccessModule],
|
||||
providers: [
|
||||
EmailAliasManagerService,
|
||||
GoogleEmailAliasManagerService,
|
||||
|
||||
+11
-13
@@ -1,8 +1,8 @@
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import { type Repository } from 'typeorm';
|
||||
|
||||
import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { GoogleEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/services/google-email-alias-manager.service';
|
||||
import { microsoftGraphMeResponseWithProxyAddresses } from 'src/modules/connected-account/email-alias-manager/drivers/microsoft/mocks/microsoft-api-examples';
|
||||
@@ -15,30 +15,27 @@ import { EmailAliasManagerService } from './email-alias-manager.service';
|
||||
describe('Email Alias Manager Service', () => {
|
||||
let emailAliasManagerService: EmailAliasManagerService;
|
||||
let microsoftEmailAliasManagerService: MicrosoftEmailAliasManagerService;
|
||||
let connectedAccountRepository: Partial<
|
||||
Repository<ConnectedAccountWorkspaceEntity>
|
||||
>;
|
||||
const mockConnectedAccountDataAccessService = {
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
update: jest.fn().mockResolvedValue((arg) => arg),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
connectedAccountRepository = {
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
update: jest.fn().mockResolvedValue((arg) => arg),
|
||||
};
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
{
|
||||
provide: GlobalWorkspaceOrmManager,
|
||||
useValue: {
|
||||
getRepository: jest
|
||||
.fn()
|
||||
.mockResolvedValue(connectedAccountRepository),
|
||||
executeInWorkspaceContext: jest
|
||||
.fn()
|
||||
.mockImplementation((fn: () => any, _authContext?: any) => fn()),
|
||||
},
|
||||
},
|
||||
EmailAliasManagerService,
|
||||
{
|
||||
provide: ConnectedAccountDataAccessService,
|
||||
useValue: mockConnectedAccountDataAccessService,
|
||||
},
|
||||
{
|
||||
provide: GoogleEmailAliasManagerService,
|
||||
useValue: {},
|
||||
@@ -96,7 +93,8 @@ describe('Email Alias Manager Service', () => {
|
||||
microsoftEmailAliasManagerService.getHandleAliases,
|
||||
).toHaveBeenCalledWith(mockConnectedAccount);
|
||||
|
||||
expect(connectedAccountRepository.update).toHaveBeenCalledWith(
|
||||
expect(mockConnectedAccountDataAccessService.update).toHaveBeenCalledWith(
|
||||
'test-workspace-id',
|
||||
{ id: mockConnectedAccount.id },
|
||||
{
|
||||
handleAliases: expectedAliases,
|
||||
|
||||
+4
-7
@@ -3,6 +3,7 @@ import { Injectable } from '@nestjs/common';
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import { assertUnreachable } from 'twenty-shared/utils';
|
||||
|
||||
import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-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 { GoogleEmailAliasManagerService } from 'src/modules/connected-account/email-alias-manager/drivers/google/services/google-email-alias-manager.service';
|
||||
@@ -15,6 +16,7 @@ export class EmailAliasManagerService {
|
||||
private readonly googleEmailAliasManagerService: GoogleEmailAliasManagerService,
|
||||
private readonly microsoftEmailAliasManagerService: MicrosoftEmailAliasManagerService,
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService,
|
||||
) {}
|
||||
|
||||
public async refreshHandleAliases(
|
||||
@@ -50,13 +52,8 @@ export class EmailAliasManagerService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const connectedAccountRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<ConnectedAccountWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'connectedAccount',
|
||||
);
|
||||
|
||||
await connectedAccountRepository.update(
|
||||
await this.connectedAccountDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: connectedAccount.id },
|
||||
{
|
||||
handleAliases: handleAliases.join(','), // TODO: modify handleAliases to be of fieldmetadatatype array
|
||||
|
||||
@@ -5,6 +5,9 @@ import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { MessageQueueModule } from 'src/engine/core-modules/message-queue/message-queue.module';
|
||||
import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty-config.module';
|
||||
import { CalendarChannelDataAccessModule } from 'src/engine/metadata-modules/calendar-channel/data-access/calendar-channel-data-access.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 { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
|
||||
import { WorkspaceEventEmitterModule } from 'src/engine/workspace-event-emitter/workspace-event-emitter.module';
|
||||
@@ -19,6 +22,9 @@ import { ImapSmtpCalDavAPIService } from 'src/modules/connected-account/services
|
||||
TwentyORMModule,
|
||||
FeatureFlagModule,
|
||||
AuthModule,
|
||||
CalendarChannelDataAccessModule,
|
||||
ConnectedAccountDataAccessModule,
|
||||
MessageChannelDataAccessModule,
|
||||
],
|
||||
providers: [ImapSmtpCalDavAPIService],
|
||||
exports: [ImapSmtpCalDavAPIService],
|
||||
|
||||
+3
-8
@@ -1,9 +1,9 @@
|
||||
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 { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-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 { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
|
||||
export type DeleteWorkspaceMemberConnectedAccountsCleanupJobData = {
|
||||
workspaceId: string;
|
||||
@@ -14,6 +14,7 @@ export type DeleteWorkspaceMemberConnectedAccountsCleanupJobData = {
|
||||
export class DeleteWorkspaceMemberConnectedAccountsCleanupJob {
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService,
|
||||
) {}
|
||||
|
||||
@Process(DeleteWorkspaceMemberConnectedAccountsCleanupJob.name)
|
||||
@@ -25,13 +26,7 @@ export class DeleteWorkspaceMemberConnectedAccountsCleanupJob {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const connectedAccountRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<ConnectedAccountWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'connectedAccount',
|
||||
);
|
||||
|
||||
await connectedAccountRepository.delete({
|
||||
await this.connectedAccountDataAccessService.delete(workspaceId, {
|
||||
accountOwnerId: workspaceMemberId,
|
||||
});
|
||||
}, authContext);
|
||||
|
||||
+3
-9
@@ -10,16 +10,16 @@ import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/wo
|
||||
import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util';
|
||||
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 { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
import { type MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
|
||||
@WorkspaceQueryHook(`connectedAccount.destroyOne`)
|
||||
export class ConnectedAccountDeleteOnePreQueryHook
|
||||
implements WorkspacePreQueryHookInstance
|
||||
{
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
) {}
|
||||
@@ -38,13 +38,7 @@ export class ConnectedAccountDeleteOnePreQueryHook
|
||||
const messageChannels =
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
async () => {
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspace.id,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
return messageChannelRepository.findBy({
|
||||
return this.messageChannelDataAccessService.find(workspace.id, {
|
||||
connectedAccountId,
|
||||
});
|
||||
},
|
||||
|
||||
+2
@@ -3,6 +3,7 @@ import { Module } from '@nestjs/common';
|
||||
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
||||
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
|
||||
import { MessageChannelDataAccessModule } from 'src/engine/metadata-modules/message-channel/data-access/message-channel-data-access.module';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { ConnectedAccountDeleteOnePreQueryHook } from 'src/modules/connected-account/query-hooks/connected-account-delete-one.pre-query.hook';
|
||||
|
||||
@@ -10,6 +11,7 @@ import { ConnectedAccountDeleteOnePreQueryHook } from 'src/modules/connected-acc
|
||||
imports: [
|
||||
NestjsQueryTypeOrmModule.forFeature([ObjectMetadataEntity]),
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
MessageChannelDataAccessModule,
|
||||
],
|
||||
providers: [ConnectedAccountDeleteOnePreQueryHook],
|
||||
})
|
||||
|
||||
+2
@@ -1,6 +1,7 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { JwtModule } from 'src/engine/core-modules/jwt/jwt.module';
|
||||
import { ConnectedAccountDataAccessModule } from 'src/engine/metadata-modules/connected-account/data-access/connected-account-data-access.module';
|
||||
import { GoogleAPIRefreshAccessTokenModule } from 'src/modules/connected-account/refresh-tokens-manager/drivers/google/google-api-refresh-access-token.module';
|
||||
import { MicrosoftAPIRefreshAccessTokenModule } from 'src/modules/connected-account/refresh-tokens-manager/drivers/microsoft/microsoft-api-refresh-access-token.module';
|
||||
import { ConnectedAccountRefreshTokensService } from 'src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service';
|
||||
@@ -8,6 +9,7 @@ import { ConnectedAccountRefreshTokensService } from 'src/modules/connected-acco
|
||||
@Module({
|
||||
imports: [
|
||||
JwtModule,
|
||||
ConnectedAccountDataAccessModule,
|
||||
GoogleAPIRefreshAccessTokenModule,
|
||||
MicrosoftAPIRefreshAccessTokenModule,
|
||||
],
|
||||
|
||||
+19
-21
@@ -2,6 +2,7 @@ import { Test, type TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
|
||||
import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-data-access.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { GoogleAPIRefreshAccessTokenService } from 'src/modules/connected-account/refresh-tokens-manager/drivers/google/services/google-api-refresh-tokens.service';
|
||||
import { MicrosoftAPIRefreshAccessTokenService } from 'src/modules/connected-account/refresh-tokens-manager/drivers/microsoft/services/microsoft-api-refresh-tokens.service';
|
||||
@@ -17,7 +18,7 @@ describe('ConnectedAccountRefreshTokensService', () => {
|
||||
let service: ConnectedAccountRefreshTokensService;
|
||||
let googleAPIRefreshAccessTokenService: GoogleAPIRefreshAccessTokenService;
|
||||
let microsoftAPIRefreshAccessTokenService: MicrosoftAPIRefreshAccessTokenService;
|
||||
let globalWorkspaceOrmManager: GlobalWorkspaceOrmManager;
|
||||
let connectedAccountDataAccessService: ConnectedAccountDataAccessService;
|
||||
|
||||
const mockWorkspaceId = 'workspace-123';
|
||||
const mockConnectedAccountId = 'account-456';
|
||||
@@ -44,13 +45,18 @@ describe('ConnectedAccountRefreshTokensService', () => {
|
||||
{
|
||||
provide: GlobalWorkspaceOrmManager,
|
||||
useValue: {
|
||||
getRepository: jest.fn(),
|
||||
executeInWorkspaceContext: jest
|
||||
.fn()
|
||||
|
||||
.mockImplementation((fn: () => any, _authContext?: any) => fn()),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: ConnectedAccountDataAccessService,
|
||||
useValue: {
|
||||
update: jest.fn(),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
@@ -65,9 +71,10 @@ describe('ConnectedAccountRefreshTokensService', () => {
|
||||
module.get<MicrosoftAPIRefreshAccessTokenService>(
|
||||
MicrosoftAPIRefreshAccessTokenService,
|
||||
);
|
||||
globalWorkspaceOrmManager = module.get<GlobalWorkspaceOrmManager>(
|
||||
GlobalWorkspaceOrmManager,
|
||||
);
|
||||
connectedAccountDataAccessService =
|
||||
module.get<ConnectedAccountDataAccessService>(
|
||||
ConnectedAccountDataAccessService,
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -96,7 +103,7 @@ describe('ConnectedAccountRefreshTokensService', () => {
|
||||
expect(
|
||||
microsoftAPIRefreshAccessTokenService.refreshTokens,
|
||||
).not.toHaveBeenCalled();
|
||||
expect(globalWorkspaceOrmManager.getRepository).not.toHaveBeenCalled();
|
||||
expect(connectedAccountDataAccessService.update).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should refresh and save new Microsoft token when expired (lastCredentialsRefreshedAt is old)', async () => {
|
||||
@@ -108,7 +115,6 @@ describe('ConnectedAccountRefreshTokensService', () => {
|
||||
lastCredentialsRefreshedAt: new Date(Date.now() - 2 * 60 * 60 * 1000), // 2 hours ago
|
||||
} as ConnectedAccountWorkspaceEntity;
|
||||
|
||||
const mockRepository = { update: jest.fn() };
|
||||
const newTokens = {
|
||||
accessToken: mockNewAccessToken,
|
||||
refreshToken: mockRefreshToken,
|
||||
@@ -117,9 +123,6 @@ describe('ConnectedAccountRefreshTokensService', () => {
|
||||
jest
|
||||
.spyOn(microsoftAPIRefreshAccessTokenService, 'refreshTokens')
|
||||
.mockResolvedValue(newTokens);
|
||||
jest
|
||||
.spyOn(globalWorkspaceOrmManager, 'getRepository')
|
||||
.mockResolvedValue(mockRepository as any);
|
||||
|
||||
const result = await service.refreshAndSaveTokens(
|
||||
connectedAccount,
|
||||
@@ -130,7 +133,8 @@ describe('ConnectedAccountRefreshTokensService', () => {
|
||||
expect(
|
||||
microsoftAPIRefreshAccessTokenService.refreshTokens,
|
||||
).toHaveBeenCalledWith(mockRefreshToken);
|
||||
expect(mockRepository.update).toHaveBeenCalledWith(
|
||||
expect(connectedAccountDataAccessService.update).toHaveBeenCalledWith(
|
||||
mockWorkspaceId,
|
||||
{ id: mockConnectedAccountId },
|
||||
expect.objectContaining({
|
||||
...newTokens,
|
||||
@@ -148,7 +152,6 @@ describe('ConnectedAccountRefreshTokensService', () => {
|
||||
lastCredentialsRefreshedAt: new Date(Date.now() - 2 * 60 * 60 * 1000), // 2 hours ago
|
||||
} as ConnectedAccountWorkspaceEntity;
|
||||
|
||||
const mockRepository = { update: jest.fn() };
|
||||
const newTokens = {
|
||||
accessToken: mockNewAccessToken,
|
||||
refreshToken: mockRefreshToken,
|
||||
@@ -157,9 +160,6 @@ describe('ConnectedAccountRefreshTokensService', () => {
|
||||
jest
|
||||
.spyOn(googleAPIRefreshAccessTokenService, 'refreshTokens')
|
||||
.mockResolvedValue(newTokens);
|
||||
jest
|
||||
.spyOn(globalWorkspaceOrmManager, 'getRepository')
|
||||
.mockResolvedValue(mockRepository as any);
|
||||
|
||||
const result = await service.refreshAndSaveTokens(
|
||||
connectedAccount,
|
||||
@@ -170,7 +170,8 @@ describe('ConnectedAccountRefreshTokensService', () => {
|
||||
expect(
|
||||
googleAPIRefreshAccessTokenService.refreshTokens,
|
||||
).toHaveBeenCalledWith(mockRefreshToken);
|
||||
expect(mockRepository.update).toHaveBeenCalledWith(
|
||||
expect(connectedAccountDataAccessService.update).toHaveBeenCalledWith(
|
||||
mockWorkspaceId,
|
||||
{ id: mockConnectedAccountId },
|
||||
expect.objectContaining({
|
||||
...newTokens,
|
||||
@@ -188,7 +189,6 @@ describe('ConnectedAccountRefreshTokensService', () => {
|
||||
lastCredentialsRefreshedAt: null,
|
||||
} as ConnectedAccountWorkspaceEntity;
|
||||
|
||||
const mockRepository = { update: jest.fn() };
|
||||
const newTokens = {
|
||||
accessToken: mockNewAccessToken,
|
||||
refreshToken: mockRefreshToken,
|
||||
@@ -197,9 +197,6 @@ describe('ConnectedAccountRefreshTokensService', () => {
|
||||
jest
|
||||
.spyOn(microsoftAPIRefreshAccessTokenService, 'refreshTokens')
|
||||
.mockResolvedValue(newTokens);
|
||||
jest
|
||||
.spyOn(globalWorkspaceOrmManager, 'getRepository')
|
||||
.mockResolvedValue(mockRepository as any);
|
||||
|
||||
const result = await service.refreshAndSaveTokens(
|
||||
connectedAccount,
|
||||
@@ -210,7 +207,8 @@ describe('ConnectedAccountRefreshTokensService', () => {
|
||||
expect(
|
||||
microsoftAPIRefreshAccessTokenService.refreshTokens,
|
||||
).toHaveBeenCalledWith(mockRefreshToken);
|
||||
expect(mockRepository.update).toHaveBeenCalledWith(
|
||||
expect(connectedAccountDataAccessService.update).toHaveBeenCalledWith(
|
||||
mockWorkspaceId,
|
||||
{ id: mockConnectedAccountId },
|
||||
expect.objectContaining({
|
||||
...newTokens,
|
||||
|
||||
+4
-7
@@ -3,6 +3,7 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import { assertUnreachable, isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ConnectedAccountDataAccessService } from 'src/engine/metadata-modules/connected-account/data-access/services/connected-account-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 { GoogleAPIRefreshAccessTokenService } from 'src/modules/connected-account/refresh-tokens-manager/drivers/google/services/google-api-refresh-tokens.service';
|
||||
@@ -30,6 +31,7 @@ export class ConnectedAccountRefreshTokensService {
|
||||
private readonly googleAPIRefreshAccessTokenService: GoogleAPIRefreshAccessTokenService,
|
||||
private readonly microsoftAPIRefreshAccessTokenService: MicrosoftAPIRefreshAccessTokenService,
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService,
|
||||
) {}
|
||||
|
||||
async refreshAndSaveTokens(
|
||||
@@ -78,13 +80,8 @@ export class ConnectedAccountRefreshTokensService {
|
||||
const authContext = buildSystemAuthContext(workspaceId);
|
||||
|
||||
await this.globalWorkspaceOrmManager.executeInWorkspaceContext(async () => {
|
||||
const connectedAccountRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<ConnectedAccountWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'connectedAccount',
|
||||
);
|
||||
|
||||
await connectedAccountRepository.update(
|
||||
await this.connectedAccountDataAccessService.update(
|
||||
workspaceId,
|
||||
{ id: connectedAccount.id },
|
||||
{
|
||||
...connectedAccountTokens,
|
||||
|
||||
+56
-51
@@ -5,6 +5,9 @@ import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import { CreateCalendarChannelService } from 'src/engine/core-modules/auth/services/create-calendar-channel.service';
|
||||
import { CreateMessageChannelService } from 'src/engine/core-modules/auth/services/create-message-channel.service';
|
||||
import { type EmailAccountConnectionParameters } from 'src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection.dto';
|
||||
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 { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { type CalendarChannelWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
|
||||
import { ImapSmtpCalDavAPIService } from 'src/modules/connected-account/services/imap-smtp-caldav-apis.service';
|
||||
@@ -18,19 +21,17 @@ jest.mock('uuid', () => ({
|
||||
describe('ImapSmtpCalDavAPIService', () => {
|
||||
let service: ImapSmtpCalDavAPIService;
|
||||
|
||||
const mockConnectedAccountRepository = {
|
||||
const mockConnectedAccountDataAccessService = {
|
||||
findOne: jest.fn(),
|
||||
save: jest.fn(),
|
||||
};
|
||||
|
||||
const mockMessageChannelRepository = {
|
||||
const mockMessageChannelDataAccessService = {
|
||||
findOne: jest.fn(),
|
||||
save: jest.fn(),
|
||||
};
|
||||
|
||||
const mockCalendarChannelRepository = {
|
||||
const mockCalendarChannelDataAccessService = {
|
||||
findOne: jest.fn(),
|
||||
save: jest.fn(),
|
||||
};
|
||||
|
||||
const mockWorkspaceDataSource = {
|
||||
@@ -52,18 +53,6 @@ describe('ImapSmtpCalDavAPIService', () => {
|
||||
{
|
||||
provide: GlobalWorkspaceOrmManager,
|
||||
useValue: {
|
||||
getRepository: jest
|
||||
.fn()
|
||||
.mockImplementation((_workspaceId, entity) => {
|
||||
if (entity === 'connectedAccount')
|
||||
return mockConnectedAccountRepository;
|
||||
if (entity === 'messageChannel')
|
||||
return mockMessageChannelRepository;
|
||||
if (entity === 'calendarChannel')
|
||||
return mockCalendarChannelRepository;
|
||||
|
||||
return {};
|
||||
}),
|
||||
getGlobalWorkspaceDataSource: jest
|
||||
.fn()
|
||||
.mockResolvedValue(mockWorkspaceDataSource),
|
||||
@@ -81,6 +70,18 @@ describe('ImapSmtpCalDavAPIService', () => {
|
||||
provide: CreateCalendarChannelService,
|
||||
useValue: mockCreateCalendarChannelService,
|
||||
},
|
||||
{
|
||||
provide: ConnectedAccountDataAccessService,
|
||||
useValue: mockConnectedAccountDataAccessService,
|
||||
},
|
||||
{
|
||||
provide: MessageChannelDataAccessService,
|
||||
useValue: mockMessageChannelDataAccessService,
|
||||
},
|
||||
{
|
||||
provide: CalendarChannelDataAccessService,
|
||||
useValue: mockCalendarChannelDataAccessService,
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
@@ -112,13 +113,14 @@ describe('ImapSmtpCalDavAPIService', () => {
|
||||
};
|
||||
|
||||
it('should create new account with message channel when account does not exist and IMAP is configured', async () => {
|
||||
mockConnectedAccountRepository.findOne.mockResolvedValue(null);
|
||||
mockMessageChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockConnectedAccountDataAccessService.findOne.mockResolvedValue(null);
|
||||
mockMessageChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
|
||||
await service.processAccount(baseInput);
|
||||
|
||||
expect(mockConnectedAccountRepository.save).toHaveBeenCalledWith(
|
||||
expect(mockConnectedAccountDataAccessService.save).toHaveBeenCalledWith(
|
||||
'workspace-id',
|
||||
{
|
||||
id: 'mocked-uuid',
|
||||
handle: 'test@example.com',
|
||||
@@ -127,7 +129,6 @@ describe('ImapSmtpCalDavAPIService', () => {
|
||||
accountOwnerId: 'workspace-member-id',
|
||||
},
|
||||
{},
|
||||
{},
|
||||
);
|
||||
|
||||
expect(
|
||||
@@ -162,11 +163,13 @@ describe('ImapSmtpCalDavAPIService', () => {
|
||||
connectedAccountId: 'existing-account-id',
|
||||
} as CalendarChannelWorkspaceEntity;
|
||||
|
||||
mockConnectedAccountRepository.findOne.mockResolvedValue(existingAccount);
|
||||
mockMessageChannelRepository.findOne.mockResolvedValue(
|
||||
mockConnectedAccountDataAccessService.findOne.mockResolvedValue(
|
||||
existingAccount,
|
||||
);
|
||||
mockMessageChannelDataAccessService.findOne.mockResolvedValue(
|
||||
existingMessageChannel,
|
||||
);
|
||||
mockCalendarChannelRepository.findOne.mockResolvedValue(
|
||||
mockCalendarChannelDataAccessService.findOne.mockResolvedValue(
|
||||
existingCalendarChannel,
|
||||
);
|
||||
|
||||
@@ -177,7 +180,8 @@ describe('ImapSmtpCalDavAPIService', () => {
|
||||
|
||||
await service.processAccount(inputWithConnectedAccountId);
|
||||
|
||||
expect(mockConnectedAccountRepository.save).toHaveBeenCalledWith(
|
||||
expect(mockConnectedAccountDataAccessService.save).toHaveBeenCalledWith(
|
||||
'workspace-id',
|
||||
{
|
||||
id: 'existing-account-id',
|
||||
handle: 'test@example.com',
|
||||
@@ -186,7 +190,6 @@ describe('ImapSmtpCalDavAPIService', () => {
|
||||
accountOwnerId: 'workspace-member-id',
|
||||
},
|
||||
{},
|
||||
{},
|
||||
);
|
||||
|
||||
expect(
|
||||
@@ -210,9 +213,9 @@ describe('ImapSmtpCalDavAPIService', () => {
|
||||
} as EmailAccountConnectionParameters,
|
||||
};
|
||||
|
||||
mockConnectedAccountRepository.findOne.mockResolvedValue(null);
|
||||
mockMessageChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockConnectedAccountDataAccessService.findOne.mockResolvedValue(null);
|
||||
mockMessageChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
|
||||
await service.processAccount(imapOnlyInput);
|
||||
|
||||
@@ -238,9 +241,9 @@ describe('ImapSmtpCalDavAPIService', () => {
|
||||
} as EmailAccountConnectionParameters,
|
||||
};
|
||||
|
||||
mockConnectedAccountRepository.findOne.mockResolvedValue(null);
|
||||
mockMessageChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockConnectedAccountDataAccessService.findOne.mockResolvedValue(null);
|
||||
mockMessageChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
|
||||
await service.processAccount(caldavOnlyInput);
|
||||
|
||||
@@ -272,9 +275,9 @@ describe('ImapSmtpCalDavAPIService', () => {
|
||||
} as EmailAccountConnectionParameters,
|
||||
};
|
||||
|
||||
mockConnectedAccountRepository.findOne.mockResolvedValue(null);
|
||||
mockMessageChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockConnectedAccountDataAccessService.findOne.mockResolvedValue(null);
|
||||
mockMessageChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
|
||||
await service.processAccount(imapSmtpInput);
|
||||
|
||||
@@ -313,9 +316,9 @@ describe('ImapSmtpCalDavAPIService', () => {
|
||||
} as EmailAccountConnectionParameters,
|
||||
};
|
||||
|
||||
mockConnectedAccountRepository.findOne.mockResolvedValue(null);
|
||||
mockMessageChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockConnectedAccountDataAccessService.findOne.mockResolvedValue(null);
|
||||
mockMessageChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
|
||||
await service.processAccount(fullConfigInput);
|
||||
|
||||
@@ -335,23 +338,26 @@ describe('ImapSmtpCalDavAPIService', () => {
|
||||
provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV,
|
||||
} as ConnectedAccountWorkspaceEntity;
|
||||
|
||||
mockConnectedAccountRepository.findOne.mockResolvedValueOnce(
|
||||
mockConnectedAccountDataAccessService.findOne.mockResolvedValueOnce(
|
||||
existingAccount,
|
||||
);
|
||||
|
||||
mockMessageChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockMessageChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
|
||||
await service.processAccount(baseInput);
|
||||
|
||||
expect(mockConnectedAccountRepository.findOne).toHaveBeenCalledWith({
|
||||
expect(
|
||||
mockConnectedAccountDataAccessService.findOne,
|
||||
).toHaveBeenCalledWith('workspace-id', {
|
||||
where: {
|
||||
handle: 'test@example.com',
|
||||
accountOwnerId: 'workspace-member-id',
|
||||
},
|
||||
});
|
||||
|
||||
expect(mockConnectedAccountRepository.save).toHaveBeenCalledWith(
|
||||
expect(mockConnectedAccountDataAccessService.save).toHaveBeenCalledWith(
|
||||
'workspace-id',
|
||||
{
|
||||
id: 'existing-account-id',
|
||||
handle: 'test@example.com',
|
||||
@@ -360,7 +366,6 @@ describe('ImapSmtpCalDavAPIService', () => {
|
||||
accountOwnerId: 'workspace-member-id',
|
||||
},
|
||||
{},
|
||||
{},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -378,9 +383,9 @@ describe('ImapSmtpCalDavAPIService', () => {
|
||||
} as EmailAccountConnectionParameters,
|
||||
};
|
||||
|
||||
mockConnectedAccountRepository.findOne.mockResolvedValue(null);
|
||||
mockMessageChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockConnectedAccountDataAccessService.findOne.mockResolvedValue(null);
|
||||
mockMessageChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
|
||||
await service.processAccount(smtpOnlyInput);
|
||||
|
||||
@@ -393,9 +398,9 @@ describe('ImapSmtpCalDavAPIService', () => {
|
||||
});
|
||||
|
||||
it('should handle transaction correctly', async () => {
|
||||
mockConnectedAccountRepository.findOne.mockResolvedValue(null);
|
||||
mockMessageChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelRepository.findOne.mockResolvedValue(null);
|
||||
mockConnectedAccountDataAccessService.findOne.mockResolvedValue(null);
|
||||
mockMessageChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
mockCalendarChannelDataAccessService.findOne.mockResolvedValue(null);
|
||||
|
||||
await service.processAccount(baseInput);
|
||||
|
||||
|
||||
+17
-34
@@ -7,17 +7,21 @@ import { v4 } from 'uuid';
|
||||
import { CreateCalendarChannelService } from 'src/engine/core-modules/auth/services/create-calendar-channel.service';
|
||||
import { CreateMessageChannelService } from 'src/engine/core-modules/auth/services/create-message-channel.service';
|
||||
import { type EmailAccountConnectionParameters } from 'src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection.dto';
|
||||
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 { 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 CalendarChannelWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
|
||||
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';
|
||||
|
||||
@Injectable()
|
||||
export class ImapSmtpCalDavAPIService {
|
||||
constructor(
|
||||
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
|
||||
private readonly calendarChannelDataAccessService: CalendarChannelDataAccessService,
|
||||
private readonly connectedAccountDataAccessService: ConnectedAccountDataAccessService,
|
||||
private readonly messageChannelDataAccessService: MessageChannelDataAccessService,
|
||||
private readonly createMessageChannelService: CreateMessageChannelService,
|
||||
private readonly createCalendarChannelService: CreateCalendarChannelService,
|
||||
) {}
|
||||
@@ -30,15 +34,12 @@ export class ImapSmtpCalDavAPIService {
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
async () => {
|
||||
const connectedAccountRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<ConnectedAccountWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'connectedAccount',
|
||||
);
|
||||
const connectedAccount =
|
||||
await this.connectedAccountDataAccessService.findOne(workspaceId, {
|
||||
where: { id, provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV },
|
||||
});
|
||||
|
||||
return connectedAccountRepository.findOne({
|
||||
where: { id, provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV },
|
||||
});
|
||||
return connectedAccount as ConnectedAccountWorkspaceEntity | null;
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
@@ -58,29 +59,11 @@ export class ImapSmtpCalDavAPIService {
|
||||
|
||||
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
|
||||
async () => {
|
||||
const connectedAccountRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<ConnectedAccountWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'connectedAccount',
|
||||
);
|
||||
|
||||
const messageChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<MessageChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'messageChannel',
|
||||
);
|
||||
|
||||
const calendarChannelRepository =
|
||||
await this.globalWorkspaceOrmManager.getRepository<CalendarChannelWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'calendarChannel',
|
||||
);
|
||||
|
||||
const existingAccount = connectedAccountId
|
||||
? await connectedAccountRepository.findOne({
|
||||
? await this.connectedAccountDataAccessService.findOne(workspaceId, {
|
||||
where: { id: connectedAccountId },
|
||||
})
|
||||
: await connectedAccountRepository.findOne({
|
||||
: await this.connectedAccountDataAccessService.findOne(workspaceId, {
|
||||
where: { handle, accountOwnerId: workspaceMemberId },
|
||||
});
|
||||
|
||||
@@ -91,13 +74,13 @@ export class ImapSmtpCalDavAPIService {
|
||||
await this.globalWorkspaceOrmManager.getGlobalWorkspaceDataSource();
|
||||
|
||||
const existingMessageChannel = existingAccount
|
||||
? await messageChannelRepository.findOne({
|
||||
? await this.messageChannelDataAccessService.findOne(workspaceId, {
|
||||
where: { connectedAccountId: existingAccount.id },
|
||||
})
|
||||
: null;
|
||||
|
||||
const existingCalendarChannel = existingAccount
|
||||
? await calendarChannelRepository.findOne({
|
||||
? await this.calendarChannelDataAccessService.findOne(workspaceId, {
|
||||
where: { connectedAccountId: existingAccount.id },
|
||||
})
|
||||
: null;
|
||||
@@ -112,7 +95,8 @@ export class ImapSmtpCalDavAPIService {
|
||||
|
||||
await workspaceDataSource.transaction(
|
||||
async (manager: WorkspaceEntityManager) => {
|
||||
await connectedAccountRepository.save(
|
||||
await this.connectedAccountDataAccessService.save(
|
||||
workspaceId,
|
||||
{
|
||||
id: newOrExistingAccountId,
|
||||
handle,
|
||||
@@ -120,7 +104,6 @@ export class ImapSmtpCalDavAPIService {
|
||||
connectionParameters: input.connectionParameters,
|
||||
accountOwnerId: workspaceMemberId,
|
||||
},
|
||||
{},
|
||||
manager,
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user