Skip to content

Commit 5ad287b

Browse files
Add option to synchronize all active workspaces at once (#6221)
In the longer term, we want to improve the efficiency and reliability of the sync-metadata command, by choosing an error handling strategy and paying greater attention to health checks. In the meantime, this PR adds an option to run the sync-metadata command on all active workspaces at once. --------- Co-authored-by: Charles Bochet <[email protected]>
1 parent 5cb7f68 commit 5ad287b

11 files changed

+192
-109
lines changed

packages/twenty-server/src/database/commands/0-22-add-new-address-field-to-views-with-deprecated-address.command.ts

+4-57
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,11 @@ import { Command, CommandRunner, Option } from 'nest-commander';
77
import { Repository } from 'typeorm';
88

99
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
10-
import {
11-
BillingSubscription,
12-
SubscriptionStatus,
13-
} from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
14-
import {
15-
FeatureFlagEntity,
16-
FeatureFlagKeys,
17-
} from 'src/engine/core-modules/feature-flag/feature-flag.entity';
18-
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
1910
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
2011
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
2112
import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service';
2213
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
14+
import { WorkspaceStatusService } from 'src/engine/workspace-manager/workspace-status/services/workspace-status.service';
2315
import { COMPANY_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
2416
import { ViewFieldWorkspaceEntity } from 'src/modules/view/standard-objects/view-field.workspace-entity';
2517

@@ -36,18 +28,13 @@ export class AddNewAddressFieldToViewsWithDeprecatedAddressFieldCommand extends
3628
AddNewAddressFieldToViewsWithDeprecatedAddressFieldCommand.name,
3729
);
3830
constructor(
39-
@InjectRepository(Workspace, 'core')
40-
private readonly workspaceRepository: Repository<Workspace>,
41-
@InjectRepository(BillingSubscription, 'core')
42-
private readonly billingSubscriptionRepository: Repository<BillingSubscription>,
43-
@InjectRepository(FeatureFlagEntity, 'core')
44-
private readonly featureFlagRepository: Repository<FeatureFlagEntity>,
4531
@InjectRepository(FieldMetadataEntity, 'metadata')
4632
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
4733
private readonly typeORMService: TypeORMService,
4834
private readonly dataSourceService: DataSourceService,
4935
private readonly workspaceCacheVersionService: WorkspaceCacheVersionService,
5036
private readonly twentyORMManager: TwentyORMManager,
37+
private readonly workspaceStatusService: WorkspaceStatusService,
5138
) {
5239
super();
5340
}
@@ -76,19 +63,8 @@ export class AddNewAddressFieldToViewsWithDeprecatedAddressFieldCommand extends
7663
if (options.workspaceId) {
7764
workspaceIds = [options.workspaceId];
7865
} else {
79-
const workspaces = await this.workspaceRepository.find();
80-
81-
const activeWorkspaceIds = (
82-
await Promise.all(
83-
workspaces.map(async (workspace) => {
84-
const isActive = await this.workspaceIsActive(workspace);
85-
86-
return { workspace, isActive };
87-
}),
88-
)
89-
)
90-
.filter((result) => result.isActive)
91-
.map((result) => result.workspace.id);
66+
const activeWorkspaceIds =
67+
await this.workspaceStatusService.getActiveWorkspaceIds();
9268

9369
workspaceIds = activeWorkspaceIds;
9470
}
@@ -198,33 +174,4 @@ export class AddNewAddressFieldToViewsWithDeprecatedAddressFieldCommand extends
198174

199175
this.logger.log(chalk.green(`Command completed!`));
200176
}
201-
202-
private async workspaceIsActive(workspace: Workspace): Promise<boolean> {
203-
const billingSupscriptionForWorkspace =
204-
await this.billingSubscriptionRepository.findOne({
205-
where: { workspaceId: workspace.id },
206-
});
207-
208-
if (
209-
billingSupscriptionForWorkspace?.status &&
210-
[
211-
SubscriptionStatus.PastDue,
212-
SubscriptionStatus.Active,
213-
SubscriptionStatus.Trialing,
214-
].includes(billingSupscriptionForWorkspace.status as SubscriptionStatus)
215-
) {
216-
return true;
217-
}
218-
219-
const freeAccessEnabledFeatureFlagForWorkspace =
220-
await this.featureFlagRepository.findOne({
221-
where: {
222-
workspaceId: workspace.id,
223-
key: FeatureFlagKeys.IsFreeAccessEnabled,
224-
value: true,
225-
},
226-
});
227-
228-
return !!freeAccessEnabledFeatureFlagForWorkspace;
229-
}
230177
}

