[BREAKING_CHANGE/GRAPHQL/OBJECT_METADATA_CREATE_ONE] Remove object/fields/view-fields v1 implementation (#15823)
# Introduction Remove the v2 feature flag for view-field field-metadata and object-metadata metadata entities ## Some details - Disabled nestjs-query for object metadata creation and explicitly calling it - removed all v1 integration tests files ## Remarks Not remove v2 referencing in both filenaming right now will handle that globally later ## Breaking change Due to object metadata resolver createOne standardization had to rename the input from `CreateObjectInput` to `CreateOneObjectInput`
This commit is contained in:
-792
@@ -1,792 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { type Query, type QueryOptions } from '@ptc-org/nestjs-query-core';
|
||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
DataSource,
|
||||
In,
|
||||
Repository,
|
||||
type FindManyOptions,
|
||||
type FindOneOptions,
|
||||
type QueryRunner,
|
||||
} from 'typeorm';
|
||||
|
||||
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 { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { IndexMetadataService } from 'src/engine/metadata-modules/index-metadata/index-metadata.service';
|
||||
import { type DeleteOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/delete-object.input';
|
||||
import {
|
||||
type UpdateObjectPayload,
|
||||
type UpdateOneObjectInput,
|
||||
} from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input';
|
||||
import { ObjectMetadataServiceV2 } from 'src/engine/metadata-modules/object-metadata/object-metadata-v2.service';
|
||||
import {
|
||||
ObjectMetadataException,
|
||||
ObjectMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||
import { ObjectMetadataFieldRelationService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-field-relation.service';
|
||||
import { ObjectMetadataMigrationService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-migration.service';
|
||||
import { ObjectMetadataRelatedRecordsService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-related-records.service';
|
||||
import { buildDefaultFieldsForCustomObject } from 'src/engine/metadata-modules/object-metadata/utils/build-default-fields-for-custom-object.util';
|
||||
import {
|
||||
validateLowerCasedAndTrimmedStringsAreDifferentOrThrow,
|
||||
validateObjectMetadataInputLabelsOrThrow,
|
||||
validateObjectMetadataInputNamesOrThrow,
|
||||
} from 'src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util';
|
||||
import { SearchVectorService } from 'src/engine/metadata-modules/search-vector/search-vector.service';
|
||||
import { type ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import { validateMetadataIdentifierFieldMetadataIds } from 'src/engine/metadata-modules/utils/validate-metadata-identifier-field-metadata-id.utils';
|
||||
import { validateNameAndLabelAreSyncOrThrow } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util';
|
||||
import { validatesNoOtherObjectWithSameNameExistsOrThrows } from 'src/engine/metadata-modules/utils/validate-no-other-object-with-same-name-exists-or-throw.util';
|
||||
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
|
||||
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
|
||||
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
|
||||
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||
import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
|
||||
import { CUSTOM_OBJECT_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
||||
import { isSearchableFieldType } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/is-searchable-field.util';
|
||||
|
||||
import { ObjectMetadataEntity } from './object-metadata.entity';
|
||||
|
||||
import { type CreateObjectInput } from './dtos/create-object.input';
|
||||
|
||||
@Injectable()
|
||||
export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEntity> {
|
||||
constructor(
|
||||
@InjectRepository(ObjectMetadataEntity)
|
||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||
@InjectRepository(FieldMetadataEntity)
|
||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||
|
||||
private readonly dataSourceService: DataSourceService,
|
||||
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
|
||||
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
|
||||
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
||||
private readonly searchVectorService: SearchVectorService,
|
||||
private readonly objectMetadataFieldRelationService: ObjectMetadataFieldRelationService,
|
||||
private readonly objectMetadataMigrationService: ObjectMetadataMigrationService,
|
||||
private readonly objectMetadataRelatedRecordsService: ObjectMetadataRelatedRecordsService,
|
||||
private readonly indexMetadataService: IndexMetadataService,
|
||||
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
|
||||
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
private readonly objectMetadataServiceV2: ObjectMetadataServiceV2,
|
||||
) {
|
||||
super(objectMetadataRepository);
|
||||
}
|
||||
|
||||
override async query(
|
||||
query: Query<ObjectMetadataEntity>,
|
||||
opts?: QueryOptions<ObjectMetadataEntity> | undefined,
|
||||
): Promise<ObjectMetadataEntity[]> {
|
||||
const start = performance.now();
|
||||
|
||||
const result = super.query(query, opts);
|
||||
|
||||
const end = performance.now();
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`metadata query time: ${end - start} ms`);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
override async createOne(
|
||||
createObjectInput: CreateObjectInput,
|
||||
): Promise<ObjectMetadataEntity> {
|
||||
const { workspaceId } = createObjectInput;
|
||||
const isWorkspaceMigrationV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (isWorkspaceMigrationV2Enabled) {
|
||||
const flatObjectMetadata = await this.objectMetadataServiceV2.createOne({
|
||||
createObjectInput,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const createdObjectMetadata = await this.objectMetadataRepository.findOne(
|
||||
{
|
||||
where: {
|
||||
id: flatObjectMetadata.id,
|
||||
workspaceId,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (!isDefined(createdObjectMetadata)) {
|
||||
throw new ObjectMetadataException(
|
||||
'Created object metadata not found',
|
||||
ObjectMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return createdObjectMetadata;
|
||||
}
|
||||
|
||||
const queryRunner = this.coreDataSource.createQueryRunner();
|
||||
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
const objectMetadataRepository =
|
||||
queryRunner.manager.getRepository(ObjectMetadataEntity);
|
||||
|
||||
const { objectMetadataMaps } =
|
||||
await this.workspaceMetadataCacheService.getExistingOrRecomputeMetadataMaps(
|
||||
{
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
const lastDataSourceMetadata =
|
||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
createObjectInput.labelSingular = capitalize(
|
||||
createObjectInput.labelSingular,
|
||||
);
|
||||
createObjectInput.labelPlural = capitalize(createObjectInput.labelPlural);
|
||||
|
||||
validateObjectMetadataInputNamesOrThrow(createObjectInput);
|
||||
validateObjectMetadataInputLabelsOrThrow(createObjectInput);
|
||||
|
||||
validateLowerCasedAndTrimmedStringsAreDifferentOrThrow({
|
||||
inputs: [createObjectInput.nameSingular, createObjectInput.namePlural],
|
||||
message:
|
||||
'The singular and plural names cannot be the same for an object',
|
||||
});
|
||||
validateLowerCasedAndTrimmedStringsAreDifferentOrThrow({
|
||||
inputs: [
|
||||
createObjectInput.labelPlural,
|
||||
createObjectInput.labelSingular,
|
||||
],
|
||||
message:
|
||||
'The singular and plural labels cannot be the same for an object',
|
||||
});
|
||||
|
||||
if (createObjectInput.isLabelSyncedWithName === true) {
|
||||
validateNameAndLabelAreSyncOrThrow({
|
||||
label: createObjectInput.labelSingular,
|
||||
name: createObjectInput.nameSingular,
|
||||
});
|
||||
validateNameAndLabelAreSyncOrThrow({
|
||||
label: createObjectInput.labelPlural,
|
||||
name: createObjectInput.namePlural,
|
||||
});
|
||||
}
|
||||
|
||||
validatesNoOtherObjectWithSameNameExistsOrThrows({
|
||||
objectMetadataNamePlural: createObjectInput.namePlural,
|
||||
objectMetadataNameSingular: createObjectInput.nameSingular,
|
||||
objectMetadataMaps,
|
||||
});
|
||||
|
||||
const baseCustomFields = buildDefaultFieldsForCustomObject(workspaceId);
|
||||
|
||||
const labelIdentifierFieldMetadataId = baseCustomFields.find(
|
||||
(field) => field.standardId === CUSTOM_OBJECT_STANDARD_FIELD_IDS.name,
|
||||
)?.id;
|
||||
|
||||
if (!isDefined(labelIdentifierFieldMetadataId)) {
|
||||
throw new ObjectMetadataException(
|
||||
'Label identifier field metadata not created properly',
|
||||
ObjectMetadataExceptionCode.MISSING_CUSTOM_OBJECT_DEFAULT_LABEL_IDENTIFIER_FIELD,
|
||||
);
|
||||
}
|
||||
|
||||
const createdObjectMetadata = await objectMetadataRepository.save({
|
||||
...createObjectInput,
|
||||
dataSourceId: lastDataSourceMetadata.id,
|
||||
targetTableName: 'DEPRECATED',
|
||||
isActive: true,
|
||||
isCustom: !createObjectInput.isRemote,
|
||||
isSystem: false,
|
||||
isRemote: createObjectInput.isRemote,
|
||||
isSearchable: !createObjectInput.isRemote,
|
||||
fields: createObjectInput.isRemote ? [] : baseCustomFields,
|
||||
labelIdentifierFieldMetadataId,
|
||||
});
|
||||
|
||||
if (createObjectInput.isRemote) {
|
||||
throw new Error('Remote objects are not supported yet');
|
||||
} else {
|
||||
const fieldsById = createdObjectMetadata.fields.reduce(
|
||||
(acc, field) => ({
|
||||
...acc,
|
||||
[field.id]: field,
|
||||
}),
|
||||
{},
|
||||
);
|
||||
|
||||
const createdRelatedObjectMetadataCollection =
|
||||
await this.objectMetadataFieldRelationService.createRelationsAndForeignKeysMetadata(
|
||||
workspaceId,
|
||||
{ ...createdObjectMetadata, fieldsById },
|
||||
objectMetadataMaps,
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
await this.objectMetadataMigrationService.createTableMigration(
|
||||
createdObjectMetadata,
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
await this.objectMetadataMigrationService.createColumnsMigrations(
|
||||
createdObjectMetadata,
|
||||
createdObjectMetadata.fields,
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
await this.objectMetadataMigrationService.createRelationMigrations(
|
||||
createdObjectMetadata,
|
||||
createdRelatedObjectMetadataCollection,
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
await this.searchVectorService.createSearchVectorFieldForObject(
|
||||
createObjectInput,
|
||||
createdObjectMetadata,
|
||||
queryRunner,
|
||||
);
|
||||
}
|
||||
|
||||
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrationsWithinTransaction(
|
||||
createdObjectMetadata.workspaceId,
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
// After commit, do non-transactional work
|
||||
await this.workspacePermissionsCacheService.recomputeRolesPermissionsCache(
|
||||
{
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
await this.objectMetadataRelatedRecordsService.createObjectRelatedRecords(
|
||||
createdObjectMetadata,
|
||||
);
|
||||
|
||||
await this.workspaceMetadataVersionService.incrementMetadataVersion(
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
await this.flatEntityMapsCacheService.invalidateFlatEntityMaps({
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatFieldMetadataMaps', 'flatObjectMetadataMaps'],
|
||||
});
|
||||
|
||||
return createdObjectMetadata;
|
||||
} catch (error) {
|
||||
if (queryRunner.isTransactionActive) {
|
||||
try {
|
||||
await queryRunner.rollbackTransaction();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.trace(`Failed to rollback transaction: ${error.message}`);
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
public async updateOneObject(
|
||||
updateObjectInput: UpdateOneObjectInput,
|
||||
workspaceId: string,
|
||||
): Promise<ObjectMetadataEntity> {
|
||||
const queryRunner = this.coreDataSource.createQueryRunner();
|
||||
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
const objectMetadataRepository =
|
||||
queryRunner.manager.getRepository(ObjectMetadataEntity);
|
||||
|
||||
const { objectMetadataMaps } =
|
||||
await this.workspaceMetadataCacheService.getExistingOrRecomputeMetadataMaps(
|
||||
{ workspaceId },
|
||||
);
|
||||
const inputId = updateObjectInput.id;
|
||||
const inputPayload = {
|
||||
...updateObjectInput.update,
|
||||
...(isDefined(updateObjectInput.update.labelSingular)
|
||||
? {
|
||||
labelSingular: capitalize(updateObjectInput.update.labelSingular),
|
||||
}
|
||||
: {}),
|
||||
...(isDefined(updateObjectInput.update.labelPlural)
|
||||
? { labelPlural: capitalize(updateObjectInput.update.labelPlural) }
|
||||
: {}),
|
||||
};
|
||||
|
||||
validateObjectMetadataInputNamesOrThrow(inputPayload);
|
||||
const existingObjectMetadata = objectMetadataMaps.byId[inputId];
|
||||
|
||||
if (!existingObjectMetadata) {
|
||||
throw new ObjectMetadataException(
|
||||
'Object does not exist',
|
||||
ObjectMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
const existingObjectMetadataCombinedWithUpdateInput = {
|
||||
...existingObjectMetadata,
|
||||
...inputPayload,
|
||||
};
|
||||
|
||||
validatesNoOtherObjectWithSameNameExistsOrThrows({
|
||||
objectMetadataNameSingular:
|
||||
existingObjectMetadataCombinedWithUpdateInput.nameSingular,
|
||||
objectMetadataNamePlural:
|
||||
existingObjectMetadataCombinedWithUpdateInput.namePlural,
|
||||
existingObjectMetadataId:
|
||||
existingObjectMetadataCombinedWithUpdateInput.id,
|
||||
objectMetadataMaps,
|
||||
});
|
||||
if (existingObjectMetadataCombinedWithUpdateInput.isLabelSyncedWithName) {
|
||||
validateNameAndLabelAreSyncOrThrow({
|
||||
label: existingObjectMetadataCombinedWithUpdateInput.labelSingular,
|
||||
name: existingObjectMetadataCombinedWithUpdateInput.nameSingular,
|
||||
});
|
||||
validateNameAndLabelAreSyncOrThrow({
|
||||
label: existingObjectMetadataCombinedWithUpdateInput.labelPlural,
|
||||
name: existingObjectMetadataCombinedWithUpdateInput.namePlural,
|
||||
});
|
||||
}
|
||||
if (
|
||||
isDefined(inputPayload.nameSingular) ||
|
||||
isDefined(inputPayload.namePlural)
|
||||
) {
|
||||
validateLowerCasedAndTrimmedStringsAreDifferentOrThrow({
|
||||
inputs: [
|
||||
existingObjectMetadataCombinedWithUpdateInput.nameSingular,
|
||||
existingObjectMetadataCombinedWithUpdateInput.namePlural,
|
||||
],
|
||||
message:
|
||||
'The singular and plural names cannot be the same for an object',
|
||||
});
|
||||
}
|
||||
validateMetadataIdentifierFieldMetadataIds({
|
||||
fieldMetadataItems: Object.values(existingObjectMetadata.fieldsById),
|
||||
labelIdentifierFieldMetadataId:
|
||||
inputPayload.labelIdentifierFieldMetadataId,
|
||||
imageIdentifierFieldMetadataId:
|
||||
inputPayload.imageIdentifierFieldMetadataId,
|
||||
});
|
||||
const updatedObject = await objectMetadataRepository.save({
|
||||
...existingObjectMetadata,
|
||||
...inputPayload,
|
||||
});
|
||||
|
||||
const { didUpdateLabelOrIcon } =
|
||||
await this.handleObjectNameAndLabelUpdates({
|
||||
existingObjectMetadata,
|
||||
objectMetadataForUpdate:
|
||||
existingObjectMetadataCombinedWithUpdateInput,
|
||||
inputPayload,
|
||||
queryRunner,
|
||||
objectMetadataMaps,
|
||||
});
|
||||
|
||||
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrationsWithinTransaction(
|
||||
workspaceId,
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
if (inputPayload.labelIdentifierFieldMetadataId) {
|
||||
const labelIdentifierFieldMetadata =
|
||||
existingObjectMetadata.fieldsById[
|
||||
inputPayload.labelIdentifierFieldMetadataId
|
||||
];
|
||||
|
||||
if (isSearchableFieldType(labelIdentifierFieldMetadata.type)) {
|
||||
await this.searchVectorService.updateSearchVector(
|
||||
inputId,
|
||||
[
|
||||
{
|
||||
name: labelIdentifierFieldMetadata.name,
|
||||
type: labelIdentifierFieldMetadata.type,
|
||||
},
|
||||
],
|
||||
workspaceId,
|
||||
queryRunner,
|
||||
);
|
||||
}
|
||||
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrationsWithinTransaction(
|
||||
workspaceId,
|
||||
queryRunner,
|
||||
);
|
||||
}
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
// After commit, do non-transactional work
|
||||
await this.workspacePermissionsCacheService.recomputeRolesPermissionsCache(
|
||||
{
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
if (didUpdateLabelOrIcon) {
|
||||
await this.objectMetadataRelatedRecordsService.updateObjectViews(
|
||||
updatedObject,
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
isDefined(inputPayload.labelIdentifierFieldMetadataId) &&
|
||||
inputPayload.labelIdentifierFieldMetadataId !==
|
||||
existingObjectMetadata.labelIdentifierFieldMetadataId
|
||||
) {
|
||||
const labelIdentifierFieldMetadata =
|
||||
existingObjectMetadata.fieldsById[
|
||||
inputPayload.labelIdentifierFieldMetadataId
|
||||
];
|
||||
|
||||
await this.objectMetadataRelatedRecordsService.updateLabelMetadataIdentifierInObjectViews(
|
||||
{
|
||||
newLabelMetadataIdentifierFieldMetadata:
|
||||
labelIdentifierFieldMetadata,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
await this.workspaceMetadataVersionService.incrementMetadataVersion(
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
await this.flatEntityMapsCacheService.invalidateFlatEntityMaps({
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatObjectMetadataMaps', 'flatFieldMetadataMaps'],
|
||||
});
|
||||
|
||||
const formattedUpdatedObject = {
|
||||
...updatedObject,
|
||||
createdAt: new Date(updatedObject.createdAt),
|
||||
updatedAt: new Date(updatedObject.updatedAt),
|
||||
};
|
||||
|
||||
return formattedUpdatedObject;
|
||||
} catch (error) {
|
||||
if (queryRunner.isTransactionActive) {
|
||||
try {
|
||||
await queryRunner.rollbackTransaction();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.trace(`Failed to rollback transaction: ${error.message}`);
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
public async deleteOneObject(
|
||||
deleteObjectInput: DeleteOneObjectInput,
|
||||
workspaceId: string,
|
||||
): Promise<Partial<ObjectMetadataEntity>> {
|
||||
const isWorkspaceMigrationV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (isWorkspaceMigrationV2Enabled) {
|
||||
return await this.objectMetadataServiceV2.deleteOne({
|
||||
deleteObjectInput,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
const queryRunner = this.coreDataSource.createQueryRunner();
|
||||
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
const objectMetadataRepository =
|
||||
queryRunner.manager.getRepository(ObjectMetadataEntity);
|
||||
const fieldMetadataRepository =
|
||||
queryRunner.manager.getRepository(FieldMetadataEntity);
|
||||
|
||||
const objectMetadata = await objectMetadataRepository.findOne({
|
||||
relations: [
|
||||
'fields',
|
||||
'fields.object',
|
||||
'fields.relationTargetFieldMetadata',
|
||||
'fields.relationTargetFieldMetadata.object',
|
||||
],
|
||||
where: {
|
||||
id: deleteObjectInput.id,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!objectMetadata) {
|
||||
throw new ObjectMetadataException(
|
||||
'Object does not exist',
|
||||
ObjectMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
if (objectMetadata.isRemote) {
|
||||
throw new ObjectMetadataException(
|
||||
'Remote objects are not supported yet',
|
||||
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
);
|
||||
} else {
|
||||
await this.objectMetadataMigrationService.deleteAllRelationsAndDropTable(
|
||||
objectMetadata,
|
||||
workspaceId,
|
||||
queryRunner,
|
||||
);
|
||||
}
|
||||
|
||||
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrationsWithinTransaction(
|
||||
workspaceId,
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
const fieldMetadataIds = objectMetadata.fields.map((field) => field.id);
|
||||
const relationMetadataIds = objectMetadata.fields.flatMap((field) => {
|
||||
if (
|
||||
isFieldMetadataEntityOfType(field, FieldMetadataType.MORPH_RELATION)
|
||||
) {
|
||||
return field.relationTargetFieldMetadata.id;
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
|
||||
await fieldMetadataRepository.delete({
|
||||
id: In(fieldMetadataIds.concat(relationMetadataIds)),
|
||||
});
|
||||
|
||||
await objectMetadataRepository.delete(objectMetadata.id);
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
// After commit, do non-transactional work
|
||||
await this.workspaceMetadataVersionService.incrementMetadataVersion(
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
await this.flatEntityMapsCacheService.invalidateFlatEntityMaps({
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatObjectMetadataMaps', 'flatFieldMetadataMaps'],
|
||||
});
|
||||
|
||||
await this.workspacePermissionsCacheService.recomputeRolesPermissionsCache(
|
||||
{
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
await this.objectMetadataRelatedRecordsService.deleteObjectViews(
|
||||
objectMetadata,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
return objectMetadata;
|
||||
} catch (error) {
|
||||
if (queryRunner.isTransactionActive) {
|
||||
try {
|
||||
await queryRunner.rollbackTransaction();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.trace(`Failed to rollback transaction: ${error.message}`);
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
public async findOneWithinWorkspace(
|
||||
workspaceId: string,
|
||||
options: FindOneOptions<ObjectMetadataEntity>,
|
||||
): Promise<ObjectMetadataEntity | null> {
|
||||
return this.objectMetadataRepository.findOne({
|
||||
relations: [
|
||||
'fields',
|
||||
'indexMetadatas',
|
||||
'indexMetadatas.indexFieldMetadatas',
|
||||
],
|
||||
...options,
|
||||
where: {
|
||||
...options.where,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public async findManyWithinWorkspace(
|
||||
workspaceId: string,
|
||||
options?: FindManyOptions<ObjectMetadataEntity>,
|
||||
) {
|
||||
return this.objectMetadataRepository.find({
|
||||
relations: [
|
||||
'fields.object',
|
||||
'fields',
|
||||
'fields.relationTargetObjectMetadata',
|
||||
],
|
||||
...options,
|
||||
where: {
|
||||
...options?.where,
|
||||
workspaceId,
|
||||
},
|
||||
order: {
|
||||
...options?.order,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public async deleteObjectsMetadata(workspaceId: string) {
|
||||
const objectsMetadata = await this.objectMetadataRepository.find({
|
||||
where: {
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
await this.fieldMetadataRepository.delete({
|
||||
workspaceId,
|
||||
type: In([FieldMetadataType.MORPH_RELATION, FieldMetadataType.RELATION]),
|
||||
});
|
||||
|
||||
for (const objectMetadata of objectsMetadata) {
|
||||
await this.objectMetadataRepository.delete({
|
||||
id: objectMetadata.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async handleObjectNameAndLabelUpdates({
|
||||
existingObjectMetadata,
|
||||
objectMetadataForUpdate,
|
||||
inputPayload,
|
||||
queryRunner,
|
||||
objectMetadataMaps,
|
||||
}: {
|
||||
existingObjectMetadata: Pick<
|
||||
ObjectMetadataItemWithFieldMaps,
|
||||
'nameSingular' | 'isCustom' | 'id' | 'labelPlural' | 'icon' | 'fieldsById'
|
||||
>;
|
||||
objectMetadataForUpdate: Pick<
|
||||
ObjectMetadataItemWithFieldMaps,
|
||||
| 'nameSingular'
|
||||
| 'isCustom'
|
||||
| 'workspaceId'
|
||||
| 'id'
|
||||
| 'labelSingular'
|
||||
| 'labelPlural'
|
||||
| 'icon'
|
||||
| 'fieldsById'
|
||||
>;
|
||||
inputPayload: UpdateObjectPayload;
|
||||
queryRunner: QueryRunner;
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
}): Promise<{ didUpdateLabelOrIcon: boolean }> {
|
||||
const newTargetTableName = computeObjectTargetTable(
|
||||
objectMetadataForUpdate,
|
||||
);
|
||||
const existingTargetTableName = computeObjectTargetTable(
|
||||
existingObjectMetadata,
|
||||
);
|
||||
|
||||
if (newTargetTableName !== existingTargetTableName) {
|
||||
await this.objectMetadataMigrationService.createRenameTableMigration(
|
||||
existingObjectMetadata,
|
||||
objectMetadataForUpdate,
|
||||
objectMetadataForUpdate.workspaceId,
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
const relationMetadataCollection =
|
||||
await this.objectMetadataFieldRelationService.updateRelationsAndForeignKeysMetadata(
|
||||
{
|
||||
workspaceId: objectMetadataForUpdate.workspaceId,
|
||||
updatedObjectMetadata: objectMetadataForUpdate,
|
||||
queryRunner,
|
||||
objectMetadataMaps,
|
||||
},
|
||||
);
|
||||
|
||||
await this.objectMetadataMigrationService.updateRelationMigrations(
|
||||
existingObjectMetadata,
|
||||
objectMetadataForUpdate,
|
||||
relationMetadataCollection,
|
||||
objectMetadataForUpdate.workspaceId,
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
const morphRelationFieldMetadataToUpdate =
|
||||
await this.objectMetadataFieldRelationService.updateMorphRelationsJoinColumnName(
|
||||
{
|
||||
existingObjectMetadata,
|
||||
objectMetadataForUpdate,
|
||||
queryRunner,
|
||||
},
|
||||
);
|
||||
|
||||
await this.objectMetadataMigrationService.updateMorphRelationMigrations({
|
||||
workspaceId: objectMetadataForUpdate.workspaceId,
|
||||
morphRelationFieldMetadataToUpdate: morphRelationFieldMetadataToUpdate,
|
||||
queryRunner,
|
||||
});
|
||||
|
||||
await this.objectMetadataMigrationService.recomputeEnumNames(
|
||||
objectMetadataForUpdate,
|
||||
objectMetadataForUpdate.workspaceId,
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
const recomputedIndexes =
|
||||
await this.indexMetadataService.recomputeIndexMetadataForObject(
|
||||
objectMetadataForUpdate.workspaceId,
|
||||
objectMetadataForUpdate,
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
await this.indexMetadataService.createIndexRecomputeMigrations(
|
||||
objectMetadataForUpdate.workspaceId,
|
||||
objectMetadataForUpdate,
|
||||
recomputedIndexes,
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
if (
|
||||
(inputPayload.labelPlural || inputPayload.icon) &&
|
||||
(inputPayload.labelPlural !== existingObjectMetadata.labelPlural ||
|
||||
inputPayload.icon !== existingObjectMetadata.icon)
|
||||
) {
|
||||
return {
|
||||
didUpdateLabelOrIcon: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
didUpdateLabelOrIcon: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user