ObjectMetadataServiceV2.deleteOne (#13871)
# Introduction - Implementing the delete one for the new object metadata service v2. - Handling relation fields and fields in the first place to finally remove the object - puting back updatedAt and createdAt in flat metadatas - duplicate criteria addition for flat object - removing datasource id from object metadata dto
This commit is contained in:
-3
@@ -35,9 +35,6 @@ export class ObjectMetadataDTO {
|
||||
@IDField(() => UUIDScalarType)
|
||||
id: string;
|
||||
|
||||
@Field(() => UUIDScalarType)
|
||||
dataSourceId: string;
|
||||
|
||||
@Field()
|
||||
nameSingular: string;
|
||||
|
||||
|
||||
+119
-5
@@ -1,12 +1,21 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { MultipleMetadataValidationErrors } from 'src/engine/core-modules/error/multiple-metadata-validation-errors';
|
||||
import { EMPTY_FLAT_OBJECT_METADATA_MAPS } from 'src/engine/metadata-modules/flat-object-metadata-maps/constant/empty-flat-object-metadata-maps.constant';
|
||||
import { addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-object-metadata-to-flat-object-metadata-maps-or-throw.util';
|
||||
import { deleteFieldFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-field-from-flat-object-metadata-maps-or-throw.util';
|
||||
import { deleteObjectFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-object-from-flat-object-metadata-maps-or-throw.util';
|
||||
import { getSubFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/get-sub-flat-object-metadata-maps-or-throw.util';
|
||||
import { FlatObjectMetadataValidatorService } from 'src/engine/metadata-modules/flat-object-metadata/services/flat-object-metadata-validator.service';
|
||||
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { fromCreateObjectInputToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-create-object-input-to-flat-object-metadata.util';
|
||||
import { fromDeleteObjectInputToFlatFieldMetadatasToDelete } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-delete-object-input-to-flat-field-metadatas-to-delete.util';
|
||||
import { fromFlatObjectMetadataToObjectMetadataDto } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-to-object-metadata-dto.util';
|
||||
import { CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input';
|
||||
import { DeleteOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/delete-object.input';
|
||||
import { ObjectMetadataDTO } from 'src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto';
|
||||
import {
|
||||
ObjectMetadataException,
|
||||
ObjectMetadataExceptionCode,
|
||||
@@ -24,11 +33,99 @@ export class ObjectMetadataServiceV2 {
|
||||
private readonly flatObjectMetadataValidatorService: FlatObjectMetadataValidatorService,
|
||||
) {}
|
||||
|
||||
async createOne({
|
||||
objectMetadataInput,
|
||||
async deleteOne({
|
||||
deleteObjectInput,
|
||||
workspaceId,
|
||||
}: {
|
||||
objectMetadataInput: Omit<CreateObjectInput, 'workspaceId'>;
|
||||
deleteObjectInput: DeleteOneObjectInput;
|
||||
workspaceId: string;
|
||||
}): Promise<ObjectMetadataDTO> {
|
||||
const { flatObjectMetadataMaps: existingFlatObjectMetadataMaps } =
|
||||
await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps(
|
||||
{
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
const { flatFieldMetadatasToDelete, flatObjectMetadataToDelete } =
|
||||
fromDeleteObjectInputToFlatFieldMetadatasToDelete({
|
||||
deleteObjectInput,
|
||||
existingFlatObjectMetadataMaps,
|
||||
});
|
||||
const { id: objectMetadataToDeleteId } = flatObjectMetadataToDelete;
|
||||
|
||||
const flatObjectDeleteValidationErrors =
|
||||
this.flatObjectMetadataValidatorService.validateFlatObjectMetadataDeletion(
|
||||
{
|
||||
existingFlatObjectMetadataMaps,
|
||||
objectMetadataToDeleteId,
|
||||
},
|
||||
);
|
||||
|
||||
if (flatObjectDeleteValidationErrors.length > 0) {
|
||||
throw new MultipleMetadataValidationErrors(
|
||||
flatObjectDeleteValidationErrors,
|
||||
'Multiple validation errors occurred while deleting object',
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const impactedObjectMetadataIds = Array.from(
|
||||
new Set(
|
||||
flatFieldMetadatasToDelete.map(
|
||||
(flatFieldMetadata) => flatFieldMetadata.objectMetadataId,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const fromFlatObjectMetadataMaps = getSubFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
|
||||
objectMetadataIds: impactedObjectMetadataIds,
|
||||
});
|
||||
|
||||
const toFlatObjectMetadataMaps = flatFieldMetadatasToDelete
|
||||
.filter(
|
||||
(flatFieldMetadataToDelete) =>
|
||||
flatFieldMetadataToDelete.objectMetadataId !==
|
||||
objectMetadataToDeleteId,
|
||||
)
|
||||
.reduce(
|
||||
(flatObjectMetadataMaps, flatFieldMetadata) =>
|
||||
deleteFieldFromFlatObjectMetadataMapsOrThrow({
|
||||
fieldMetadataId: flatFieldMetadata.id,
|
||||
objectMetadataId: flatFieldMetadata.objectMetadataId,
|
||||
flatObjectMetadataMaps,
|
||||
}),
|
||||
deleteObjectFromFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadataMaps: fromFlatObjectMetadataMaps,
|
||||
objectMetadataId: objectMetadataToDeleteId,
|
||||
}),
|
||||
);
|
||||
const workspaceMigration = this.workspaceMigrationBuilderV2.build({
|
||||
fromFlatObjectMetadataMaps,
|
||||
toFlatObjectMetadataMaps,
|
||||
inferDeletionFromMissingObjectFieldIndex: true,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
await this.workspaceMigrationRunnerV2Service.run(workspaceMigration);
|
||||
} catch {
|
||||
throw new ObjectMetadataException(
|
||||
'Workspace migration failed to run',
|
||||
ObjectMetadataExceptionCode.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
|
||||
return fromFlatObjectMetadataToObjectMetadataDto(
|
||||
flatObjectMetadataToDelete,
|
||||
);
|
||||
}
|
||||
|
||||
async createOne({
|
||||
createObjectInput,
|
||||
workspaceId,
|
||||
}: {
|
||||
createObjectInput: Omit<CreateObjectInput, 'workspaceId'>;
|
||||
workspaceId: string;
|
||||
}): Promise<FlatObjectMetadata> {
|
||||
const { flatObjectMetadataMaps: existingFlatObjectMetadataMaps } =
|
||||
@@ -40,7 +137,7 @@ export class ObjectMetadataServiceV2 {
|
||||
|
||||
const flatObjectMetadataToCreate =
|
||||
fromCreateObjectInputToFlatObjectMetadata({
|
||||
objectMetadataInput,
|
||||
createObjectInput,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
@@ -80,6 +177,23 @@ export class ObjectMetadataServiceV2 {
|
||||
);
|
||||
}
|
||||
|
||||
return flatObjectMetadataToCreate; // TODO retrieve from cache
|
||||
const { flatObjectMetadataMaps: recomputedFlatObjectMetadataMaps } =
|
||||
await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps(
|
||||
{
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
const createdFlatObjectMetadata =
|
||||
recomputedFlatObjectMetadataMaps.byId[flatObjectMetadataToCreate.id];
|
||||
|
||||
if (!isDefined(createdFlatObjectMetadata)) {
|
||||
throw new ObjectMetadataException(
|
||||
'Fail to find just created object metadata',
|
||||
ObjectMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return createdFlatObjectMetadata;
|
||||
}
|
||||
}
|
||||
|
||||
+48
-40
@@ -96,18 +96,18 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
}
|
||||
|
||||
override async createOne(
|
||||
objectMetadataInput: CreateObjectInput,
|
||||
createObjectInput: CreateObjectInput,
|
||||
): Promise<ObjectMetadataEntity> {
|
||||
const isWorkspaceMigrationV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
|
||||
objectMetadataInput.workspaceId,
|
||||
createObjectInput.workspaceId,
|
||||
);
|
||||
|
||||
if (isWorkspaceMigrationV2Enabled) {
|
||||
const flatObjectMetadata = await this.objectMetadataServiceV2.createOne({
|
||||
objectMetadataInput,
|
||||
workspaceId: objectMetadataInput.workspaceId,
|
||||
createObjectInput,
|
||||
workspaceId: createObjectInput.workspaceId,
|
||||
});
|
||||
|
||||
// Since V2 returns FlatObjectMetadata, we need to fetch the created entity
|
||||
@@ -115,7 +115,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
{
|
||||
where: {
|
||||
id: flatObjectMetadata.id,
|
||||
workspaceId: objectMetadataInput.workspaceId,
|
||||
workspaceId: createObjectInput.workspaceId,
|
||||
},
|
||||
},
|
||||
);
|
||||
@@ -144,61 +144,56 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
const { objectMetadataMaps } =
|
||||
await this.workspaceMetadataCacheService.getExistingOrRecomputeMetadataMaps(
|
||||
{
|
||||
workspaceId: objectMetadataInput.workspaceId,
|
||||
workspaceId: createObjectInput.workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
const lastDataSourceMetadata =
|
||||
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
|
||||
objectMetadataInput.workspaceId,
|
||||
createObjectInput.workspaceId,
|
||||
);
|
||||
|
||||
objectMetadataInput.labelSingular = capitalize(
|
||||
objectMetadataInput.labelSingular,
|
||||
);
|
||||
objectMetadataInput.labelPlural = capitalize(
|
||||
objectMetadataInput.labelPlural,
|
||||
createObjectInput.labelSingular = capitalize(
|
||||
createObjectInput.labelSingular,
|
||||
);
|
||||
createObjectInput.labelPlural = capitalize(createObjectInput.labelPlural);
|
||||
|
||||
validateObjectMetadataInputNamesOrThrow(objectMetadataInput);
|
||||
validateObjectMetadataInputLabelsOrThrow(objectMetadataInput);
|
||||
validateObjectMetadataInputNamesOrThrow(createObjectInput);
|
||||
validateObjectMetadataInputLabelsOrThrow(createObjectInput);
|
||||
|
||||
validateLowerCasedAndTrimmedStringsAreDifferentOrThrow({
|
||||
inputs: [
|
||||
objectMetadataInput.nameSingular,
|
||||
objectMetadataInput.namePlural,
|
||||
],
|
||||
inputs: [createObjectInput.nameSingular, createObjectInput.namePlural],
|
||||
message:
|
||||
'The singular and plural names cannot be the same for an object',
|
||||
});
|
||||
validateLowerCasedAndTrimmedStringsAreDifferentOrThrow({
|
||||
inputs: [
|
||||
objectMetadataInput.labelPlural,
|
||||
objectMetadataInput.labelSingular,
|
||||
createObjectInput.labelPlural,
|
||||
createObjectInput.labelSingular,
|
||||
],
|
||||
message:
|
||||
'The singular and plural labels cannot be the same for an object',
|
||||
});
|
||||
|
||||
if (objectMetadataInput.isLabelSyncedWithName === true) {
|
||||
if (createObjectInput.isLabelSyncedWithName === true) {
|
||||
validateNameAndLabelAreSyncOrThrow({
|
||||
label: objectMetadataInput.labelSingular,
|
||||
name: objectMetadataInput.nameSingular,
|
||||
label: createObjectInput.labelSingular,
|
||||
name: createObjectInput.nameSingular,
|
||||
});
|
||||
validateNameAndLabelAreSyncOrThrow({
|
||||
label: objectMetadataInput.labelPlural,
|
||||
name: objectMetadataInput.namePlural,
|
||||
label: createObjectInput.labelPlural,
|
||||
name: createObjectInput.namePlural,
|
||||
});
|
||||
}
|
||||
|
||||
validatesNoOtherObjectWithSameNameExistsOrThrows({
|
||||
objectMetadataNamePlural: objectMetadataInput.namePlural,
|
||||
objectMetadataNameSingular: objectMetadataInput.nameSingular,
|
||||
objectMetadataNamePlural: createObjectInput.namePlural,
|
||||
objectMetadataNameSingular: createObjectInput.nameSingular,
|
||||
objectMetadataMaps,
|
||||
});
|
||||
|
||||
const baseCustomFields = buildDefaultFieldsForCustomObject(
|
||||
objectMetadataInput.workspaceId,
|
||||
createObjectInput.workspaceId,
|
||||
);
|
||||
|
||||
const labelIdentifierFieldMetadataId = baseCustomFields.find(
|
||||
@@ -213,24 +208,24 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
}
|
||||
|
||||
const createdObjectMetadata = await objectMetadataRepository.save({
|
||||
...objectMetadataInput,
|
||||
...createObjectInput,
|
||||
dataSourceId: lastDataSourceMetadata.id,
|
||||
targetTableName: 'DEPRECATED',
|
||||
isActive: true,
|
||||
isCustom: !objectMetadataInput.isRemote,
|
||||
isCustom: !createObjectInput.isRemote,
|
||||
isSystem: false,
|
||||
isRemote: objectMetadataInput.isRemote,
|
||||
isSearchable: !objectMetadataInput.isRemote,
|
||||
fields: objectMetadataInput.isRemote ? [] : baseCustomFields,
|
||||
isRemote: createObjectInput.isRemote,
|
||||
isSearchable: !createObjectInput.isRemote,
|
||||
fields: createObjectInput.isRemote ? [] : baseCustomFields,
|
||||
labelIdentifierFieldMetadataId,
|
||||
});
|
||||
|
||||
if (objectMetadataInput.isRemote) {
|
||||
if (createObjectInput.isRemote) {
|
||||
throw new Error('Remote objects are not supported yet');
|
||||
} else {
|
||||
const createdRelatedObjectMetadataCollection =
|
||||
await this.objectMetadataFieldRelationService.createRelationsAndForeignKeysMetadata(
|
||||
objectMetadataInput.workspaceId,
|
||||
createObjectInput.workspaceId,
|
||||
createdObjectMetadata,
|
||||
objectMetadataMaps,
|
||||
queryRunner,
|
||||
@@ -254,7 +249,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
);
|
||||
|
||||
await this.searchVectorService.createSearchVectorFieldForObject(
|
||||
objectMetadataInput,
|
||||
createObjectInput,
|
||||
createdObjectMetadata,
|
||||
queryRunner,
|
||||
);
|
||||
@@ -270,7 +265,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
// After commit, do non-transactional work
|
||||
await this.workspacePermissionsCacheService.recomputeRolesPermissionsCache(
|
||||
{
|
||||
workspaceId: objectMetadataInput.workspaceId,
|
||||
workspaceId: createObjectInput.workspaceId,
|
||||
},
|
||||
);
|
||||
await this.objectMetadataRelatedRecordsService.createObjectRelatedRecords(
|
||||
@@ -278,7 +273,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
);
|
||||
|
||||
await this.workspaceMetadataVersionService.incrementMetadataVersion(
|
||||
objectMetadataInput.workspaceId,
|
||||
createObjectInput.workspaceId,
|
||||
);
|
||||
|
||||
return createdObjectMetadata;
|
||||
@@ -455,9 +450,22 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
}
|
||||
|
||||
public async deleteOneObject(
|
||||
input: DeleteOneObjectInput,
|
||||
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 mainDataSource =
|
||||
await this.workspaceDataSourceService.connectToMainDataSource();
|
||||
const queryRunner = mainDataSource.createQueryRunner();
|
||||
@@ -479,7 +487,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
'fields.relationTargetFieldMetadata.object',
|
||||
],
|
||||
where: {
|
||||
id: input.id,
|
||||
id: deleteObjectInput.id,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
+15
@@ -125,6 +125,7 @@ export const buildDefaultFlatFieldMetadataForCustomObject = ({
|
||||
workspaceId,
|
||||
objectMetadataId,
|
||||
}: BuildDefaultFlatFieldMetadataForCustomObjectArgs) => {
|
||||
const createdAt = new Date();
|
||||
const idField: FlatFieldMetadata<FieldMetadataType.UUID> = {
|
||||
type: FieldMetadataType.UUID,
|
||||
id: v4(),
|
||||
@@ -144,6 +145,8 @@ export const buildDefaultFlatFieldMetadataForCustomObject = ({
|
||||
isSystem: true,
|
||||
defaultValue: 'uuid',
|
||||
|
||||
createdAt,
|
||||
updatedAt: createdAt,
|
||||
flatRelationTargetFieldMetadata: null,
|
||||
flatRelationTargetObjectMetadata: null,
|
||||
options: null,
|
||||
@@ -172,6 +175,8 @@ export const buildDefaultFlatFieldMetadataForCustomObject = ({
|
||||
isSystem: false,
|
||||
defaultValue: "'Untitled'",
|
||||
|
||||
createdAt,
|
||||
updatedAt: createdAt,
|
||||
flatRelationTargetFieldMetadata: null,
|
||||
flatRelationTargetObjectMetadata: null,
|
||||
options: null,
|
||||
@@ -200,6 +205,8 @@ export const buildDefaultFlatFieldMetadataForCustomObject = ({
|
||||
isSystem: false,
|
||||
defaultValue: 'now',
|
||||
|
||||
createdAt,
|
||||
updatedAt: createdAt,
|
||||
flatRelationTargetFieldMetadata: null,
|
||||
flatRelationTargetObjectMetadata: null,
|
||||
options: null,
|
||||
@@ -228,6 +235,8 @@ export const buildDefaultFlatFieldMetadataForCustomObject = ({
|
||||
isSystem: false,
|
||||
defaultValue: 'now',
|
||||
|
||||
createdAt,
|
||||
updatedAt: createdAt,
|
||||
flatRelationTargetFieldMetadata: null,
|
||||
flatRelationTargetObjectMetadata: null,
|
||||
options: null,
|
||||
@@ -256,6 +265,8 @@ export const buildDefaultFlatFieldMetadataForCustomObject = ({
|
||||
isSystem: false,
|
||||
defaultValue: null,
|
||||
|
||||
createdAt,
|
||||
updatedAt: createdAt,
|
||||
flatRelationTargetFieldMetadata: null,
|
||||
flatRelationTargetObjectMetadata: null,
|
||||
options: null,
|
||||
@@ -284,6 +295,8 @@ export const buildDefaultFlatFieldMetadataForCustomObject = ({
|
||||
isSystem: false,
|
||||
defaultValue: { name: "''", source: "'MANUAL'" },
|
||||
|
||||
createdAt,
|
||||
updatedAt: createdAt,
|
||||
flatRelationTargetFieldMetadata: null,
|
||||
flatRelationTargetObjectMetadata: null,
|
||||
options: null,
|
||||
@@ -312,6 +325,8 @@ export const buildDefaultFlatFieldMetadataForCustomObject = ({
|
||||
isSystem: true,
|
||||
defaultValue: 0,
|
||||
|
||||
createdAt,
|
||||
updatedAt: createdAt,
|
||||
flatRelationTargetFieldMetadata: null,
|
||||
flatRelationTargetObjectMetadata: null,
|
||||
options: null,
|
||||
|
||||
Reference in New Issue
Block a user