Skip to content

Commit 5a72b94

Browse files
authored
6382 create a command to add a uservar in the key value pair table for every account which needs to reconnect (#6553)
Closes #6382 Create SetUserVarsAccountsToReconnectCommand. This command loops on all workspaces and: - deletes all user vars with deprecated key `ACCOUNTS_TO_RECONNECT` - creates a key value pair of type `USER_VAR` with a key of `ACCOUNTS_TO_RECONNECT_INSUFFICIENT_PERMISSIONS` for all connect accounts with a message channel or calendar channel with status `FAILED_INSUFFICIENT_PERMISSIONS`
1 parent 2abb6ad commit 5a72b94

File tree

9 files changed

+220
-62
lines changed

9 files changed

+220
-62
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import { Logger } from '@nestjs/common';
2+
import { InjectRepository } from '@nestjs/typeorm';
3+
4+
import chalk from 'chalk';
5+
import { Command, CommandRunner, Option } from 'nest-commander';
6+
import { Repository } from 'typeorm';
7+
8+
import {
9+
KeyValuePair,
10+
KeyValuePairType,
11+
} from 'src/engine/core-modules/key-value-pair/key-value-pair.entity';
12+
import {
13+
Workspace,
14+
WorkspaceActivationStatus,
15+
} from 'src/engine/core-modules/workspace/workspace.entity';
16+
import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service';
17+
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
18+
import { CalendarChannelSyncStatus } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
19+
import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service';
20+
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
21+
import { AccountsToReconnectKeys } from 'src/modules/connected-account/types/accounts-to-reconnect-key-value.type';
22+
import { MessageChannelSyncStatus } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
23+
24+
interface SetUserVarsAccountsToReconnectCommandOptions {
25+
workspaceId?: string;
26+
}
27+
28+
@Command({
29+
name: 'upgrade-0.23:set-user-vars-accounts-to-reconnect',
30+
description: 'Set user vars accounts to reconnect',
31+
})
32+
export class SetUserVarsAccountsToReconnectCommand extends CommandRunner {
33+
private readonly logger = new Logger(
34+
SetUserVarsAccountsToReconnectCommand.name,
35+
);
36+
constructor(
37+
private readonly workspaceCacheVersionService: WorkspaceCacheVersionService,
38+
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
39+
private readonly accountsToReconnectService: AccountsToReconnectService,
40+
@InjectRepository(KeyValuePair, 'core')
41+
private readonly keyValuePairRepository: Repository<KeyValuePair>,
42+
@InjectRepository(Workspace, 'core')
43+
private readonly workspaceRepository: Repository<Workspace>,
44+
) {
45+
super();
46+
}
47+
48+
@Option({
49+
flags: '-w, --workspace-id [workspace_id]',
50+
description: 'workspace id. Command runs on all workspaces if not provided',
51+
required: false,
52+
})
53+
parseWorkspaceId(value: string): string {
54+
return value;
55+
}
56+
57+
async run(
58+
_passedParam: string[],
59+
options: SetUserVarsAccountsToReconnectCommandOptions,
60+
): Promise<void> {
61+
let activeWorkspaceIds: string[] = [];
62+
63+
if (options.workspaceId) {
64+
activeWorkspaceIds = [options.workspaceId];
65+
} else {
66+
const activeWorkspaces = await this.workspaceRepository.find({
67+
where: {
68+
activationStatus: WorkspaceActivationStatus.ACTIVE,
69+
...(options.workspaceId && { id: options.workspaceId }),
70+
},
71+
});
72+
73+
activeWorkspaceIds = activeWorkspaces.map((workspace) => workspace.id);
74+
}
75+
76+
if (!activeWorkspaceIds.length) {
77+
this.logger.log(chalk.yellow('No workspace found'));
78+
79+
return;
80+
} else {
81+
this.logger.log(
82+
chalk.green(
83+
`Running command on ${activeWorkspaceIds.length} workspaces`,
84+
),
85+
);
86+
}
87+
88+
// Remove all deprecated user vars
89+
await this.keyValuePairRepository.delete({
90+
type: KeyValuePairType.USER_VAR,
91+
key: 'ACCOUNTS_TO_RECONNECT',
92+
});
93+
94+
for (const workspaceId of activeWorkspaceIds) {
95+
try {
96+
const connectedAccountRepository =
97+
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ConnectedAccountWorkspaceEntity>(
98+
workspaceId,
99+
'connectedAccount',
100+
);
101+
102+
try {
103+
const connectedAccountsInFailedInsufficientPermissions =
104+
await connectedAccountRepository.find({
105+
select: {
106+
id: true,
107+
accountOwner: {
108+
userId: true,
109+
},
110+
},
111+
where: [
112+
{
113+
messageChannels: {
114+
syncStatus:
115+
MessageChannelSyncStatus.FAILED_INSUFFICIENT_PERMISSIONS,
116+
},
117+
},
118+
{
119+
calendarChannels: {
120+
syncStatus:
121+
CalendarChannelSyncStatus.FAILED_INSUFFICIENT_PERMISSIONS,
122+
},
123+
},
124+
],
125+
relations: {
126+
accountOwner: true,
127+
},
128+
});
129+
130+
for (const connectedAccount of connectedAccountsInFailedInsufficientPermissions) {
131+
try {
132+
await this.accountsToReconnectService.addAccountToReconnectByKey(
133+
AccountsToReconnectKeys.ACCOUNTS_TO_RECONNECT_INSUFFICIENT_PERMISSIONS,
134+
connectedAccount.accountOwner.userId,
135+
workspaceId,
136+
connectedAccount.id,
137+
);
138+
} catch (error) {
139+
this.logger.error(
140+
`Failed to add account to reconnect for workspace ${workspaceId}: ${error.message}`,
141+
);
142+
throw error;
143+
}
144+
}
145+
} catch (error) {
146+
this.logger.log(
147+
chalk.red(`Running command on workspace ${workspaceId} failed`),
148+
);
149+
throw error;
150+
}
151+
152+
await this.workspaceCacheVersionService.incrementVersion(workspaceId);
153+
154+
this.logger.log(
155+
chalk.green(`Running command on workspace ${workspaceId} done`),
156+
);
157+
} catch (error) {
158+
this.logger.error(
159+
`Migration failed for workspace ${workspaceId}: ${error.message}`,
160+
);
161+
}
162+
}
163+
164+
this.logger.log(chalk.green(`Command completed!`));
165+
}
166+
}

packages/twenty-server/src/database/commands/upgrade-version/0-23/0-23-set-workspace-activation-status.command.ts

-10
Original file line numberDiff line numberDiff line change
@@ -83,26 +83,16 @@ export class SetWorkspaceActivationStatusCommand extends CommandRunner {
8383
await this.typeORMService.connectToDataSource(dataSourceMetadata);
8484

8585
if (workspaceDataSource) {
86-
const queryRunner = workspaceDataSource.createQueryRunner();
87-
88-
await queryRunner.connect();
89-
await queryRunner.startTransaction();
90-
9186
try {
9287
await this.workspaceRepository.update(
9388
{ id: workspaceId },
9489
{ activationStatus: WorkspaceActivationStatus.ACTIVE },
9590
);
96-
97-
await queryRunner.commitTransaction();
9891
} catch (error) {
99-
await queryRunner.rollbackTransaction();
10092
this.logger.log(
10193
chalk.red(`Running command on workspace ${workspaceId} failed`),
10294
);
10395
throw error;
104-
} finally {
105-
await queryRunner.release();
10696
}
10797
}
10898
}

packages/twenty-server/src/database/commands/upgrade-version/0-23/0-23-upgrade-version.command.ts

+3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { BackfillNewOnboardingUserVarsCommand } from 'src/database/commands/upgr
44
import { MigrateDomainNameFromTextToLinksCommand } from 'src/database/commands/upgrade-version/0-23/0-23-migrate-domain-to-links.command';
55
import { MigrateLinkFieldsToLinksCommand } from 'src/database/commands/upgrade-version/0-23/0-23-migrate-link-fields-to-links.command';
66
import { MigrateMessageChannelSyncStatusEnumCommand } from 'src/database/commands/upgrade-version/0-23/0-23-migrate-message-channel-sync-status-enum.command';
7+
import { SetUserVarsAccountsToReconnectCommand } from 'src/database/commands/upgrade-version/0-23/0-23-set-user-vars-accounts-to-reconnect.command';
78
import { SetWorkspaceActivationStatusCommand } from 'src/database/commands/upgrade-version/0-23/0-23-set-workspace-activation-status.command';
89
import { UpdateActivitiesCommand } from 'src/database/commands/upgrade-version/0-23/0-23-update-activities.command';
910
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 {
2728
private readonly setWorkspaceActivationStatusCommand: SetWorkspaceActivationStatusCommand,
2829
private readonly updateActivitiesCommand: UpdateActivitiesCommand,
2930
private readonly backfillNewOnboardingUserVarsCommand: BackfillNewOnboardingUserVarsCommand,
31+
private readonly setUserVarsAccountsToReconnectCommand: SetUserVarsAccountsToReconnectCommand,
3032
) {
3133
super();
3234
}
@@ -62,5 +64,6 @@ export class UpgradeTo0_23Command extends CommandRunner {
6264
});
6365
await this.updateActivitiesCommand.run(_passedParam, options);
6466
await this.backfillNewOnboardingUserVarsCommand.run(_passedParam, options);
67+
await this.setUserVarsAccountsToReconnectCommand.run(_passedParam, options);
6568
}
6669
}