packages/twenty-server/src/database/commands/database-command.module.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadat
2424
import { WorkspaceCacheVersionModule } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.module';
2525
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
2626
import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-manager.module';
27+
import { WorkspaceStatusModule } from 'src/engine/workspace-manager/workspace-status/workspace-manager.module';
2728
import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.module';
2829

2930
@Module({
@@ -42,10 +43,10 @@ import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/worksp
4243
WorkspaceModule,
4344
WorkspaceDataSourceModule,
4445
WorkspaceSyncMetadataModule,
46+
WorkspaceStatusModule,
4547
ObjectMetadataModule,
4648
DataSeedDemoWorkspaceModule,
4749
WorkspaceCacheVersionModule,
48-
4950
// Upgrades
5051
UpgradeTo0_22CommandModule,
5152
],

packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
1-
import { InjectRepository } from '@nestjs/typeorm';
21
import { BadRequestException } from '@nestjs/common';
2+
import { InjectRepository } from '@nestjs/typeorm';
33

44
import assert from 'assert';
55

66
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
7-
import { Repository } from 'typeorm';
8-
import { SendInviteLinkEmail } from 'twenty-emails';
97
import { render } from '@react-email/render';
8+
import { SendInviteLinkEmail } from 'twenty-emails';
9+
import { Repository } from 'typeorm';
1010

11-
import { WorkspaceManagerService } from 'src/engine/workspace-manager/workspace-manager.service';
12-
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
13-
import { User } from 'src/engine/core-modules/user/user.entity';
14-
import { ActivateWorkspaceInput } from 'src/engine/core-modules/workspace/dtos/activate-workspace-input';
11+
import { BillingWorkspaceService } from 'src/engine/core-modules/billing/billing.workspace-service';
12+
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
1513
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
1614
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
17-
import { BillingWorkspaceService } from 'src/engine/core-modules/billing/billing.workspace-service';
15+
import { User } from 'src/engine/core-modules/user/user.entity';
16+
import { ActivateWorkspaceInput } from 'src/engine/core-modules/workspace/dtos/activate-workspace-input';
1817
import { SendInviteLink } from 'src/engine/core-modules/workspace/dtos/send-invite-link.entity';
18+
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
1919
import { EmailService } from 'src/engine/integrations/email/email.service';
2020
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
21-
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
21+
import { WorkspaceManagerService } from 'src/engine/workspace-manager/workspace-manager.service';
2222

2323
export class WorkspaceService extends TypeOrmQueryService<Workspace> {
2424
constructor(

packages/twenty-server/src/engine/core-modules/workspace/workspace.module.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,20 @@ import { Module } from '@nestjs/common';
33
import { NestjsQueryGraphQLModule } from '@ptc-org/nestjs-query-graphql';
44
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
55

6-
import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-manager.module';
7-
import { WorkspaceResolver } from 'src/engine/core-modules/workspace/workspace.resolver';
86
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
7+
import { BillingModule } from 'src/engine/core-modules/billing/billing.module';
98
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
9+
import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module';
10+
import { OnboardingModule } from 'src/engine/core-modules/onboarding/onboarding.module';
1011
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
1112
import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module';
12-
import { BillingModule } from 'src/engine/core-modules/billing/billing.module';
13-
import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module';
14-
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
13+
import { UserWorkspaceResolver } from 'src/engine/core-modules/user-workspace/user-workspace.resolver';
14+
import { User } from 'src/engine/core-modules/user/user.entity';
1515
import { WorkspaceWorkspaceMemberListener } from 'src/engine/core-modules/workspace/workspace-workspace-member.listener';
16+
import { WorkspaceResolver } from 'src/engine/core-modules/workspace/workspace.resolver';
17+
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
1618
import { WorkspaceCacheVersionModule } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.module';
17-
import { User } from 'src/engine/core-modules/user/user.entity';
18-
import { UserWorkspaceResolver } from 'src/engine/core-modules/user-workspace/user-workspace.resolver';
19-
import { OnboardingModule } from 'src/engine/core-modules/onboarding/onboarding.module';
19+
import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-manager.module';
2020

2121
import { workspaceAutoResolverOpts } from './workspace.auto-resolver-opts';
2222
import { Workspace } from './workspace.entity';

packages/twenty-server/src/engine/workspace-manager/workspace-manager.module.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-s
44
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
55
import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module';
66
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
7-
import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.module';
87
import { WorkspaceHealthModule } from 'src/engine/workspace-manager/workspace-health/workspace-health.module';
8+
import { WorkspaceStatusModule } from 'src/engine/workspace-manager/workspace-status/workspace-manager.module';
9+
import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.module';
910

1011
import { WorkspaceManagerService } from './workspace-manager.service';
1112

@@ -17,6 +18,7 @@ import { WorkspaceManagerService } from './workspace-manager.service';
1718
DataSourceModule,
1819
WorkspaceSyncMetadataModule,
1920
WorkspaceHealthModule,
21+
WorkspaceStatusModule,
2022
],
2123
exports: [WorkspaceManagerService],
2224
providers: [WorkspaceManagerService],

packages/twenty-server/src/engine/workspace-manager/workspace-manager.service.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { Injectable } from '@nestjs/common';
22

3+
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
34
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
45
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
56
import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service';
6-
import { standardObjectsPrefillData } from 'src/engine/workspace-manager/standard-objects-prefill-data/standard-objects-prefill-data';
7-
import { demoObjectsPrefillData } from 'src/engine/workspace-manager/demo-objects-prefill-data/demo-objects-prefill-data';
87
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
9-
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
8+
import { demoObjectsPrefillData } from 'src/engine/workspace-manager/demo-objects-prefill-data/demo-objects-prefill-data';
9+
import { standardObjectsPrefillData } from 'src/engine/workspace-manager/standard-objects-prefill-data/standard-objects-prefill-data';
1010
import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service';
1111

1212
@Injectable()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { Injectable } from '@nestjs/common';
2+
import { InjectRepository } from '@nestjs/typeorm';
3+
4+
import { Any, Repository } from 'typeorm';
5+
6+
import {
7+
BillingSubscription,
8+
SubscriptionStatus,
9+
} from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
10+
import {
11+
FeatureFlagEntity,
12+
FeatureFlagKeys,
13+
} from 'src/engine/core-modules/feature-flag/feature-flag.entity';
14+
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
15+
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
16+
17+
@Injectable()
18+
export class WorkspaceStatusService {
19+
constructor(
20+
private readonly environmentService: EnvironmentService,
21+
@InjectRepository(Workspace, 'core')
22+
private readonly workspaceRepository: Repository<Workspace>,
23+
@InjectRepository(BillingSubscription, 'core')
24+
private readonly billingSubscriptionRepository: Repository<BillingSubscription>,
25+
@InjectRepository(FeatureFlagEntity, 'core')
26+
private readonly featureFlagRepository: Repository<FeatureFlagEntity>,
27+
) {}
28+
29+
async getActiveWorkspaceIds(): Promise<string[]> {
30+
const workspaces = await this.workspaceRepository.find();
31+
const workspaceIds = workspaces.map((workspace) => workspace.id);
32+
33+
if (!this.environmentService.get('IS_BILLING_ENABLED')) {
34+
return workspaceIds;
35+
}
36+
37+
const billingSubscriptionForWorkspaces =
38+
await this.billingSubscriptionRepository.find({
39+
where: {
40+
workspaceId: Any(workspaceIds),
41+
status: Any([
42+
SubscriptionStatus.PastDue,
43+
SubscriptionStatus.Active,
44+
SubscriptionStatus.Trialing,
45+
]),
46+
},
47+
});
48+
49+
const workspaceIdsWithActiveSubscription =
50+
billingSubscriptionForWorkspaces.map(
51+
(billingSubscription) => billingSubscription.workspaceId,
52+
);
53+
54+
const freeAccessEnabledFeatureFlagForWorkspace =
55+
await this.featureFlagRepository.find({
56+
where: {
57+
workspaceId: Any(workspaceIds),
58+
key: FeatureFlagKeys.IsFreeAccessEnabled,
59+
value: true,
60+
},
61+
});
62+
63+
const workspaceIdsWithFreeAccessEnabled =
64+
freeAccessEnabledFeatureFlagForWorkspace.map(
65+
(featureFlag) => featureFlag.workspaceId,
66+
);
67+
68+
return workspaceIds.filter(
69+
(workspaceId) =>
70+
workspaceIdsWithActiveSubscription.includes(workspaceId) ||
71+
workspaceIdsWithFreeAccessEnabled.includes(workspaceId),
72+
);
73+
}
74+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Module } from '@nestjs/common';
2+
import { TypeOrmModule } from '@nestjs/typeorm';
3+
4+
import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
5+
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
6+
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
7+
import { EnvironmentModule } from 'src/engine/integrations/environment/environment.module';
8+
import { WorkspaceStatusService } from 'src/engine/workspace-manager/workspace-status/services/workspace-status.service';
9+
10+
@Module({
11+
imports: [
12+
EnvironmentModule,
13+
TypeOrmModule.forFeature(
14+
[Workspace, BillingSubscription, FeatureFlagEntity],
15+
'core',
16+
),
17+
],
18+
exports: [WorkspaceStatusService],
19+
providers: [WorkspaceStatusService],
20+
})
21+
export class WorkspaceStatusModule {}

0 commit comments

Comments
 (0)