Metadata api create entity in workspace custom app (#15911)
# Introduction Cleaner and fewer scope version of https://github.com/twentyhq/twenty/pull/15745 ( removed sync-metadata hack through, too ambitious migration and upgrade ) Please note that this PR won't have any interaction with the existing sync-metadata Which mean that the sync metadata does not update the standard entities applicationId and universalIdentifier, and it won't we will deprecate it on favor of a workspace migration aka twenty-standard app installation ## API Metadata Any operation going through the api metadata nows automatically scope the related entity to the workspace custom application instance. ( optionally passing an applicationId to allow current hacky implem of app sync service ) We need to either ignore the tests or remove the cli status check from the blocking status badges for a PR to be merged ## New workspace Already handled in previous https://github.com/twentyhq/twenty/pull/15625, when a workspace is created it gets created a twenty standard and custom workspace instance All his views and permissions will be prefilled to the its twenty standard app instance with a specific universalIdentifier ## New universalIdentifier At the contrary as before with standardIds, universalIdentifier are unique for a given workspace This means that createdAt field of both object company and opportunity will have a unique universalIdentifier whereas they share the same standardId ## FlatApplication Introduced the flatApplication and cache. Will migrate existing `MetadataName` to be `SyncableMetadataName` in a following PR ## What's next Next we will describe a twenty standard app configuration as json that will be used to generate a workspace migration that will be run instead of the sync metadata, in a nutshell we aim to deprecated the sync metadata So we can standardize any entity to have a non nullable applicationId and universalIdentifier ## Upgrade command Introduced an upgrade command that will create a custom workspace instance for any workspace that do not have one in order to align with the new behavior when creating a new workspace
This commit is contained in:
+10
-1
@@ -4,6 +4,7 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { FieldPermissionService } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.service';
|
||||
@@ -41,7 +42,13 @@ export class DevSeederPermissionsService {
|
||||
private readonly coreDataSource: DataSource,
|
||||
) {}
|
||||
|
||||
public async initPermissions(workspaceId: string) {
|
||||
public async initPermissions({
|
||||
twentyStandardApplication,
|
||||
workspaceId,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
twentyStandardApplication: ApplicationEntity;
|
||||
}) {
|
||||
const adminRole = await this.roleRepository.findOne({
|
||||
where: {
|
||||
standardId: ADMIN_ROLE.standardId,
|
||||
@@ -100,6 +107,7 @@ export class DevSeederPermissionsService {
|
||||
|
||||
const guestRole = await this.roleService.createGuestRole({
|
||||
workspaceId,
|
||||
applicationId: twentyStandardApplication.id,
|
||||
});
|
||||
|
||||
await this.userRoleService.assignRoleToUserWorkspace({
|
||||
@@ -135,6 +143,7 @@ export class DevSeederPermissionsService {
|
||||
|
||||
const memberRole = await this.roleService.createMemberRole({
|
||||
workspaceId,
|
||||
applicationId: twentyStandardApplication.id,
|
||||
});
|
||||
|
||||
await this.coreDataSource
|
||||
|
||||
+9
-16
@@ -13,7 +13,6 @@ import { seedFeatureFlags } from 'src/engine/workspace-manager/dev-seeder/core/u
|
||||
import { seedUserWorkspaces } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-user-workspaces.util';
|
||||
import { seedUsers } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-users.util';
|
||||
import { createWorkspace } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-workspace.util';
|
||||
import { computeWorkspaceCustomCreateApplicationInput } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/compute-workspace-custom-create-application-input';
|
||||
import { extractVersionMajorMinorPatch } from 'src/utils/version/extract-version-major-minor-patch';
|
||||
|
||||
type SeedCoreSchemaArgs = {
|
||||
@@ -36,14 +35,6 @@ export const seedCoreSchema = async ({
|
||||
const schemaName = 'core';
|
||||
|
||||
const createWorkspaceStaticInput = SEEDER_CREATE_WORKSPACE_INPUT[workspaceId];
|
||||
const workspaceCustomApplicationCreateInput =
|
||||
computeWorkspaceCustomCreateApplicationInput({
|
||||
workspace: {
|
||||
id: workspaceId,
|
||||
displayName: createWorkspaceStaticInput.displayName,
|
||||
},
|
||||
});
|
||||
|
||||
const version = extractVersionMajorMinorPatch(appVersion);
|
||||
const queryRunner = dataSource.createQueryRunner();
|
||||
|
||||
@@ -51,13 +42,14 @@ export const seedCoreSchema = async ({
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
const customWorkspaceApplication = await applicationService.create(
|
||||
{
|
||||
...workspaceCustomApplicationCreateInput,
|
||||
serverlessFunctionLayerId: null,
|
||||
},
|
||||
queryRunner,
|
||||
);
|
||||
const customWorkspaceApplication =
|
||||
await applicationService.createWorkspaceCustomApplication(
|
||||
{
|
||||
workspaceId,
|
||||
workspaceDisplayName: createWorkspaceStaticInput.displayName,
|
||||
},
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
await createWorkspace({
|
||||
queryRunner,
|
||||
@@ -76,6 +68,7 @@ export const seedCoreSchema = async ({
|
||||
await applicationService.createTwentyStandardApplication(
|
||||
{
|
||||
workspaceId,
|
||||
skipCacheInvalidation: true,
|
||||
},
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
+8
@@ -4,6 +4,7 @@ import { InjectDataSource } from '@nestjs/typeorm';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
import { FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import { type DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
||||
import { CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
|
||||
import { FieldMetadataServiceV2 } from 'src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2';
|
||||
@@ -85,9 +86,11 @@ export class DevSeederMetadataService {
|
||||
dataSourceMetadata,
|
||||
workspaceId,
|
||||
featureFlags,
|
||||
twentyStandardFlatApplication,
|
||||
}: {
|
||||
dataSourceMetadata: DataSourceEntity;
|
||||
workspaceId: string;
|
||||
twentyStandardFlatApplication: FlatApplication;
|
||||
featureFlags?: Record<string, boolean>;
|
||||
}) {
|
||||
const config = this.workspaceConfigs[workspaceId];
|
||||
@@ -98,6 +101,7 @@ export class DevSeederMetadataService {
|
||||
);
|
||||
}
|
||||
|
||||
// TODO get
|
||||
for (const obj of config.objects) {
|
||||
await this.seedCustomObject({
|
||||
dataSourceId: dataSourceMetadata.id,
|
||||
@@ -126,6 +130,7 @@ export class DevSeederMetadataService {
|
||||
workspaceId,
|
||||
dataSourceMetadata,
|
||||
featureFlags,
|
||||
twentyStandardFlatApplication,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -181,10 +186,12 @@ export class DevSeederMetadataService {
|
||||
workspaceId,
|
||||
dataSourceMetadata,
|
||||
featureFlags,
|
||||
twentyStandardFlatApplication,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
dataSourceMetadata: DataSourceEntity;
|
||||
featureFlags?: Record<string, boolean>;
|
||||
twentyStandardFlatApplication: FlatApplication;
|
||||
}): Promise<void> {
|
||||
const createdObjectMetadata =
|
||||
await this.objectMetadataServiceV2.findManyWithinWorkspace(workspaceId);
|
||||
@@ -195,6 +202,7 @@ export class DevSeederMetadataService {
|
||||
objectMetadataItems: createdObjectMetadata,
|
||||
workspaceSchemaName: dataSourceMetadata.schema,
|
||||
featureFlags,
|
||||
twentyStandardFlatApplication,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+19
-2
@@ -5,6 +5,8 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/core-modules/application/constants/twenty-standard-applications';
|
||||
import { WorkspaceFlatApplicationMapCacheService } from 'src/engine/core-modules/application/services/workspace-flat-application-map-cache.service';
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
@@ -21,7 +23,6 @@ import { seedPageLayoutWidgets } from 'src/engine/workspace-manager/dev-seeder/c
|
||||
import { seedPageLayouts } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-page-layouts.util';
|
||||
import { DevSeederDataService } from 'src/engine/workspace-manager/dev-seeder/data/services/dev-seeder-data.service';
|
||||
import { DevSeederMetadataService } from 'src/engine/workspace-manager/dev-seeder/metadata/services/dev-seeder-metadata.service';
|
||||
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/twenty-standard-applications';
|
||||
import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service';
|
||||
|
||||
@Injectable()
|
||||
@@ -38,6 +39,7 @@ export class DevSeederService {
|
||||
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
private readonly devSeederDataService: DevSeederDataService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly workspaceFlatApplicationMapCacheService: WorkspaceFlatApplicationMapCacheService,
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
) {}
|
||||
@@ -59,6 +61,10 @@ export class DevSeederService {
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
await this.workspaceFlatApplicationMapCacheService.invalidateCache({
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const dataSourceMetadata =
|
||||
await this.dataSourceService.createDataSourceMetadata(
|
||||
workspaceId,
|
||||
@@ -80,6 +86,13 @@ export class DevSeederService {
|
||||
);
|
||||
}
|
||||
|
||||
const { twentyStandardFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
await this.workspaceSyncMetadataService.synchronize({
|
||||
workspaceId: workspaceId,
|
||||
dataSourceId: dataSourceMetadata.id,
|
||||
@@ -90,13 +103,17 @@ export class DevSeederService {
|
||||
dataSourceMetadata,
|
||||
workspaceId,
|
||||
featureFlags,
|
||||
twentyStandardFlatApplication,
|
||||
});
|
||||
|
||||
await this.devSeederMetadataService.seedRelations({
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
await this.devSeederPermissionsService.initPermissions(workspaceId);
|
||||
await this.devSeederPermissionsService.initPermissions({
|
||||
workspaceId,
|
||||
twentyStandardApplication,
|
||||
});
|
||||
|
||||
await seedPageLayouts(this.coreDataSource, 'core', workspaceId);
|
||||
await seedPageLayoutTabs(this.coreDataSource, 'core', workspaceId);
|
||||
|
||||
Reference in New Issue
Block a user