packages/twenty-server/src/database/commands/upgrade-version/0-23/0-23-upgrade-version.module.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ import { BackfillNewOnboardingUserVarsCommand } from 'src/database/commands/upgr
55
import { MigrateDomainNameFromTextToLinksCommand } from 'src/database/commands/upgrade-version/0-23/0-23-migrate-domain-to-links.command';
66
import { MigrateLinkFieldsToLinksCommand } from 'src/database/commands/upgrade-version/0-23/0-23-migrate-link-fields-to-links.command';
77
import { MigrateMessageChannelSyncStatusEnumCommand } from 'src/database/commands/upgrade-version/0-23/0-23-migrate-message-channel-sync-status-enum.command';
8+
import { SetUserVarsAccountsToReconnectCommand } from 'src/database/commands/upgrade-version/0-23/0-23-set-user-vars-accounts-to-reconnect.command';
89
import { SetWorkspaceActivationStatusCommand } from 'src/database/commands/upgrade-version/0-23/0-23-set-workspace-activation-status.command';
910
import { UpdateActivitiesCommand } from 'src/database/commands/upgrade-version/0-23/0-23-update-activities.command';
1011
import { UpdateFileFolderStructureCommand } from 'src/database/commands/upgrade-version/0-23/0-23-update-file-folder-structure.command';
1112
import { UpgradeTo0_23Command } from 'src/database/commands/upgrade-version/0-23/0-23-upgrade-version.command';
1213
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
1314
import { BillingModule } from 'src/engine/core-modules/billing/billing.module';
15+
import { KeyValuePair } from 'src/engine/core-modules/key-value-pair/key-value-pair.entity';
1416
import { OnboardingModule } from 'src/engine/core-modules/onboarding/onboarding.module';
1517
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
1618
import { FileStorageModule } from 'src/engine/integrations/file-storage/file-storage.module';
@@ -22,12 +24,13 @@ import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadat
2224
import { WorkspaceCacheVersionModule } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.module';
2325
import { WorkspaceStatusModule } from 'src/engine/workspace-manager/workspace-status/workspace-manager.module';
2426
import { WorkspaceSyncMetadataCommandsModule } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/workspace-sync-metadata-commands.module';
27+
import { ConnectedAccountModule } from 'src/modules/connected-account/connected-account.module';
2528
import { ViewModule } from 'src/modules/view/view.module';
2629

