Field metadata and object metadata v1 relicas (#16230)
# Introduction Related https://github.com/twentyhq/core-team-issues/issues/1911 Nearly done with all functional v1 implemen removal Will remain dead code methods that I will detect using knip
This commit is contained in:
-11
@@ -1,11 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`validateFieldNameAvailabilityOrThrow throws error with ACTOR suffixes 1`] = `"Name "fieldActorName" is not available"`;
|
||||
|
||||
exports[`validateFieldNameAvailabilityOrThrow throws error with ADDRESS suffixes 1`] = `"Name "fieldAddressAddressStreet1" is not available"`;
|
||||
|
||||
exports[`validateFieldNameAvailabilityOrThrow throws error with CURRENCY suffixes 1`] = `"Name "fieldCurrencyAmountMicros" is not available"`;
|
||||
|
||||
exports[`validateFieldNameAvailabilityOrThrow throws error with FULL_NAME suffixes 1`] = `"Name "fieldFullNameFirstName" is not available"`;
|
||||
|
||||
exports[`validateFieldNameAvailabilityOrThrow throws error with LINKS suffixes 1`] = `"Name "fieldLinksPrimaryLinkLabel" is not available"`;
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`validateMetadataNameOrThrow throw error when string is not in camel case 1`] = `"TestName should be in camelCase"`;
|
||||
|
||||
exports[`validateMetadataNameOrThrow throws error when starts with digits 1`] = `"123string should be in camelCase"`;
|
||||
|
||||
exports[`validateMetadataNameOrThrow throws error when string has non latin characters 1`] = `"String "בְרִבְרִ" is not valid: must start with lowercase letter and contain only alphanumeric letters"`;
|
||||
|
||||
exports[`validateMetadataNameOrThrow throws error when string has spaces 1`] = `"name with spaces should be in camelCase"`;
|
||||
|
||||
exports[`validateMetadataNameOrThrow throws error when string is a reserved word 1`] = `"The name "role" is not available"`;
|
||||
|
||||
exports[`validateMetadataNameOrThrow throws error when string is above 63 characters 1`] = `"Name is too long: it exceeds the 63 characters limit."`;
|
||||
|
||||
exports[`validateMetadataNameOrThrow throws error when string is empty 1`] = `"Input is too short: """`;
|
||||
|
||||
exports[`validateMetadataNameOrThrow throws error when string starts with capital letter 1`] = `"StringStartingWithCapitalLetter should be in camelCase"`;
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
import { type EachTestingContext } from 'twenty-shared/testing';
|
||||
|
||||
import { computeMetadataNameFromLabel } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util';
|
||||
import { computeMetadataNameFromLabelOrThrow } from 'src/engine/metadata-modules/utils/compute-metadata-name-from-label-or-throw.util';
|
||||
import {
|
||||
InvalidMetadataException,
|
||||
InvalidMetadataExceptionCode,
|
||||
@@ -124,7 +124,7 @@ describe('computeMetadataNameFromLabel', () => {
|
||||
|
||||
describe('successful cases', () => {
|
||||
it.each(successfulTestCases)('$title', ({ context }) => {
|
||||
const result = computeMetadataNameFromLabel(context.input);
|
||||
const result = computeMetadataNameFromLabelOrThrow(context.input);
|
||||
|
||||
expect(result).toBe(context.expected);
|
||||
});
|
||||
@@ -132,7 +132,7 @@ describe('computeMetadataNameFromLabel', () => {
|
||||
|
||||
describe('failing cases', () => {
|
||||
it.each(failingTestCases)('$title', ({ context }) => {
|
||||
expect(() => computeMetadataNameFromLabel(context.input)).toThrow(
|
||||
expect(() => computeMetadataNameFromLabelOrThrow(context.input)).toThrow(
|
||||
context.expectToThrow?.error,
|
||||
);
|
||||
});
|
||||
|
||||
-180
@@ -1,180 +0,0 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { type EachTestingContext } from 'twenty-shared/testing';
|
||||
|
||||
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { getFlatFieldMetadataMock } from 'src/engine/metadata-modules/flat-field-metadata/__mocks__/get-flat-field-metadata.mock';
|
||||
import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock';
|
||||
import { validateFieldNameAvailabilityOrThrow } from 'src/engine/metadata-modules/utils/validate-field-name-availability.utils';
|
||||
|
||||
const FIELD_LINKS_MOCK_NAME = 'fieldLinks';
|
||||
const FIELD_CURRENCY_MOCK_NAME = 'fieldCurrency';
|
||||
const FIELD_ADDRESS_MOCK_NAME = 'fieldAddress';
|
||||
const FIELD_ACTOR_MOCK_NAME = 'fieldActor';
|
||||
const FIELD_FULL_NAME_MOCK_NAME = 'fieldFullName';
|
||||
|
||||
const objectMetadataId = '20202020-0000-0000-0000-000000000001';
|
||||
const workspaceId = '20202020-0000-0000-0000-000000000000';
|
||||
|
||||
const createFlatFieldMetadata = (
|
||||
id: string,
|
||||
name: string,
|
||||
type: FieldMetadataType,
|
||||
): FlatFieldMetadata => {
|
||||
return getFlatFieldMetadataMock({
|
||||
id,
|
||||
name,
|
||||
type,
|
||||
objectMetadataId,
|
||||
universalIdentifier: id,
|
||||
workspaceId,
|
||||
});
|
||||
};
|
||||
|
||||
const fieldLinksMock = createFlatFieldMetadata(
|
||||
'fieldLinksId',
|
||||
FIELD_LINKS_MOCK_NAME,
|
||||
FieldMetadataType.LINKS,
|
||||
);
|
||||
const fieldCurrencyMock = createFlatFieldMetadata(
|
||||
'fieldCurrencyId',
|
||||
FIELD_CURRENCY_MOCK_NAME,
|
||||
FieldMetadataType.CURRENCY,
|
||||
);
|
||||
const fieldFullNameMock = createFlatFieldMetadata(
|
||||
'fieldFullNameId',
|
||||
FIELD_FULL_NAME_MOCK_NAME,
|
||||
FieldMetadataType.FULL_NAME,
|
||||
);
|
||||
const fieldActorMock = createFlatFieldMetadata(
|
||||
'fieldActorId',
|
||||
FIELD_ACTOR_MOCK_NAME,
|
||||
FieldMetadataType.ACTOR,
|
||||
);
|
||||
const fieldAddressMock = createFlatFieldMetadata(
|
||||
'fieldAddressId',
|
||||
FIELD_ADDRESS_MOCK_NAME,
|
||||
FieldMetadataType.ADDRESS,
|
||||
);
|
||||
|
||||
const FIELDS_MOCK = [
|
||||
fieldLinksMock,
|
||||
fieldCurrencyMock,
|
||||
fieldFullNameMock,
|
||||
fieldActorMock,
|
||||
fieldAddressMock,
|
||||
];
|
||||
|
||||
const flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata> =
|
||||
FIELDS_MOCK.reduce(
|
||||
(acc, field) => ({
|
||||
...acc,
|
||||
byId: {
|
||||
...acc.byId,
|
||||
[field.id]: field,
|
||||
},
|
||||
}),
|
||||
createEmptyFlatEntityMaps() as FlatEntityMaps<FlatFieldMetadata>,
|
||||
);
|
||||
|
||||
const flatObjectMetadata = getFlatObjectMetadataMock({
|
||||
id: objectMetadataId,
|
||||
workspaceId,
|
||||
nameSingular: 'objectName',
|
||||
namePlural: 'objectNames',
|
||||
labelSingular: 'Object Name',
|
||||
labelPlural: 'Object Names',
|
||||
description: 'Object description',
|
||||
icon: 'Icon123',
|
||||
isCustom: false,
|
||||
isRemote: false,
|
||||
isActive: true,
|
||||
isSystem: false,
|
||||
isAuditLogged: true,
|
||||
isSearchable: true,
|
||||
fieldMetadataIds: FIELDS_MOCK.map((f) => f.id),
|
||||
indexMetadataIds: [],
|
||||
viewIds: [],
|
||||
universalIdentifier: objectMetadataId,
|
||||
applicationId: null,
|
||||
labelIdentifierFieldMetadataId: null,
|
||||
imageIdentifierFieldMetadataId: null,
|
||||
shortcut: null,
|
||||
isLabelSyncedWithName: true,
|
||||
standardId: null,
|
||||
standardOverrides: null,
|
||||
targetTableName: 'DEPRECATED',
|
||||
duplicateCriteria: null,
|
||||
});
|
||||
|
||||
type ValidateFieldNameAvailabilityTestContext = EachTestingContext<{
|
||||
input: string;
|
||||
shouldNotThrow?: true;
|
||||
}>;
|
||||
|
||||
const validateFieldNameAvailabilityTestCases: ValidateFieldNameAvailabilityTestContext[] =
|
||||
[
|
||||
{
|
||||
title: 'does not throw if name is not reserved',
|
||||
context: {
|
||||
input: 'testName',
|
||||
shouldNotThrow: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'throws error with LINKS suffixes',
|
||||
context: {
|
||||
input: `${FIELD_LINKS_MOCK_NAME}PrimaryLinkLabel`,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'throws error with CURRENCY suffixes',
|
||||
context: {
|
||||
input: `${FIELD_CURRENCY_MOCK_NAME}AmountMicros`,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'throws error with FULL_NAME suffixes',
|
||||
context: {
|
||||
input: `${FIELD_FULL_NAME_MOCK_NAME}FirstName`,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'throws error with ACTOR suffixes',
|
||||
context: {
|
||||
input: `${FIELD_ACTOR_MOCK_NAME}Name`,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'throws error with ADDRESS suffixes',
|
||||
context: {
|
||||
input: `${FIELD_ADDRESS_MOCK_NAME}AddressStreet1`,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('validateFieldNameAvailabilityOrThrow', () => {
|
||||
it.each(validateFieldNameAvailabilityTestCases)(
|
||||
'$title',
|
||||
({ context: { input, shouldNotThrow } }) => {
|
||||
if (shouldNotThrow) {
|
||||
expect(() =>
|
||||
validateFieldNameAvailabilityOrThrow({
|
||||
name: input,
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
}),
|
||||
).not.toThrow();
|
||||
} else {
|
||||
expect(() =>
|
||||
validateFieldNameAvailabilityOrThrow({
|
||||
name: input,
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
}),
|
||||
).toThrowErrorMatchingSnapshot();
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
-88
@@ -1,88 +0,0 @@
|
||||
import { type EachTestingContext } from 'twenty-shared/testing';
|
||||
|
||||
import { validateMetadataNameOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-or-throw.utils';
|
||||
|
||||
type ValidateMetadataNameTestContext = EachTestingContext<{
|
||||
input: string;
|
||||
shouldNotThrow?: true;
|
||||
}>;
|
||||
|
||||
const validateMetadataNameTestCases: ValidateMetadataNameTestContext[] = [
|
||||
{
|
||||
title: 'validates when string is valid',
|
||||
context: {
|
||||
input: 'testName',
|
||||
shouldNotThrow: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'throw error when string is not in camel case',
|
||||
context: {
|
||||
input: 'TestName',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'throws error when string has spaces',
|
||||
context: {
|
||||
input: 'name with spaces',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'throws error when string is a reserved word',
|
||||
context: {
|
||||
input: 'role',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'throws error when string starts with capital letter',
|
||||
context: {
|
||||
input: 'StringStartingWithCapitalLetter',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'throws error when string has non latin characters',
|
||||
context: {
|
||||
input: 'בְרִבְרִ',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'throws error when starts with digits',
|
||||
context: {
|
||||
input: '123string',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'validates when string is less than 63 characters',
|
||||
context: {
|
||||
input: 'a'.repeat(63),
|
||||
shouldNotThrow: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'throws error when string is above 63 characters',
|
||||
context: {
|
||||
input: 'a'.repeat(64),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'throws error when string is empty',
|
||||
context: {
|
||||
input: '',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
describe('validateMetadataNameOrThrow', () => {
|
||||
it.each(validateMetadataNameTestCases)(
|
||||
'$title',
|
||||
({ context: { input, shouldNotThrow } }) => {
|
||||
if (shouldNotThrow) {
|
||||
expect(() => validateMetadataNameOrThrow(input)).not.toThrow();
|
||||
} else {
|
||||
expect(() =>
|
||||
validateMetadataNameOrThrow(input),
|
||||
).toThrowErrorMatchingSnapshot();
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
+1
-18
@@ -8,7 +8,7 @@ import {
|
||||
|
||||
// Server-specific wrapper that converts generic errors to InvalidMetadataException
|
||||
// This provides consistent error handling with proper exception codes for the server
|
||||
export const computeMetadataNameFromLabel = (label: string): string => {
|
||||
export const computeMetadataNameFromLabelOrThrow = (label: string): string => {
|
||||
if (!isDefined(label)) {
|
||||
throw new InvalidMetadataException(
|
||||
'Label is required',
|
||||
@@ -28,20 +28,3 @@ export const computeMetadataNameFromLabel = (label: string): string => {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const validateNameAndLabelAreSyncOrThrow = ({
|
||||
label,
|
||||
name,
|
||||
}: {
|
||||
label: string;
|
||||
name: string;
|
||||
}) => {
|
||||
const computedName = computeMetadataNameFromLabel(label);
|
||||
|
||||
if (name !== computedName) {
|
||||
throw new InvalidMetadataException(
|
||||
`Name is not synced with label. Expected name: "${computedName}", got ${name}`,
|
||||
InvalidMetadataExceptionCode.NAME_NOT_SYNCED_WITH_LABEL,
|
||||
);
|
||||
}
|
||||
};
|
||||
+3
-1
@@ -7,7 +7,9 @@ import {
|
||||
beneathDatabaseIdentifierMinimumLength,
|
||||
exceedsDatabaseIdentifierMaximumLength,
|
||||
} from 'src/engine/metadata-modules/utils/validate-database-identifier-length.utils';
|
||||
import { STARTS_WITH_LOWER_CASE_AND_CONTAINS_ONLY_CAPS_AND_LOWER_LETTERS_AND_NUMBER_STRING_REGEX } from 'src/engine/metadata-modules/utils/validate-metadata-name-start-with-lowercase-letter-and-contain-digits-nor-letters.utils';
|
||||
|
||||
const STARTS_WITH_LOWER_CASE_AND_CONTAINS_ONLY_CAPS_AND_LOWER_LETTERS_AND_NUMBER_STRING_REGEX =
|
||||
/^[a-z][a-zA-Z0-9]*$/;
|
||||
|
||||
export const METADATA_NAME_VALIDATORS: FlatMetadataValidator<string>[] = [
|
||||
{
|
||||
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
export const isArgDefinedIfProvidedOrThrow = ({
|
||||
input,
|
||||
key,
|
||||
value,
|
||||
}: {
|
||||
input: object;
|
||||
key: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
value: any;
|
||||
}) => {
|
||||
if (key in input && !isDefined(value)) {
|
||||
throw new Error(`${key} must be defined when provided`);
|
||||
}
|
||||
};
|
||||
-89
@@ -1,89 +0,0 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
compositeTypeDefinitions,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import {
|
||||
InvalidMetadataException,
|
||||
InvalidMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception';
|
||||
|
||||
const getReservedCompositeFieldNames = (
|
||||
flatObjectMetadata: FlatObjectMetadata,
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>,
|
||||
) => {
|
||||
const reservedCompositeFieldsNames: string[] = [];
|
||||
|
||||
for (const fieldId of flatObjectMetadata.fieldMetadataIds) {
|
||||
const field = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
flatEntityId: fieldId,
|
||||
});
|
||||
|
||||
if (isCompositeFieldMetadataType(field.type)) {
|
||||
const base = field.name;
|
||||
const compositeType = compositeTypeDefinitions.get(field.type);
|
||||
|
||||
compositeType?.properties.map((property) =>
|
||||
reservedCompositeFieldsNames.push(
|
||||
computeCompositeColumnName(base, property),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return reservedCompositeFieldsNames;
|
||||
};
|
||||
|
||||
type ValidateFieldNameAvailabilityOrThrowArgs = {
|
||||
name: string;
|
||||
flatObjectMetadata: FlatObjectMetadata;
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
};
|
||||
export const validateFieldNameAvailabilityOrThrow = ({
|
||||
name,
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
}: ValidateFieldNameAvailabilityOrThrowArgs) => {
|
||||
const reservedCompositeFieldsNames = getReservedCompositeFieldNames(
|
||||
flatObjectMetadata,
|
||||
flatFieldMetadataMaps,
|
||||
);
|
||||
|
||||
for (const fieldId of flatObjectMetadata.fieldMetadataIds) {
|
||||
const field = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
flatEntityId: fieldId,
|
||||
});
|
||||
|
||||
if (
|
||||
field.name === name ||
|
||||
(field.type === FieldMetadataType.RELATION && `${field.name}Id` === name)
|
||||
) {
|
||||
throw new InvalidMetadataException(
|
||||
`Name "${name}" is not available as it is already used by another field`,
|
||||
InvalidMetadataExceptionCode.NOT_AVAILABLE,
|
||||
{
|
||||
userFriendlyMessage: msg`This name is not available as it is already used by another field.`,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (reservedCompositeFieldsNames.includes(name)) {
|
||||
throw new InvalidMetadataException(
|
||||
`Name "${name}" is not available`,
|
||||
InvalidMetadataExceptionCode.RESERVED_KEYWORD,
|
||||
{
|
||||
userFriendlyMessage: msg`This name is not available.`,
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
-92
@@ -1,92 +0,0 @@
|
||||
import {
|
||||
isDefined,
|
||||
isLabelIdentifierFieldMetadataTypes,
|
||||
} from 'twenty-shared/utils';
|
||||
|
||||
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import {
|
||||
ObjectMetadataException,
|
||||
ObjectMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||
|
||||
type Validator = {
|
||||
validator: (args: {
|
||||
fieldMetadataId: string;
|
||||
matchingFieldMetadata?: FieldMetadataEntity;
|
||||
}) => boolean;
|
||||
label: string;
|
||||
};
|
||||
|
||||
type ValidateMetadataIdentifierFieldMetadataIdOrThrowArgs = {
|
||||
fieldMetadataId: string;
|
||||
fieldMetadataItems: FieldMetadataEntity[];
|
||||
validators: Validator[];
|
||||
};
|
||||
const validatorRunner = ({
|
||||
fieldMetadataId,
|
||||
fieldMetadataItems,
|
||||
validators,
|
||||
}: ValidateMetadataIdentifierFieldMetadataIdOrThrowArgs): void => {
|
||||
const matchingFieldMetadata = fieldMetadataItems.find(
|
||||
(fieldMetadata) => fieldMetadata.id === fieldMetadataId,
|
||||
);
|
||||
|
||||
validators.forEach(({ label, validator }) => {
|
||||
if (validator({ fieldMetadataId, matchingFieldMetadata })) {
|
||||
throw new ObjectMetadataException(
|
||||
label,
|
||||
ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT,
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
type ValidateMetadataIdentifierFieldMetadataIdsArgs = {
|
||||
labelIdentifierFieldMetadataId: string | undefined;
|
||||
imageIdentifierFieldMetadataId: string | undefined;
|
||||
fieldMetadataItems: FieldMetadataEntity[];
|
||||
};
|
||||
export const validateMetadataIdentifierFieldMetadataIds = ({
|
||||
imageIdentifierFieldMetadataId,
|
||||
labelIdentifierFieldMetadataId,
|
||||
fieldMetadataItems,
|
||||
}: ValidateMetadataIdentifierFieldMetadataIdsArgs) => {
|
||||
const isMatchingFieldMetadataDefined: Validator['validator'] = ({
|
||||
matchingFieldMetadata,
|
||||
}) => !isDefined(matchingFieldMetadata);
|
||||
|
||||
if (isDefined(labelIdentifierFieldMetadataId)) {
|
||||
validatorRunner({
|
||||
fieldMetadataId: labelIdentifierFieldMetadataId,
|
||||
fieldMetadataItems,
|
||||
validators: [
|
||||
{
|
||||
validator: isMatchingFieldMetadataDefined,
|
||||
label:
|
||||
'labelIdentifierFieldMetadataId validation failed: related field metadata not found',
|
||||
},
|
||||
{
|
||||
validator: ({ matchingFieldMetadata }) =>
|
||||
isDefined(matchingFieldMetadata) &&
|
||||
!isLabelIdentifierFieldMetadataTypes(matchingFieldMetadata.type),
|
||||
label:
|
||||
'labelIdentifierFieldMetadataId validation failed: it must be a TEXT or FULL_NAME field metadata type id',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
if (isDefined(imageIdentifierFieldMetadataId)) {
|
||||
validatorRunner({
|
||||
fieldMetadataId: imageIdentifierFieldMetadataId,
|
||||
fieldMetadataItems,
|
||||
validators: [
|
||||
{
|
||||
validator: isMatchingFieldMetadataDefined,
|
||||
label:
|
||||
'imageIdentifierFieldMetadataId validation failed: related field metadata not found',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
};
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import camelCase from 'lodash.camelcase';
|
||||
|
||||
import {
|
||||
InvalidMetadataException,
|
||||
InvalidMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception';
|
||||
|
||||
export const validateMetadataNameIsCamelCaseOrThrow = (name: string) => {
|
||||
if (name !== camelCase(name)) {
|
||||
throw new InvalidMetadataException(
|
||||
t`${name} should be in camelCase`,
|
||||
InvalidMetadataExceptionCode.NOT_CAMEL_CASE,
|
||||
);
|
||||
}
|
||||
};
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { RESERVED_METADATA_NAME_KEYWORDS } from 'twenty-shared/metadata';
|
||||
|
||||
import {
|
||||
InvalidMetadataException,
|
||||
InvalidMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception';
|
||||
|
||||
export const validateMetadataNameIsNotReservedKeywordOrThrow = (
|
||||
name: string,
|
||||
) => {
|
||||
if (RESERVED_METADATA_NAME_KEYWORDS.includes(name)) {
|
||||
throw new InvalidMetadataException(
|
||||
`The name "${name}" is not available`,
|
||||
InvalidMetadataExceptionCode.RESERVED_KEYWORD,
|
||||
{
|
||||
userFriendlyMessage: msg`This name is not available.`,
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
import {
|
||||
InvalidMetadataException,
|
||||
InvalidMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception';
|
||||
import { exceedsDatabaseIdentifierMaximumLength } from 'src/engine/metadata-modules/utils/validate-database-identifier-length.utils';
|
||||
|
||||
export const validateMetadataNameIsNotTooLongOrThrow = (name: string) => {
|
||||
if (exceedsDatabaseIdentifierMaximumLength(name)) {
|
||||
throw new InvalidMetadataException(
|
||||
t`Name is too long: it exceeds the 63 characters limit.`,
|
||||
InvalidMetadataExceptionCode.EXCEEDS_MAX_LENGTH,
|
||||
);
|
||||
}
|
||||
};
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
import {
|
||||
InvalidMetadataException,
|
||||
InvalidMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception';
|
||||
import { beneathDatabaseIdentifierMinimumLength } from 'src/engine/metadata-modules/utils/validate-database-identifier-length.utils';
|
||||
|
||||
export const validateMetadataNameIsNotTooShortOrThrow = (name: string) => {
|
||||
if (beneathDatabaseIdentifierMinimumLength(name)) {
|
||||
throw new InvalidMetadataException(
|
||||
t`Input is too short: "${name}"`,
|
||||
InvalidMetadataExceptionCode.INPUT_TOO_SHORT,
|
||||
);
|
||||
}
|
||||
};
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
import { validateMetadataNameIsCamelCaseOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-is-camel-case.utils';
|
||||
import { validateMetadataNameIsNotReservedKeywordOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-is-not-reserved-keyword';
|
||||
import { validateMetadataNameIsNotTooLongOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-is-not-too-long.utils';
|
||||
import { validateMetadataNameIsNotTooShortOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-is-not-too-short.utils';
|
||||
import { validateMetadataNameStartWithLowercaseLetterAndContainDigitsNorLettersOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-start-with-lowercase-letter-and-contain-digits-nor-letters.utils';
|
||||
|
||||
export const validateMetadataNameOrThrow = (name: string): void => {
|
||||
const validators = [
|
||||
validateMetadataNameIsNotTooLongOrThrow,
|
||||
validateMetadataNameIsNotTooShortOrThrow,
|
||||
validateMetadataNameIsCamelCaseOrThrow,
|
||||
validateMetadataNameStartWithLowercaseLetterAndContainDigitsNorLettersOrThrow,
|
||||
validateMetadataNameIsNotReservedKeywordOrThrow,
|
||||
];
|
||||
|
||||
validators.forEach((validator) => validator(name));
|
||||
};
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
import {
|
||||
InvalidMetadataException,
|
||||
InvalidMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception';
|
||||
|
||||
export const STARTS_WITH_LOWER_CASE_AND_CONTAINS_ONLY_CAPS_AND_LOWER_LETTERS_AND_NUMBER_STRING_REGEX =
|
||||
/^[a-z][a-zA-Z0-9]*$/;
|
||||
|
||||
export const validateMetadataNameStartWithLowercaseLetterAndContainDigitsNorLettersOrThrow =
|
||||
(name: string) => {
|
||||
if (
|
||||
!name.match(
|
||||
STARTS_WITH_LOWER_CASE_AND_CONTAINS_ONLY_CAPS_AND_LOWER_LETTERS_AND_NUMBER_STRING_REGEX,
|
||||
)
|
||||
) {
|
||||
throw new InvalidMetadataException(
|
||||
t`String "${name}" is not valid: must start with lowercase letter and contain only alphanumeric letters`,
|
||||
InvalidMetadataExceptionCode.INVALID_STRING,
|
||||
);
|
||||
}
|
||||
};
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import {
|
||||
ObjectMetadataException,
|
||||
ObjectMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||
|
||||
type ValidateNoOtherObjectWithSameNameExistsOrThrowsParams = {
|
||||
objectMetadataNameSingular: string;
|
||||
objectMetadataNamePlural: string;
|
||||
existingObjectMetadataId?: string;
|
||||
objectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
|
||||
};
|
||||
|
||||
export const doesOtherObjectWithSameNameExists = ({
|
||||
objectMetadataMaps,
|
||||
objectMetadataNamePlural,
|
||||
objectMetadataNameSingular,
|
||||
existingObjectMetadataId,
|
||||
}: ValidateNoOtherObjectWithSameNameExistsOrThrowsParams) =>
|
||||
Object.values(objectMetadataMaps.byId)
|
||||
.filter(isDefined)
|
||||
.some(
|
||||
(objectMetadata) =>
|
||||
(objectMetadata.nameSingular === objectMetadataNameSingular ||
|
||||
objectMetadata.namePlural === objectMetadataNamePlural ||
|
||||
objectMetadata.nameSingular === objectMetadataNamePlural ||
|
||||
objectMetadata.namePlural === objectMetadataNameSingular) &&
|
||||
objectMetadata.id !== existingObjectMetadataId,
|
||||
);
|
||||
|
||||
export const validatesNoOtherObjectWithSameNameExistsOrThrows = (
|
||||
args: ValidateNoOtherObjectWithSameNameExistsOrThrowsParams,
|
||||
) => {
|
||||
const objectAlreadyExists = doesOtherObjectWithSameNameExists(args);
|
||||
|
||||
if (objectAlreadyExists) {
|
||||
throw new ObjectMetadataException(
|
||||
'Object already exists',
|
||||
ObjectMetadataExceptionCode.OBJECT_ALREADY_EXISTS,
|
||||
{
|
||||
userFriendlyMessage: msg`Object already exists`,
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user