diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-23/0-23-set-user-vars-accounts-to-reconnect.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-23/0-23-set-user-vars-accounts-to-reconnect.command.ts new file mode 100644 index 000000000..875a9fdaa --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version/0-23/0-23-set-user-vars-accounts-to-reconnect.command.ts @@ -0,0 +1,166 @@ +import { Logger } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; + +import chalk from 'chalk'; +import { Command, CommandRunner, Option } from 'nest-commander'; +import { Repository } from 'typeorm'; + +import { + KeyValuePair, + KeyValuePairType, +} from 'src/engine/core-modules/key-value-pair/key-value-pair.entity'; +import { + Workspace, + WorkspaceActivationStatus, +} from 'src/engine/core-modules/workspace/workspace.entity'; +import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { CalendarChannelSyncStatus } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; +import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service'; +import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; +import { AccountsToReconnectKeys } from 'src/modules/connected-account/types/accounts-to-reconnect-key-value.type'; +import { MessageChannelSyncStatus } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; + +interface SetUserVarsAccountsToReconnectCommandOptions { + workspaceId?: string; +} + +@Command({ + name: 'upgrade-0.23:set-user-vars-accounts-to-reconnect', + description: 'Set user vars accounts to reconnect', +}) +export class SetUserVarsAccountsToReconnectCommand extends CommandRunner { + private readonly logger = new Logger( + SetUserVarsAccountsToReconnectCommand.name, + ); + constructor( + private readonly workspaceCacheVersionService: WorkspaceCacheVersionService, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + private readonly accountsToReconnectService: AccountsToReconnectService, + @InjectRepository(KeyValuePair, 'core') + private readonly keyValuePairRepository: Repository, + @InjectRepository(Workspace, 'core') + private readonly workspaceRepository: Repository, + ) { + super(); + } + + @Option({ + flags: '-w, --workspace-id [workspace_id]', + description: 'workspace id. Command runs on all workspaces if not provided', + required: false, + }) + parseWorkspaceId(value: string): string { + return value; + } + + async run( + _passedParam: string[], + options: SetUserVarsAccountsToReconnectCommandOptions, + ): Promise { + let activeWorkspaceIds: string[] = []; + + if (options.workspaceId) { + activeWorkspaceIds = [options.workspaceId]; + } else { + const activeWorkspaces = await this.workspaceRepository.find({ + where: { + activationStatus: WorkspaceActivationStatus.ACTIVE, + ...(options.workspaceId && { id: options.workspaceId }), + }, + }); + + activeWorkspaceIds = activeWorkspaces.map((workspace) => workspace.id); + } + + if (!activeWorkspaceIds.length) { + this.logger.log(chalk.yellow('No workspace found')); + + return; + } else { + this.logger.log( + chalk.green( + `Running command on ${activeWorkspaceIds.length} workspaces`, + ), + ); + } + + // Remove all deprecated user vars + await this.keyValuePairRepository.delete({ + type: KeyValuePairType.USER_VAR, + key: 'ACCOUNTS_TO_RECONNECT', + }); + + for (const workspaceId of activeWorkspaceIds) { + try { + const connectedAccountRepository = + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, + 'connectedAccount', + ); + + try { + const connectedAccountsInFailedInsufficientPermissions = + await connectedAccountRepository.find({ + select: { + id: true, + accountOwner: { + userId: true, + }, + }, + where: [ + { + messageChannels: { + syncStatus: + MessageChannelSyncStatus.FAILED_INSUFFICIENT_PERMISSIONS, + }, + }, + { + calendarChannels: { + syncStatus: + CalendarChannelSyncStatus.FAILED_INSUFFICIENT_PERMISSIONS, + }, + }, + ], + relations: { + accountOwner: true, + }, + }); + + for (const connectedAccount of connectedAccountsInFailedInsufficientPermissions) { + try { + await this.accountsToReconnectService.addAccountToReconnectByKey( + AccountsToReconnectKeys.ACCOUNTS_TO_RECONNECT_INSUFFICIENT_PERMISSIONS, + connectedAccount.accountOwner.userId, + workspaceId, + connectedAccount.id, + ); + } catch (error) { + this.logger.error( + `Failed to add account to reconnect for workspace ${workspaceId}: ${error.message}`, + ); + throw error; + } + } + } catch (error) { + this.logger.log( + chalk.red(`Running command on workspace ${workspaceId} failed`), + ); + throw error; + } + + await this.workspaceCacheVersionService.incrementVersion(workspaceId); + + this.logger.log( + chalk.green(`Running command on workspace ${workspaceId} done`), + ); + } catch (error) { + this.logger.error( + `Migration failed for workspace ${workspaceId}: ${error.message}`, + ); + } + } + + this.logger.log(chalk.green(`Command completed!`)); + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-23/0-23-set-workspace-activation-status.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-23/0-23-set-workspace-activation-status.command.ts index ef89453a0..7331b7fde 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-23/0-23-set-workspace-activation-status.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version/0-23/0-23-set-workspace-activation-status.command.ts @@ -83,26 +83,16 @@ export class SetWorkspaceActivationStatusCommand extends CommandRunner { await this.typeORMService.connectToDataSource(dataSourceMetadata); if (workspaceDataSource) { - const queryRunner = workspaceDataSource.createQueryRunner(); - - await queryRunner.connect(); - await queryRunner.startTransaction(); - try { await this.workspaceRepository.update( { id: workspaceId }, { activationStatus: WorkspaceActivationStatus.ACTIVE }, ); - - await queryRunner.commitTransaction(); } catch (error) { - await queryRunner.rollbackTransaction(); this.logger.log( chalk.red(`Running command on workspace ${workspaceId} failed`), ); throw error; - } finally { - await queryRunner.release(); } } } diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-23/0-23-upgrade-version.command.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-23/0-23-upgrade-version.command.ts index ac1cc0ea0..77ef0ef79 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-23/0-23-upgrade-version.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version/0-23/0-23-upgrade-version.command.ts @@ -4,6 +4,7 @@ import { BackfillNewOnboardingUserVarsCommand } from 'src/database/commands/upgr import { MigrateDomainNameFromTextToLinksCommand } from 'src/database/commands/upgrade-version/0-23/0-23-migrate-domain-to-links.command'; import { MigrateLinkFieldsToLinksCommand } from 'src/database/commands/upgrade-version/0-23/0-23-migrate-link-fields-to-links.command'; import { MigrateMessageChannelSyncStatusEnumCommand } from 'src/database/commands/upgrade-version/0-23/0-23-migrate-message-channel-sync-status-enum.command'; +import { SetUserVarsAccountsToReconnectCommand } from 'src/database/commands/upgrade-version/0-23/0-23-set-user-vars-accounts-to-reconnect.command'; import { SetWorkspaceActivationStatusCommand } from 'src/database/commands/upgrade-version/0-23/0-23-set-workspace-activation-status.command'; import { UpdateActivitiesCommand } from 'src/database/commands/upgrade-version/0-23/0-23-update-activities.command'; import { UpdateFileFolderStructureCommand } from 'src/database/commands/upgrade-version/0-23/0-23-update-file-folder-structure.command'; @@ -27,6 +28,7 @@ export class UpgradeTo0_23Command extends CommandRunner { private readonly setWorkspaceActivationStatusCommand: SetWorkspaceActivationStatusCommand, private readonly updateActivitiesCommand: UpdateActivitiesCommand, private readonly backfillNewOnboardingUserVarsCommand: BackfillNewOnboardingUserVarsCommand, + private readonly setUserVarsAccountsToReconnectCommand: SetUserVarsAccountsToReconnectCommand, ) { super(); } @@ -62,5 +64,6 @@ export class UpgradeTo0_23Command extends CommandRunner { }); await this.updateActivitiesCommand.run(_passedParam, options); await this.backfillNewOnboardingUserVarsCommand.run(_passedParam, options); + await this.setUserVarsAccountsToReconnectCommand.run(_passedParam, options); } } diff --git a/packages/twenty-server/src/database/commands/upgrade-version/0-23/0-23-upgrade-version.module.ts b/packages/twenty-server/src/database/commands/upgrade-version/0-23/0-23-upgrade-version.module.ts index 05fc1396c..e62f15b65 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version/0-23/0-23-upgrade-version.module.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version/0-23/0-23-upgrade-version.module.ts @@ -5,12 +5,14 @@ import { BackfillNewOnboardingUserVarsCommand } from 'src/database/commands/upgr import { MigrateDomainNameFromTextToLinksCommand } from 'src/database/commands/upgrade-version/0-23/0-23-migrate-domain-to-links.command'; import { MigrateLinkFieldsToLinksCommand } from 'src/database/commands/upgrade-version/0-23/0-23-migrate-link-fields-to-links.command'; import { MigrateMessageChannelSyncStatusEnumCommand } from 'src/database/commands/upgrade-version/0-23/0-23-migrate-message-channel-sync-status-enum.command'; +import { SetUserVarsAccountsToReconnectCommand } from 'src/database/commands/upgrade-version/0-23/0-23-set-user-vars-accounts-to-reconnect.command'; import { SetWorkspaceActivationStatusCommand } from 'src/database/commands/upgrade-version/0-23/0-23-set-workspace-activation-status.command'; import { UpdateActivitiesCommand } from 'src/database/commands/upgrade-version/0-23/0-23-update-activities.command'; import { UpdateFileFolderStructureCommand } from 'src/database/commands/upgrade-version/0-23/0-23-update-file-folder-structure.command'; import { UpgradeTo0_23Command } from 'src/database/commands/upgrade-version/0-23/0-23-upgrade-version.command'; import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; import { BillingModule } from 'src/engine/core-modules/billing/billing.module'; +import { KeyValuePair } from 'src/engine/core-modules/key-value-pair/key-value-pair.entity'; import { OnboardingModule } from 'src/engine/core-modules/onboarding/onboarding.module'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { FileStorageModule } from 'src/engine/integrations/file-storage/file-storage.module'; @@ -22,12 +24,13 @@ import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadat import { WorkspaceCacheVersionModule } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.module'; import { WorkspaceStatusModule } from 'src/engine/workspace-manager/workspace-status/workspace-manager.module'; import { WorkspaceSyncMetadataCommandsModule } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/workspace-sync-metadata-commands.module'; +import { ConnectedAccountModule } from 'src/modules/connected-account/connected-account.module'; import { ViewModule } from 'src/modules/view/view.module'; @Module({ imports: [ + TypeOrmModule.forFeature([Workspace, KeyValuePair], 'core'), WorkspaceSyncMetadataCommandsModule, - TypeOrmModule.forFeature([Workspace], 'core'), FileStorageModule, OnboardingModule, TypeORMModule, @@ -42,16 +45,17 @@ import { ViewModule } from 'src/modules/view/view.module'; ViewModule, BillingModule, ObjectMetadataModule, + ConnectedAccountModule, ], providers: [ UpdateFileFolderStructureCommand, - UpgradeTo0_23Command, MigrateLinkFieldsToLinksCommand, MigrateDomainNameFromTextToLinksCommand, MigrateMessageChannelSyncStatusEnumCommand, SetWorkspaceActivationStatusCommand, UpdateActivitiesCommand, BackfillNewOnboardingUserVarsCommand, + SetUserVarsAccountsToReconnectCommand, UpgradeTo0_23Command, ], }) diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/calendar-event-import-manager.module.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/calendar-event-import-manager.module.ts index 27ec9ca9f..8fa22e0a0 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/calendar-event-import-manager.module.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/calendar-event-import-manager.module.ts @@ -3,7 +3,6 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { BillingModule } from 'src/engine/core-modules/billing/billing.module'; import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; -import { UserVarsModule } from 'src/engine/core-modules/user/user-vars/user-vars.module'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity'; import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module'; @@ -25,6 +24,7 @@ import { CalendarChannelEventAssociationWorkspaceEntity } from 'src/modules/cale import { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; import { CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event-participant.workspace-entity'; import { CalendarEventWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event.workspace-entity'; +import { ConnectedAccountModule } from 'src/modules/connected-account/connected-account.module'; import { RefreshAccessTokenManagerModule } from 'src/modules/connected-account/refresh-access-token-manager/refresh-access-token-manager.module'; import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity'; @@ -53,7 +53,7 @@ import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/sta BillingModule, RefreshAccessTokenManagerModule, CalendarEventParticipantManagerModule, - UserVarsModule, + ConnectedAccountModule, ], providers: [ CalendarChannelSyncStatusService, diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-channel-sync-status.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-channel-sync-status.service.ts index 9f576058b..28306cea8 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-channel-sync-status.service.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-channel-sync-status.service.ts @@ -1,6 +1,5 @@ import { Injectable } from '@nestjs/common'; -import { UserVarsService } from 'src/engine/core-modules/user/user-vars/services/user-vars.service'; import { CacheStorageService } from 'src/engine/integrations/cache-storage/cache-storage.service'; import { InjectCacheStorage } from 'src/engine/integrations/cache-storage/decorators/cache-storage.decorator'; import { CacheStorageNamespace } from 'src/engine/integrations/cache-storage/types/cache-storage-namespace.enum'; @@ -10,10 +9,8 @@ import { CalendarChannelSyncStatus, CalendarChannelWorkspaceEntity, } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; -import { - AccountsToReconnectKeyValueType, - AccountsToReconnectKeys, -} from 'src/modules/connected-account/types/accounts-to-reconnect-key-value.type'; +import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service'; +import { AccountsToReconnectKeys } from 'src/modules/connected-account/types/accounts-to-reconnect-key-value.type'; @Injectable() export class CalendarChannelSyncStatusService { @@ -21,7 +18,7 @@ export class CalendarChannelSyncStatusService { private readonly twentyORMManager: TwentyORMManager, @InjectCacheStorage(CacheStorageNamespace.Calendar) private readonly cacheStorage: CacheStorageService, - private readonly userVarsService: UserVarsService, + private readonly accountsToReconnectService: AccountsToReconnectService, ) {} public async scheduleFullCalendarEventListFetch(calendarChannelId: string) { @@ -194,24 +191,11 @@ export class CalendarChannelSyncStatusService { const userId = calendarChannel.connectedAccount.accountOwner.userId; const connectedAccountId = calendarChannel.connectedAccount.id; - const accountsToReconnect = - (await this.userVarsService.get({ - userId, - workspaceId, - key: AccountsToReconnectKeys.ACCOUNTS_TO_RECONNECT_INSUFFICIENT_PERMISSIONS, - })) ?? []; - - if (accountsToReconnect.includes(connectedAccountId)) { - return; - } - - accountsToReconnect.push(connectedAccountId); - - await this.userVarsService.set({ + await this.accountsToReconnectService.addAccountToReconnectByKey( + AccountsToReconnectKeys.ACCOUNTS_TO_RECONNECT_INSUFFICIENT_PERMISSIONS, userId, workspaceId, - key: AccountsToReconnectKeys.ACCOUNTS_TO_RECONNECT_INSUFFICIENT_PERMISSIONS, - value: accountsToReconnect, - }); + connectedAccountId, + ); } } diff --git a/packages/twenty-server/src/modules/connected-account/services/accounts-to-reconnect.service.ts b/packages/twenty-server/src/modules/connected-account/services/accounts-to-reconnect.service.ts index 60bfc322f..ae6c05ccf 100644 --- a/packages/twenty-server/src/modules/connected-account/services/accounts-to-reconnect.service.ts +++ b/packages/twenty-server/src/modules/connected-account/services/accounts-to-reconnect.service.ts @@ -64,4 +64,31 @@ export class AccountsToReconnectService { value: updatedAccountsToReconnect, }); } + + public async addAccountToReconnectByKey( + key: AccountsToReconnectKeys, + userId: string, + workspaceId: string, + connectedAccountId: string, + ) { + const accountsToReconnect = + (await this.userVarsService.get({ + userId, + workspaceId, + key, + })) ?? []; + + if (accountsToReconnect.includes(connectedAccountId)) { + return; + } + + accountsToReconnect.push(connectedAccountId); + + await this.userVarsService.set({ + userId, + workspaceId, + key, + value: accountsToReconnect, + }); + } } diff --git a/packages/twenty-server/src/modules/messaging/common/messaging-common.module.ts b/packages/twenty-server/src/modules/messaging/common/messaging-common.module.ts index 69e2dc856..29b8dd7c2 100644 --- a/packages/twenty-server/src/modules/messaging/common/messaging-common.module.ts +++ b/packages/twenty-server/src/modules/messaging/common/messaging-common.module.ts @@ -2,9 +2,9 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; -import { UserVarsModule } from 'src/engine/core-modules/user/user-vars/user-vars.module'; import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module'; import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; +import { ConnectedAccountModule } from 'src/modules/connected-account/connected-account.module'; import { MessagingChannelSyncStatusService } from 'src/modules/messaging/common/services/messaging-channel-sync-status.service'; import { MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-participant.workspace-entity'; import { MessageThreadWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-thread.workspace-entity'; @@ -21,7 +21,7 @@ import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/perso MessageThreadWorkspaceEntity, ]), TypeOrmModule.forFeature([FeatureFlagEntity], 'core'), - UserVarsModule, + ConnectedAccountModule, ], providers: [MessagingChannelSyncStatusService], exports: [MessagingChannelSyncStatusService], diff --git a/packages/twenty-server/src/modules/messaging/common/services/messaging-channel-sync-status.service.ts b/packages/twenty-server/src/modules/messaging/common/services/messaging-channel-sync-status.service.ts index adae81c30..2b326bc39 100644 --- a/packages/twenty-server/src/modules/messaging/common/services/messaging-channel-sync-status.service.ts +++ b/packages/twenty-server/src/modules/messaging/common/services/messaging-channel-sync-status.service.ts @@ -1,15 +1,12 @@ import { Injectable } from '@nestjs/common'; -import { UserVarsService } from 'src/engine/core-modules/user/user-vars/services/user-vars.service'; import { CacheStorageService } from 'src/engine/integrations/cache-storage/cache-storage.service'; import { InjectCacheStorage } from 'src/engine/integrations/cache-storage/decorators/cache-storage.decorator'; import { CacheStorageNamespace } from 'src/engine/integrations/cache-storage/types/cache-storage-namespace.enum'; import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; -import { - AccountsToReconnectKeyValueType, - AccountsToReconnectKeys, -} from 'src/modules/connected-account/types/accounts-to-reconnect-key-value.type'; +import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service'; +import { AccountsToReconnectKeys } from 'src/modules/connected-account/types/accounts-to-reconnect-key-value.type'; import { MessageChannelRepository } from 'src/modules/messaging/common/repositories/message-channel.repository'; import { MessageChannelSyncStage, @@ -24,8 +21,8 @@ export class MessagingChannelSyncStatusService { private readonly messageChannelRepository: MessageChannelRepository, @InjectCacheStorage(CacheStorageNamespace.Messaging) private readonly cacheStorage: CacheStorageService, - private readonly userVarsService: UserVarsService, private readonly twentyORMManager: TwentyORMManager, + private readonly accountsToReconnectService: AccountsToReconnectService, ) {} public async scheduleFullMessageListFetch( @@ -199,24 +196,11 @@ export class MessagingChannelSyncStatusService { const userId = messageChannel.connectedAccount.accountOwner.userId; const connectedAccountId = messageChannel.connectedAccount.id; - const accountsToReconnect = - (await this.userVarsService.get({ - userId, - workspaceId, - key: AccountsToReconnectKeys.ACCOUNTS_TO_RECONNECT_INSUFFICIENT_PERMISSIONS, - })) ?? []; - - if (accountsToReconnect.includes(connectedAccountId)) { - return; - } - - accountsToReconnect.push(connectedAccountId); - - await this.userVarsService.set({ + await this.accountsToReconnectService.addAccountToReconnectByKey( + AccountsToReconnectKeys.ACCOUNTS_TO_RECONNECT_INSUFFICIENT_PERMISSIONS, userId, workspaceId, - key: AccountsToReconnectKeys.ACCOUNTS_TO_RECONNECT_INSUFFICIENT_PERMISSIONS, - value: accountsToReconnect, - }); + connectedAccountId, + ); } }