ObjectMetadataServiceV2.createOne (#13849)
# Introduction Introducing v2 object metadata service create one handler v2, not overkilling the flatObjectMetadata validation for the moment that will require sequential validation for the import in order to handle relations and morph relations
This commit is contained in:
+154
@@ -0,0 +1,154 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
import { FlatFieldMetadataValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-validator.service';
|
||||
import { FailedFlatFieldMetadataValidationExceptions } from 'src/engine/metadata-modules/flat-field-metadata/types/failed-flat-field-metadata-validation.type';
|
||||
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
|
||||
import { addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-field-metadata-in-flat-object-metadata-maps-or-throw.util';
|
||||
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 { FailedFlatObjectMetadataValidationExceptions } from 'src/engine/metadata-modules/flat-object-metadata/types/failed-flat-object-metadata-validation.type';
|
||||
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { validateFlatObjectMetadataLabel } from 'src/engine/metadata-modules/flat-object-metadata/validators/validate-flat-object-metadata-label.validator';
|
||||
import { validateFlatObjectMetadataNames } from 'src/engine/metadata-modules/flat-object-metadata/validators/validate-flat-object-metadata-name.validator';
|
||||
import {
|
||||
ObjectMetadataException,
|
||||
ObjectMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||
import { computeMetadataNameFromLabel } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util';
|
||||
import { doesOtherObjectWithSameNameExists } from 'src/engine/metadata-modules/utils/validate-no-other-object-with-same-name-exists-or-throw.util';
|
||||
|
||||
export type ValidateOneFlatObjectMetadataArgs = {
|
||||
existingFlatObjectMetadataMaps: FlatObjectMetadataMaps;
|
||||
flatObjectMetadataToValidate: FlatObjectMetadata;
|
||||
otherFlatObjectMetadataMapsToValidate?: FlatObjectMetadataMaps;
|
||||
workspaceId: string;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class FlatObjectMetadataValidatorService {
|
||||
constructor(
|
||||
private readonly flatFieldMetadataValidatorService: FlatFieldMetadataValidatorService,
|
||||
) {}
|
||||
|
||||
public async validateFlatObjectMetadataCreation({
|
||||
existingFlatObjectMetadataMaps,
|
||||
flatObjectMetadataToValidate,
|
||||
workspaceId,
|
||||
}: ValidateOneFlatObjectMetadataArgs) {
|
||||
const errors: FailedFlatObjectMetadataValidationExceptions[] = [];
|
||||
|
||||
errors.push(
|
||||
...validateFlatObjectMetadataNames({
|
||||
namePlural: flatObjectMetadataToValidate.namePlural,
|
||||
nameSingular: flatObjectMetadataToValidate.nameSingular,
|
||||
}),
|
||||
);
|
||||
|
||||
errors.push(
|
||||
...validateFlatObjectMetadataLabel({
|
||||
labelPlural: flatObjectMetadataToValidate.labelPlural,
|
||||
labelSingular: flatObjectMetadataToValidate.labelSingular,
|
||||
}),
|
||||
);
|
||||
|
||||
if (flatObjectMetadataToValidate.isRemote) {
|
||||
errors.push(
|
||||
new ObjectMetadataException(
|
||||
t`Remote objects are not supported yet`,
|
||||
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (flatObjectMetadataToValidate.isLabelSyncedWithName === true) {
|
||||
const computedNameSingular = computeMetadataNameFromLabel(
|
||||
flatObjectMetadataToValidate.labelSingular,
|
||||
);
|
||||
|
||||
if (computedNameSingular !== flatObjectMetadataToValidate.nameSingular) {
|
||||
errors.push(
|
||||
new ObjectMetadataException(
|
||||
t`Singular name is not synced with singular label`,
|
||||
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const computedNamePlural = computeMetadataNameFromLabel(
|
||||
flatObjectMetadataToValidate.labelPlural,
|
||||
);
|
||||
|
||||
if (computedNamePlural !== flatObjectMetadataToValidate.namePlural) {
|
||||
errors.push(
|
||||
new ObjectMetadataException(
|
||||
t`Plural name is not synced with plural label`,
|
||||
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
doesOtherObjectWithSameNameExists({
|
||||
objectMetadataNamePlural: flatObjectMetadataToValidate.namePlural,
|
||||
objectMetadataNameSingular: flatObjectMetadataToValidate.nameSingular,
|
||||
objectMetadataMaps: existingFlatObjectMetadataMaps,
|
||||
})
|
||||
) {
|
||||
errors.push(
|
||||
new ObjectMetadataException(
|
||||
'Object already exists',
|
||||
ObjectMetadataExceptionCode.OBJECT_ALREADY_EXISTS,
|
||||
{
|
||||
userFriendlyMessage: t`Object already exists`,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const allFlatFieldMetadatasValidationErrors: FailedFlatFieldMetadataValidationExceptions[] =
|
||||
[];
|
||||
let existingFlatObjectMetadataMapsWithFlatObjectMetadataToBeCreatedWithoutFields =
|
||||
addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadata: {
|
||||
...flatObjectMetadataToValidate,
|
||||
flatFieldMetadatas: [],
|
||||
},
|
||||
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
for (const flatFieldMetadataToValidate of flatObjectMetadataToValidate.flatFieldMetadatas) {
|
||||
const otherFlatObjectMetadataMapsToValidate = undefined; // TODO prastoin when implementing import
|
||||
|
||||
const flatFieldValidatorErrors =
|
||||
await this.flatFieldMetadataValidatorService.validateFlatFieldMetadataCreation(
|
||||
{
|
||||
existingFlatObjectMetadataMaps:
|
||||
existingFlatObjectMetadataMapsWithFlatObjectMetadataToBeCreatedWithoutFields,
|
||||
flatFieldMetadataToValidate,
|
||||
workspaceId,
|
||||
otherFlatObjectMetadataMapsToValidate,
|
||||
},
|
||||
);
|
||||
|
||||
if (flatFieldValidatorErrors.length > 0) {
|
||||
allFlatFieldMetadatasValidationErrors.push(...flatFieldValidatorErrors);
|
||||
continue;
|
||||
}
|
||||
|
||||
existingFlatObjectMetadataMapsWithFlatObjectMetadataToBeCreatedWithoutFields =
|
||||
addFlatFieldMetadataInFlatObjectMetadataMapsOrThrow({
|
||||
flatFieldMetadata: flatFieldMetadataToValidate,
|
||||
flatObjectMetadataMaps:
|
||||
existingFlatObjectMetadataMapsWithFlatObjectMetadataToBeCreatedWithoutFields,
|
||||
});
|
||||
}
|
||||
|
||||
if (allFlatFieldMetadatasValidationErrors.length > 0) {
|
||||
errors.push(...allFlatFieldMetadatasValidationErrors);
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import { type FieldMetadataException } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||
import { type ObjectMetadataException } from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||
import { type InvalidMetadataException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception';
|
||||
|
||||
export type FailedFlatObjectMetadataValidationExceptions =
|
||||
| FieldMetadataException
|
||||
| ObjectMetadataException
|
||||
| InvalidMetadataException;
|
||||
+18
-22
@@ -1,24 +1,20 @@
|
||||
import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'twenty-shared/utils';
|
||||
import {
|
||||
capitalize,
|
||||
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
|
||||
} from 'twenty-shared/utils';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { type CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input';
|
||||
import {
|
||||
ObjectMetadataException,
|
||||
ObjectMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||
import { buildDefaultFlatFieldMetadataForCustomObject } from 'src/engine/metadata-modules/object-metadata/utils/build-default-fields-for-custom-object.util';
|
||||
|
||||
export const fromCreateObjectInputToFlatObjectMetadata = (
|
||||
rawCreateObjectInput: CreateObjectInput,
|
||||
): FlatObjectMetadata => {
|
||||
if (rawCreateObjectInput.isRemote) {
|
||||
throw new ObjectMetadataException(
|
||||
'Remote objects are not supported',
|
||||
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
);
|
||||
}
|
||||
|
||||
export const fromCreateObjectInputToFlatObjectMetadata = ({
|
||||
objectMetadataInput: rawCreateObjectInput,
|
||||
workspaceId,
|
||||
}: {
|
||||
objectMetadataInput: Omit<CreateObjectInput, 'workspaceId'>;
|
||||
workspaceId: string;
|
||||
}): FlatObjectMetadata => {
|
||||
const createObjectInput =
|
||||
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
|
||||
rawCreateObjectInput,
|
||||
@@ -37,7 +33,7 @@ export const fromCreateObjectInputToFlatObjectMetadata = (
|
||||
const baseCustomFlatFieldMetadatas =
|
||||
buildDefaultFlatFieldMetadataForCustomObject({
|
||||
objectMetadataId,
|
||||
workspaceId: createObjectInput.workspaceId,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -51,19 +47,19 @@ export const fromCreateObjectInputToFlatObjectMetadata = (
|
||||
isAuditLogged: true,
|
||||
isCustom: true,
|
||||
isLabelSyncedWithName: createObjectInput.isLabelSyncedWithName ?? false,
|
||||
isRemote: false,
|
||||
isRemote: createObjectInput.isRemote ?? false,
|
||||
isSearchable: true,
|
||||
isSystem: false,
|
||||
labelIdentifierFieldMetadataId: baseCustomFlatFieldMetadatas.nameField.id,
|
||||
labelPlural: createObjectInput.labelPlural ?? null,
|
||||
labelSingular: createObjectInput.labelSingular ?? null,
|
||||
namePlural: createObjectInput.namePlural ?? null,
|
||||
nameSingular: createObjectInput.nameSingular ?? null,
|
||||
labelPlural: capitalize(createObjectInput.labelPlural),
|
||||
labelSingular: capitalize(createObjectInput.labelSingular),
|
||||
namePlural: createObjectInput.namePlural,
|
||||
nameSingular: createObjectInput.nameSingular,
|
||||
shortcut: createObjectInput.shortcut ?? null,
|
||||
standardId: null,
|
||||
standardOverrides: null,
|
||||
uniqueIdentifier: objectMetadataId,
|
||||
targetTableName: 'DEPRECATED',
|
||||
workspaceId: createObjectInput.workspaceId,
|
||||
workspaceId,
|
||||
};
|
||||
};
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { type FailedFlatObjectMetadataValidationExceptions } from 'src/engine/metadata-modules/flat-object-metadata/types/failed-flat-object-metadata-validation.type';
|
||||
import {
|
||||
ObjectMetadataException,
|
||||
ObjectMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||
import { type FlatMetadataValidator } from 'src/engine/metadata-modules/types/flat-metadata-validator.type';
|
||||
|
||||
export const runFlatObjectMetadataValidator = <T>(
|
||||
elementToValidate: T,
|
||||
{ message, validator }: FlatMetadataValidator<T>,
|
||||
): FailedFlatObjectMetadataValidationExceptions | undefined => {
|
||||
const validationFailed = validator(elementToValidate);
|
||||
|
||||
if (validationFailed) {
|
||||
return new ObjectMetadataException(
|
||||
message,
|
||||
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
{
|
||||
userFriendlyMessage: message,
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FailedFlatObjectMetadataValidationExceptions } from 'src/engine/metadata-modules/flat-object-metadata/types/failed-flat-object-metadata-validation.type';
|
||||
import { runFlatObjectMetadataValidator } from 'src/engine/metadata-modules/flat-object-metadata/utils/run-flat-object-metadata-validator.util';
|
||||
import { type FlatMetadataValidator } from 'src/engine/metadata-modules/types/flat-metadata-validator.type';
|
||||
|
||||
export const runFlatObjectMetadataValidators = <T>(
|
||||
elementToValidate: T,
|
||||
validators: FlatMetadataValidator<T>[],
|
||||
): FailedFlatObjectMetadataValidationExceptions[] =>
|
||||
validators
|
||||
.map((validator) =>
|
||||
runFlatObjectMetadataValidator(elementToValidate, validator),
|
||||
)
|
||||
.filter(isDefined);
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
import { type FailedFlatObjectMetadataValidationExceptions } from 'src/engine/metadata-modules/flat-object-metadata/types/failed-flat-object-metadata-validation.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { runFlatObjectMetadataValidators } from 'src/engine/metadata-modules/flat-object-metadata/utils/run-flat-object-metadata-validators.util';
|
||||
import {
|
||||
ObjectMetadataException,
|
||||
ObjectMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||
import { type FlatMetadataValidator } from 'src/engine/metadata-modules/types/flat-metadata-validator.type';
|
||||
import {
|
||||
beneathDatabaseIdentifierMinimumLength,
|
||||
exceedsDatabaseIdentifierMaximumLength,
|
||||
} from 'src/engine/metadata-modules/utils/validate-database-identifier-length.utils';
|
||||
|
||||
export const validateFlatObjectMetadataLabel = ({
|
||||
labelPlural,
|
||||
labelSingular,
|
||||
}: Pick<
|
||||
FlatObjectMetadata,
|
||||
'labelPlural' | 'labelSingular'
|
||||
>): FailedFlatObjectMetadataValidationExceptions[] => {
|
||||
const errors: FailedFlatObjectMetadataValidationExceptions[] = [];
|
||||
const validators: FlatMetadataValidator<string>[] = [
|
||||
{
|
||||
validator: (label) => beneathDatabaseIdentifierMinimumLength(label),
|
||||
message: t`Object label is too short`,
|
||||
},
|
||||
{
|
||||
validator: (label) => exceedsDatabaseIdentifierMaximumLength(label),
|
||||
message: t`Object label is too long`,
|
||||
},
|
||||
];
|
||||
|
||||
errors.push(
|
||||
...[labelSingular, labelPlural].flatMap((label) =>
|
||||
runFlatObjectMetadataValidators(label, validators),
|
||||
),
|
||||
);
|
||||
|
||||
const labelsAreIdentical =
|
||||
labelSingular.trim().toLowerCase() === labelPlural.trim().toLowerCase();
|
||||
|
||||
if (labelsAreIdentical) {
|
||||
errors.push(
|
||||
new ObjectMetadataException(
|
||||
t`The singular and plural labels cannot be the same for an object`,
|
||||
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
import { type FailedFlatObjectMetadataValidationExceptions } from 'src/engine/metadata-modules/flat-object-metadata/types/failed-flat-object-metadata-validation.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { runFlatObjectMetadataValidators } from 'src/engine/metadata-modules/flat-object-metadata/utils/run-flat-object-metadata-validators.util';
|
||||
import {
|
||||
ObjectMetadataException,
|
||||
ObjectMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||
import { METADATA_NAME_VALIDATORS } from 'src/engine/metadata-modules/utils/constants/metadata-name-flat-metadata-validators.constants';
|
||||
|
||||
export const validateFlatObjectMetadataNames = ({
|
||||
namePlural,
|
||||
nameSingular,
|
||||
}: Pick<FlatObjectMetadata, 'nameSingular' | 'namePlural'>) => {
|
||||
const errors: FailedFlatObjectMetadataValidationExceptions[] = [];
|
||||
|
||||
errors.push(
|
||||
...[nameSingular, namePlural].flatMap((name) =>
|
||||
runFlatObjectMetadataValidators(name, METADATA_NAME_VALIDATORS),
|
||||
),
|
||||
);
|
||||
|
||||
const namesAreIdentical =
|
||||
namePlural.trim().toLowerCase() === nameSingular.trim().toLowerCase();
|
||||
|
||||
if (namesAreIdentical) {
|
||||
errors.push(
|
||||
new ObjectMetadataException(
|
||||
t`The singular and plural names cannot be the same for an object`,
|
||||
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
Reference in New Issue
Block a user