2730
@Module({
2831
imports: [
32+
TypeOrmModule.forFeature([Workspace, KeyValuePair], 'core'),
2933
WorkspaceSyncMetadataCommandsModule,
30-
TypeOrmModule.forFeature([Workspace], 'core'),
3134
FileStorageModule,
3235
OnboardingModule,
3336
TypeORMModule,
@@ -42,16 +45,17 @@ import { ViewModule } from 'src/modules/view/view.module';
4245
ViewModule,
4346
BillingModule,
4447
ObjectMetadataModule,
48+
ConnectedAccountModule,
4549
],
4650
providers: [
4751
UpdateFileFolderStructureCommand,
48-
UpgradeTo0_23Command,
4952
MigrateLinkFieldsToLinksCommand,
5053
MigrateDomainNameFromTextToLinksCommand,
5154
MigrateMessageChannelSyncStatusEnumCommand,
5255
SetWorkspaceActivationStatusCommand,
5356
UpdateActivitiesCommand,
5457
BackfillNewOnboardingUserVarsCommand,
58+
SetUserVarsAccountsToReconnectCommand,
5559
UpgradeTo0_23Command,
5660
],
5761
})

packages/twenty-server/src/modules/calendar/calendar-event-import-manager/calendar-event-import-manager.module.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { TypeOrmModule } from '@nestjs/typeorm';
33

