From 22fec0eb3289ba63fb6e3a371ab7d2b9f9388534 Mon Sep 17 00:00:00 2001 From: Weiko Date: Fri, 31 Jan 2025 17:11:34 +0100 Subject: [PATCH 1/3] add createmany fields --- .../data-seed-dev-workspace.command.ts | 17 +- .../field-metadata/field-metadata.service.ts | 426 ++++++++++-------- .../src/engine/seeder/seeder.service.ts | 8 +- 3 files changed, 251 insertions(+), 200 deletions(-) diff --git a/packages/twenty-server/src/database/commands/data-seed-dev-workspace.command.ts b/packages/twenty-server/src/database/commands/data-seed-dev-workspace.command.ts index 34cc58d127da..dc435894a6ca 100644 --- a/packages/twenty-server/src/database/commands/data-seed-dev-workspace.command.ts +++ b/packages/twenty-server/src/database/commands/data-seed-dev-workspace.command.ts @@ -265,13 +265,12 @@ export class DataSeedWorkspaceCommand extends CommandRunner { workspaceId, ); - for (const customField of DEV_SEED_COMPANY_CUSTOM_FIELDS) { - // TODO: Use createMany once implemented for better performances - await this.fieldMetadataService.createOne({ + await this.fieldMetadataService.createMany( + DEV_SEED_COMPANY_CUSTOM_FIELDS.map((customField) => ({ ...customField, isCustom: true, - }); - } + })), + ); } async seedPeopleCustomFields( @@ -289,11 +288,11 @@ export class DataSeedWorkspaceCommand extends CommandRunner { workspaceId, ); - for (const customField of DEV_SEED_PERSON_CUSTOM_FIELDS) { - await this.fieldMetadataService.createOne({ + await this.fieldMetadataService.createMany( + DEV_SEED_PERSON_CUSTOM_FIELDS.map((customField) => ({ ...customField, isCustom: true, - }); - } + })), + ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts index c5ee2eeba209..b1845aa5fcf0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts @@ -5,7 +5,7 @@ import { i18n } from '@lingui/core'; import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm'; import isEmpty from 'lodash.isempty'; import { FieldMetadataType } from 'twenty-shared'; -import { DataSource, FindOneOptions, Repository } from 'typeorm'; +import { DataSource, FindOneOptions, In, Repository } from 'typeorm'; import { v4 as uuidV4, v4 } from 'uuid'; import { TypeORMService } from 'src/database/typeorm/typeorm.service'; @@ -96,195 +96,16 @@ export class FieldMetadataService extends TypeOrmQueryService { - const queryRunner = this.metadataDataSource.createQueryRunner(); - - await queryRunner.connect(); - await queryRunner.startTransaction(); - - try { - const fieldMetadataRepository = - queryRunner.manager.getRepository( - FieldMetadataEntity, - ); - - const [objectMetadata] = await this.objectMetadataRepository.find({ - where: { - id: fieldMetadataInput.objectMetadataId, - workspaceId: fieldMetadataInput.workspaceId, - }, - relations: ['fields'], - order: {}, - }); - - if (!objectMetadata) { - throw new FieldMetadataException( - 'Object metadata does not exist', - FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND, - ); - } - - if (!fieldMetadataInput.isRemoteCreation) { - assertMutationNotOnRemoteObject(objectMetadata); - } - - // Double check in case the service is directly called - if (isEnumFieldMetadataType(fieldMetadataInput.type)) { - if ( - !fieldMetadataInput.options && - fieldMetadataInput.type !== FieldMetadataType.RATING - ) { - throw new FieldMetadataException( - 'Options are required for enum fields', - FieldMetadataExceptionCode.INVALID_FIELD_INPUT, - ); - } - } - - // Generate options for rating fields - if (fieldMetadataInput.type === FieldMetadataType.RATING) { - fieldMetadataInput.options = generateRatingOptions(); - } - - const fieldMetadataForCreate = { - id: v4(), - createdAt: new Date(), - updatedAt: new Date(), - ...fieldMetadataInput, - isNullable: generateNullable( - fieldMetadataInput.type, - fieldMetadataInput.isNullable, - fieldMetadataInput.isRemoteCreation, - ), - defaultValue: - fieldMetadataInput.defaultValue ?? - generateDefaultValue(fieldMetadataInput.type), - options: fieldMetadataInput.options - ? fieldMetadataInput.options.map((option) => ({ - ...option, - id: uuidV4(), - })) - : undefined, - isActive: true, - isCustom: true, - }; - - this.validateFieldMetadata( - fieldMetadataForCreate.type, - fieldMetadataForCreate, - objectMetadata, - ); - - if (fieldMetadataForCreate.isLabelSyncedWithName === true) { - validateNameAndLabelAreSyncOrThrow( - fieldMetadataForCreate.label, - fieldMetadataForCreate.name, - ); - } + const [createdFieldMetadata] = await this.createMany([fieldMetadataInput]); - const createdFieldMetadata = await fieldMetadataRepository.save( - fieldMetadataForCreate, - ); - - if (!fieldMetadataInput.isRemoteCreation) { - await this.workspaceMigrationService.createCustomMigration( - generateMigrationName(`create-${createdFieldMetadata.name}`), - fieldMetadataInput.workspaceId, - [ - { - name: computeObjectTargetTable(objectMetadata), - action: WorkspaceMigrationTableActionType.ALTER, - columns: this.workspaceMigrationFactory.createColumnActions( - WorkspaceMigrationColumnActionType.CREATE, - createdFieldMetadata, - ), - } satisfies WorkspaceMigrationTableAction, - ], - ); - - await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations( - fieldMetadataInput.workspaceId, - ); - } - - // TODO: Move viewField creation to a cdc scheduler - const dataSourceMetadata = - await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail( - fieldMetadataInput.workspaceId, - ); - - const workspaceDataSource = - await this.typeORMService.connectToDataSource(dataSourceMetadata); - - const workspaceQueryRunner = workspaceDataSource?.createQueryRunner(); - - if (!workspaceQueryRunner) { - throw new FieldMetadataException( - 'Could not create workspace query runner', - FieldMetadataExceptionCode.INTERNAL_SERVER_ERROR, - ); - } - - await workspaceQueryRunner.connect(); - await workspaceQueryRunner.startTransaction(); - - try { - // TODO: use typeorm repository - const view = await workspaceQueryRunner?.query( - `SELECT id FROM ${dataSourceMetadata.schema}."view" - WHERE "objectMetadataId" = '${createdFieldMetadata.objectMetadataId}'`, - ); - - if (!isEmpty(view)) { - const existingViewFields = (await workspaceQueryRunner?.query( - `SELECT * FROM ${dataSourceMetadata.schema}."viewField" - WHERE "viewId" = '${view[0].id}'`, - )) as ViewFieldWorkspaceEntity[]; - - const createdFieldIsAlreadyInView = existingViewFields.some( - (existingViewField) => - existingViewField.fieldMetadataId === createdFieldMetadata.id, - ); - - if (!createdFieldIsAlreadyInView) { - const lastPosition = existingViewFields - .map((viewField) => viewField.position) - .reduce((acc, position) => { - if (position > acc) { - return position; - } - - return acc; - }, -1); - - await workspaceQueryRunner?.query( - `INSERT INTO ${dataSourceMetadata.schema}."viewField" - ("fieldMetadataId", "position", "isVisible", "size", "viewId") - VALUES ('${createdFieldMetadata.id}', '${lastPosition + 1}', true, 180, '${ - view[0].id - }')`, - ); - } - } - await workspaceQueryRunner.commitTransaction(); - } catch (error) { - await workspaceQueryRunner.rollbackTransaction(); - throw error; - } finally { - await workspaceQueryRunner.release(); - } - - await queryRunner.commitTransaction(); - - return createdFieldMetadata; - } catch (error) { - await queryRunner.rollbackTransaction(); - throw error; - } finally { - await queryRunner.release(); - await this.workspaceMetadataVersionService.incrementMetadataVersion( - fieldMetadataInput.workspaceId, + if (!createdFieldMetadata) { + throw new FieldMetadataException( + 'Failed to create field metadata', + FieldMetadataExceptionCode.INTERNAL_SERVER_ERROR, ); } + + return createdFieldMetadata; } override async updateOne( @@ -800,4 +621,235 @@ export class FieldMetadataService extends TypeOrmQueryService { + if (!fieldMetadataInputs.length) { + return []; + } + + const queryRunner = this.metadataDataSource.createQueryRunner(); + + await queryRunner.connect(); + await queryRunner.startTransaction(); + + try { + const fieldMetadataRepository = + queryRunner.manager.getRepository( + FieldMetadataEntity, + ); + + const inputsByObjectId = fieldMetadataInputs.reduce( + (acc, input) => { + if (!acc[input.objectMetadataId]) { + acc[input.objectMetadataId] = []; + } + acc[input.objectMetadataId].push(input); + + return acc; + }, + {} as Record, + ); + + const objectMetadataIds = Object.keys(inputsByObjectId); + const objectMetadatas = await this.objectMetadataRepository.find({ + where: { + id: In(objectMetadataIds), + workspaceId: fieldMetadataInputs[0].workspaceId, + }, + relations: ['fields'], + }); + + const objectMetadataMap = objectMetadatas.reduce( + (acc, obj) => { + acc[obj.id] = obj; + + return acc; + }, + {} as Record, + ); + + const createdFieldMetadatas: FieldMetadataEntity[] = []; + const migrationActions: WorkspaceMigrationTableAction[] = []; + + for (const objectMetadataId of objectMetadataIds) { + const objectMetadata = objectMetadataMap[objectMetadataId]; + const inputs = inputsByObjectId[objectMetadataId]; + + if (!objectMetadata) { + throw new FieldMetadataException( + 'Object metadata does not exist', + FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND, + ); + } + + for (const fieldMetadataInput of inputs) { + if (!fieldMetadataInput.isRemoteCreation) { + assertMutationNotOnRemoteObject(objectMetadata); + } + + if (isEnumFieldMetadataType(fieldMetadataInput.type)) { + if ( + !fieldMetadataInput.options && + fieldMetadataInput.type !== FieldMetadataType.RATING + ) { + throw new FieldMetadataException( + 'Options are required for enum fields', + FieldMetadataExceptionCode.INVALID_FIELD_INPUT, + ); + } + } + + if (fieldMetadataInput.type === FieldMetadataType.RATING) { + fieldMetadataInput.options = generateRatingOptions(); + } + + const fieldMetadataForCreate = { + id: v4(), + createdAt: new Date(), + updatedAt: new Date(), + ...fieldMetadataInput, + isNullable: generateNullable( + fieldMetadataInput.type, + fieldMetadataInput.isNullable, + fieldMetadataInput.isRemoteCreation, + ), + defaultValue: + fieldMetadataInput.defaultValue ?? + generateDefaultValue(fieldMetadataInput.type), + options: fieldMetadataInput.options + ? fieldMetadataInput.options.map((option) => ({ + ...option, + id: uuidV4(), + })) + : undefined, + isActive: true, + isCustom: true, + }; + + this.validateFieldMetadata( + fieldMetadataForCreate.type, + fieldMetadataForCreate, + objectMetadata, + ); + + if (fieldMetadataForCreate.isLabelSyncedWithName === true) { + validateNameAndLabelAreSyncOrThrow( + fieldMetadataForCreate.label, + fieldMetadataForCreate.name, + ); + } + + const createdFieldMetadata = await fieldMetadataRepository.save( + fieldMetadataForCreate, + ); + + createdFieldMetadatas.push(createdFieldMetadata); + + if (!fieldMetadataInput.isRemoteCreation) { + migrationActions.push({ + name: computeObjectTargetTable(objectMetadata), + action: WorkspaceMigrationTableActionType.ALTER, + columns: this.workspaceMigrationFactory.createColumnActions( + WorkspaceMigrationColumnActionType.CREATE, + createdFieldMetadata, + ), + }); + } + } + } + + if (migrationActions.length > 0) { + await this.workspaceMigrationService.createCustomMigration( + generateMigrationName(`create-multiple-fields`), + fieldMetadataInputs[0].workspaceId, + migrationActions, + ); + + await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations( + fieldMetadataInputs[0].workspaceId, + ); + } + + const dataSourceMetadata = + await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail( + fieldMetadataInputs[0].workspaceId, + ); + + const workspaceDataSource = + await this.typeORMService.connectToDataSource(dataSourceMetadata); + + const workspaceQueryRunner = workspaceDataSource?.createQueryRunner(); + + if (!workspaceQueryRunner) { + throw new FieldMetadataException( + 'Could not create workspace query runner', + FieldMetadataExceptionCode.INTERNAL_SERVER_ERROR, + ); + } + + await workspaceQueryRunner.connect(); + await workspaceQueryRunner.startTransaction(); + + try { + for (const createdFieldMetadata of createdFieldMetadatas) { + const view = await workspaceQueryRunner?.query( + `SELECT id FROM ${dataSourceMetadata.schema}."view" + WHERE "objectMetadataId" = '${createdFieldMetadata.objectMetadataId}'`, + ); + + if (!isEmpty(view)) { + const existingViewFields = (await workspaceQueryRunner?.query( + `SELECT * FROM ${dataSourceMetadata.schema}."viewField" + WHERE "viewId" = '${view[0].id}'`, + )) as ViewFieldWorkspaceEntity[]; + + const createdFieldIsAlreadyInView = existingViewFields.some( + (existingViewField) => + existingViewField.fieldMetadataId === createdFieldMetadata.id, + ); + + if (!createdFieldIsAlreadyInView) { + const lastPosition = existingViewFields + .map((viewField) => viewField.position) + .reduce((acc, position) => { + if (position > acc) { + return position; + } + + return acc; + }, -1); + + await workspaceQueryRunner?.query( + `INSERT INTO ${dataSourceMetadata.schema}."viewField" + ("fieldMetadataId", "position", "isVisible", "size", "viewId") + VALUES ('${createdFieldMetadata.id}', '${ + lastPosition + 1 + }', true, 180, '${view[0].id}')`, + ); + } + } + } + await workspaceQueryRunner.commitTransaction(); + } catch (error) { + await workspaceQueryRunner.rollbackTransaction(); + throw error; + } finally { + await workspaceQueryRunner.release(); + } + + await queryRunner.commitTransaction(); + + return createdFieldMetadatas; + } catch (error) { + await queryRunner.rollbackTransaction(); + throw error; + } finally { + await queryRunner.release(); + await this.workspaceMetadataVersionService.incrementMetadataVersion( + fieldMetadataInputs[0].workspaceId, + ); + } + } } diff --git a/packages/twenty-server/src/engine/seeder/seeder.service.ts b/packages/twenty-server/src/engine/seeder/seeder.service.ts index 0f06c7d55808..14ce7885fba3 100644 --- a/packages/twenty-server/src/engine/seeder/seeder.service.ts +++ b/packages/twenty-server/src/engine/seeder/seeder.service.ts @@ -38,13 +38,13 @@ export class SeederService { throw new Error("Object metadata couldn't be created"); } - for (const fieldMetadataSeed of objectMetadataSeed.fields) { - await this.fieldMetadataService.createOne({ + await this.fieldMetadataService.createMany( + objectMetadataSeed.fields.map((fieldMetadataSeed) => ({ ...fieldMetadataSeed, objectMetadataId: createdObjectMetadata.id, workspaceId, - }); - } + })), + ); const objectMetadataAfterFieldCreation = await this.objectMetadataService.findOneWithinWorkspace(workspaceId, { From bc224839a14cdc93aa0e12b5ed0c96871c1c0e9a Mon Sep 17 00:00:00 2001 From: Weiko Date: Mon, 3 Feb 2025 18:38:59 +0100 Subject: [PATCH 2/3] refactor a bit --- .../field-metadata/field-metadata.service.ts | 190 ++++++++++-------- 1 file changed, 102 insertions(+), 88 deletions(-) diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts index 1e21b0b9a713..3f5be6471b84 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts @@ -621,6 +621,31 @@ export class FieldMetadataService extends TypeOrmQueryService ({ + ...option, + id: uuidV4(), + })) + : undefined, + isActive: true, + isCustom: true, + }; + } + async createMany( fieldMetadataInputs: CreateFieldInput[], ): Promise { @@ -628,6 +653,8 @@ export class FieldMetadataService extends TypeOrmQueryService ({ - ...option, - id: uuidV4(), - })) - : undefined, - isActive: true, - isCustom: true, - }; + const fieldMetadataForCreate = + this.prepareCustomFieldMetadata(fieldMetadataInput); this.validateFieldMetadata( fieldMetadataForCreate.type, @@ -762,93 +769,100 @@ export class FieldMetadataService extends TypeOrmQueryService 0) { await this.workspaceMigrationService.createCustomMigration( generateMigrationName(`create-multiple-fields`), - fieldMetadataInputs[0].workspaceId, + workspaceId, migrationActions, ); await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations( - fieldMetadataInputs[0].workspaceId, + workspaceId, ); } - const dataSourceMetadata = - await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail( - fieldMetadataInputs[0].workspaceId, - ); + await this.createViewAndViewFields(createdFieldMetadatas, workspaceId); - const workspaceDataSource = - await this.typeORMService.connectToDataSource(dataSourceMetadata); + await queryRunner.commitTransaction(); - const workspaceQueryRunner = workspaceDataSource?.createQueryRunner(); + return createdFieldMetadatas; + } catch (error) { + await queryRunner.rollbackTransaction(); + throw error; + } finally { + await queryRunner.release(); + await this.workspaceMetadataVersionService.incrementMetadataVersion( + workspaceId, + ); + } + } - if (!workspaceQueryRunner) { - throw new FieldMetadataException( - 'Could not create workspace query runner', - FieldMetadataExceptionCode.INTERNAL_SERVER_ERROR, + private async createViewAndViewFields( + createdFieldMetadatas: FieldMetadataEntity[], + workspaceId: string, + ) { + const dataSourceMetadata = + await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail( + workspaceId, + ); + + const workspaceDataSource = + await this.typeORMService.connectToDataSource(dataSourceMetadata); + + const workspaceQueryRunner = workspaceDataSource?.createQueryRunner(); + + if (!workspaceQueryRunner) { + throw new FieldMetadataException( + 'Could not create workspace query runner', + FieldMetadataExceptionCode.INTERNAL_SERVER_ERROR, + ); + } + + await workspaceQueryRunner.connect(); + await workspaceQueryRunner.startTransaction(); + + try { + for (const createdFieldMetadata of createdFieldMetadatas) { + const view = await workspaceQueryRunner?.query( + `SELECT id FROM ${dataSourceMetadata.schema}."view" + WHERE "objectMetadataId" = '${createdFieldMetadata.objectMetadataId}'`, ); - } - await workspaceQueryRunner.connect(); - await workspaceQueryRunner.startTransaction(); + if (!isEmpty(view)) { + const existingViewFields = (await workspaceQueryRunner?.query( + `SELECT * FROM ${dataSourceMetadata.schema}."viewField" + WHERE "viewId" = '${view[0].id}'`, + )) as ViewFieldWorkspaceEntity[]; - try { - for (const createdFieldMetadata of createdFieldMetadatas) { - const view = await workspaceQueryRunner?.query( - `SELECT id FROM ${dataSourceMetadata.schema}."view" - WHERE "objectMetadataId" = '${createdFieldMetadata.objectMetadataId}'`, + const createdFieldIsAlreadyInView = existingViewFields.some( + (existingViewField) => + existingViewField.fieldMetadataId === createdFieldMetadata.id, ); - if (!isEmpty(view)) { - const existingViewFields = (await workspaceQueryRunner?.query( - `SELECT * FROM ${dataSourceMetadata.schema}."viewField" - WHERE "viewId" = '${view[0].id}'`, - )) as ViewFieldWorkspaceEntity[]; - - const createdFieldIsAlreadyInView = existingViewFields.some( - (existingViewField) => - existingViewField.fieldMetadataId === createdFieldMetadata.id, + if (!createdFieldIsAlreadyInView) { + const lastPosition = existingViewFields + .map((viewField) => viewField.position) + .reduce((acc, position) => { + if (position > acc) { + return position; + } + + return acc; + }, -1); + + await workspaceQueryRunner?.query( + `INSERT INTO ${dataSourceMetadata.schema}."viewField" + ("fieldMetadataId", "position", "isVisible", "size", "viewId") + VALUES ('${createdFieldMetadata.id}', '${ + lastPosition + 1 + }', true, 180, '${view[0].id}')`, ); - - if (!createdFieldIsAlreadyInView) { - const lastPosition = existingViewFields - .map((viewField) => viewField.position) - .reduce((acc, position) => { - if (position > acc) { - return position; - } - - return acc; - }, -1); - - await workspaceQueryRunner?.query( - `INSERT INTO ${dataSourceMetadata.schema}."viewField" - ("fieldMetadataId", "position", "isVisible", "size", "viewId") - VALUES ('${createdFieldMetadata.id}', '${ - lastPosition + 1 - }', true, 180, '${view[0].id}')`, - ); - } } } - await workspaceQueryRunner.commitTransaction(); - } catch (error) { - await workspaceQueryRunner.rollbackTransaction(); - throw error; - } finally { - await workspaceQueryRunner.release(); } - - await queryRunner.commitTransaction(); - - return createdFieldMetadatas; + await workspaceQueryRunner.commitTransaction(); } catch (error) { - await queryRunner.rollbackTransaction(); + await workspaceQueryRunner.rollbackTransaction(); throw error; } finally { - await queryRunner.release(); - await this.workspaceMetadataVersionService.incrementMetadataVersion( - fieldMetadataInputs[0].workspaceId, - ); + await workspaceQueryRunner.release(); } } } From 1f2576eb55dcc0f4363a4eecd5994955948f15b5 Mon Sep 17 00:00:00 2001 From: Weiko Date: Mon, 3 Feb 2025 18:48:31 +0100 Subject: [PATCH 3/3] more refactoring --- .../field-metadata/field-metadata.service.ts | 162 +++++++++++------- 1 file changed, 97 insertions(+), 65 deletions(-) diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts index 3f5be6471b84..c1d180947ac5 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts @@ -646,6 +646,85 @@ export class FieldMetadataService extends TypeOrmQueryService { + return fieldMetadataInputs.reduce( + (acc, input) => { + if (!acc[input.objectMetadataId]) { + acc[input.objectMetadataId] = []; + } + acc[input.objectMetadataId].push(input); + + return acc; + }, + {} as Record, + ); + } + + private async validateAndCreateFieldMetadata( + fieldMetadataInput: CreateFieldInput, + objectMetadata: ObjectMetadataEntity, + fieldMetadataRepository: Repository, + ): Promise { + if (!fieldMetadataInput.isRemoteCreation) { + assertMutationNotOnRemoteObject(objectMetadata); + } + + if (isEnumFieldMetadataType(fieldMetadataInput.type)) { + if ( + !fieldMetadataInput.options && + fieldMetadataInput.type !== FieldMetadataType.RATING + ) { + throw new FieldMetadataException( + 'Options are required for enum fields', + FieldMetadataExceptionCode.INVALID_FIELD_INPUT, + ); + } + } + + if (fieldMetadataInput.type === FieldMetadataType.RATING) { + fieldMetadataInput.options = generateRatingOptions(); + } + + const fieldMetadataForCreate = + this.prepareCustomFieldMetadata(fieldMetadataInput); + + this.validateFieldMetadata( + fieldMetadataForCreate.type, + fieldMetadataForCreate, + objectMetadata, + ); + + if (fieldMetadataForCreate.isLabelSyncedWithName === true) { + validateNameAndLabelAreSyncOrThrow( + fieldMetadataForCreate.label, + fieldMetadataForCreate.name, + ); + } + + return await fieldMetadataRepository.save(fieldMetadataForCreate); + } + + private async createMigrationActions( + createdFieldMetadata: FieldMetadataEntity, + objectMetadata: ObjectMetadataEntity, + isRemoteCreation: boolean, + ): Promise { + if (isRemoteCreation) { + return null; + } + + return { + name: computeObjectTargetTable(objectMetadata), + action: WorkspaceMigrationTableActionType.ALTER, + columns: this.workspaceMigrationFactory.createColumnActions( + WorkspaceMigrationColumnActionType.CREATE, + createdFieldMetadata, + ), + }; + } + async createMany( fieldMetadataInputs: CreateFieldInput[], ): Promise { @@ -654,7 +733,6 @@ export class FieldMetadataService extends TypeOrmQueryService { - if (!acc[input.objectMetadataId]) { - acc[input.objectMetadataId] = []; - } - acc[input.objectMetadataId].push(input); - - return acc; - }, - {} as Record, - ); - + const inputsByObjectId = + this.groupFieldInputsByObjectId(fieldMetadataInputs); const objectMetadataIds = Object.keys(inputsByObjectId); + const objectMetadatas = await this.objectMetadataRepository.find({ where: { id: In(objectMetadataIds), @@ -688,11 +757,7 @@ export class FieldMetadataService extends TypeOrmQueryService { - acc[obj.id] = obj; - - return acc; - }, + (acc, obj) => ({ ...acc, [obj.id]: obj }), {} as Record, ); @@ -701,7 +766,6 @@ export class FieldMetadataService extends TypeOrmQueryService( - fieldMetadataForCreate.type, - fieldMetadataForCreate, + const migrationAction = await this.createMigrationActions( + createdFieldMetadata, objectMetadata, + fieldMetadataInput.isRemoteCreation ?? false, ); - if (fieldMetadataForCreate.isLabelSyncedWithName === true) { - validateNameAndLabelAreSyncOrThrow( - fieldMetadataForCreate.label, - fieldMetadataForCreate.name, - ); - } - - const createdFieldMetadata = await fieldMetadataRepository.save( - fieldMetadataForCreate, - ); - - createdFieldMetadatas.push(createdFieldMetadata); - - if (!fieldMetadataInput.isRemoteCreation) { - migrationActions.push({ - name: computeObjectTargetTable(objectMetadata), - action: WorkspaceMigrationTableActionType.ALTER, - columns: this.workspaceMigrationFactory.createColumnActions( - WorkspaceMigrationColumnActionType.CREATE, - createdFieldMetadata, - ), - }); + if (migrationAction) { + migrationActions.push(migrationAction); } } }