44
import { BillingModule } from 'src/engine/core-modules/billing/billing.module';
55
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
6-
import { UserVarsModule } from 'src/engine/core-modules/user/user-vars/user-vars.module';
76
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
87
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
98
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
@@ -25,6 +24,7 @@ import { CalendarChannelEventAssociationWorkspaceEntity } from 'src/modules/cale
2524
import { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
2625
import { CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event-participant.workspace-entity';
2726
import { CalendarEventWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event.workspace-entity';
27+
import { ConnectedAccountModule } from 'src/modules/connected-account/connected-account.module';
2828
import { RefreshAccessTokenManagerModule } from 'src/modules/connected-account/refresh-access-token-manager/refresh-access-token-manager.module';
2929
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
3030
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
@@ -53,7 +53,7 @@ import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/sta
5353
BillingModule,
5454
RefreshAccessTokenManagerModule,
5555
CalendarEventParticipantManagerModule,
56-
UserVarsModule,
56+
ConnectedAccountModule,
5757
],
5858
providers: [
5959
CalendarChannelSyncStatusService,

packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-channel-sync-status.service.ts

+7-23
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { Injectable } from '@nestjs/common';
22

3-
import { UserVarsService } from 'src/engine/core-modules/user/user-vars/services/user-vars.service';
43
import { CacheStorageService } from 'src/engine/integrations/cache-storage/cache-storage.service';
54
import { InjectCacheStorage } from 'src/engine/integrations/cache-storage/decorators/cache-storage.decorator';
65
import { CacheStorageNamespace } from 'src/engine/integrations/cache-storage/types/cache-storage-namespace.enum';
@@ -10,18 +9,16 @@ import {
109
CalendarChannelSyncStatus,
1110
CalendarChannelWorkspaceEntity,
1211
} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
13-
import {
14-
AccountsToReconnectKeyValueType,
15-
AccountsToReconnectKeys,
16-
} from 'src/modules/connected-account/types/accounts-to-reconnect-key-value.type';
12+
import { AccountsToReconnectService } from 'src/modules/connected-account/services/accounts-to-reconnect.service';
13+
import { AccountsToReconnectKeys } from 'src/modules/connected-account/types/accounts-to-reconnect-key-value.type';
1714

1815
@Injectable()
1916
export class CalendarChannelSyncStatusService {
2017
constructor(
2118
private readonly twentyORMManager: TwentyORMManager,
2219
@InjectCacheStorage(CacheStorageNamespace.Calendar)
2320
private readonly cacheStorage: CacheStorageService,
24-
private readonly userVarsService: UserVarsService<AccountsToReconnectKeyValueType>,
21+
private readonly accountsToReconnectService: AccountsToReconnectService,
2522
) {}
2623

2724
public async scheduleFullCalendarEventListFetch(calendarChannelId: string) {
@@ -194,24 +191,11 @@ export class CalendarChannelSyncStatusService {
194191
const userId = calendarChannel.connectedAccount.accountOwner.userId;
195192
const connectedAccountId = calendarChannel.connectedAccount.id;
196193

197-
const accountsToReconnect =
198-
(await this.userVarsService.get({
199-
userId,
200-
workspaceId,
201-
key: AccountsToReconnectKeys.ACCOUNTS_TO_RECONNECT_INSUFFICIENT_PERMISSIONS,
202-
})) ?? [];
203-
204-
if (accountsToReconnect.includes(connectedAccountId)) {
205-
return;
206-
}
207-
208-
accountsToReconnect.push(connectedAccountId);
209-
210-
await this.userVarsService.set({
194+
await this.accountsToReconnectService.addAccountToReconnectByKey(
195+
AccountsToReconnectKeys.ACCOUNTS_TO_RECONNECT_INSUFFICIENT_PERMISSIONS,
211196
userId,
212197
workspaceId,
213-
key: AccountsToReconnectKeys.ACCOUNTS_TO_RECONNECT_INSUFFICIENT_PERMISSIONS,
214-
value: accountsToReconnect,
215-
});
198+
connectedAccountId,
199+
);
216200
}
217201
}

packages/twenty-server/src/modules/connected-account/services/accounts-to-reconnect.service.ts

+27
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,31 @@ export class AccountsToReconnectService {
6464
value: updatedAccountsToReconnect,
6565
});
6666
}
67+
68+
public async addAccountToReconnectByKey(
69+
key: AccountsToReconnectKeys,
70+
userId: string,
71+
workspaceId: string,
72+
connectedAccountId: string,
73+
) {
74+
const accountsToReconnect =
75+
(await this.userVarsService.get({
76+
userId,
77+
workspaceId,
78+
key,
79+
})) ?? [];
80+
81+
if (accountsToReconnect.includes(connectedAccountId)) {
82+
return;
83+
}
84+
85+
accountsToReconnect.push(connectedAccountId);
86+
87+
await this.userVarsService.set({
88+
userId,
89+
workspaceId,
90+
key,
91+
value: accountsToReconnect,
92+
});
93+
}
6794
}

0 commit comments

Comments
 (0)