12660 bugapi create one person post api request example is returning 400 in playground (#12787)
Use faker to provide simple working examples for REST API create one, create many, update one and find duplicates Eg: <img width="1505" alt="image" src="https://github.com/user-attachments/assets/99be990f-efd6-4ad7-8c29-f9dcecac112f" />
This commit is contained in:
@ -4,6 +4,9 @@ exports[`computeSchemaComponents Float without decimals 1`] = `
|
||||
{
|
||||
"ObjectName": {
|
||||
"description": undefined,
|
||||
"example": {
|
||||
"number2": 692.1852368365229,
|
||||
},
|
||||
"properties": {
|
||||
"number2": {
|
||||
"type": "number",
|
||||
@ -25,6 +28,9 @@ exports[`computeSchemaComponents Float without decimals 1`] = `
|
||||
},
|
||||
"ObjectNameForUpdate": {
|
||||
"description": undefined,
|
||||
"example": {
|
||||
"number2": 316.2001153750569,
|
||||
},
|
||||
"properties": {
|
||||
"number2": {
|
||||
"type": "number",
|
||||
@ -39,6 +45,9 @@ exports[`computeSchemaComponents Integer dataType with decimals 1`] = `
|
||||
{
|
||||
"ObjectName": {
|
||||
"description": undefined,
|
||||
"example": {
|
||||
"number1": 957.9316406203515,
|
||||
},
|
||||
"properties": {
|
||||
"number1": {
|
||||
"type": "number",
|
||||
@ -60,6 +69,9 @@ exports[`computeSchemaComponents Integer dataType with decimals 1`] = `
|
||||
},
|
||||
"ObjectNameForUpdate": {
|
||||
"description": undefined,
|
||||
"example": {
|
||||
"number1": 533.6321196880441,
|
||||
},
|
||||
"properties": {
|
||||
"number1": {
|
||||
"type": "number",
|
||||
@ -74,6 +86,9 @@ exports[`computeSchemaComponents Integer with a 0 decimals 1`] = `
|
||||
{
|
||||
"ObjectName": {
|
||||
"description": undefined,
|
||||
"example": {
|
||||
"number3": 686.8144267539021,
|
||||
},
|
||||
"properties": {
|
||||
"number3": {
|
||||
"type": "integer",
|
||||
@ -95,6 +110,9 @@ exports[`computeSchemaComponents Integer with a 0 decimals 1`] = `
|
||||
},
|
||||
"ObjectNameForUpdate": {
|
||||
"description": undefined,
|
||||
"example": {
|
||||
"number3": 834.7910462254755,
|
||||
},
|
||||
"properties": {
|
||||
"number3": {
|
||||
"type": "integer",
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { EachTestingContext } from 'twenty-shared/testing';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { faker } from '@faker-js/faker';
|
||||
|
||||
import { NumberDataType } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
|
||||
|
||||
@ -9,6 +10,7 @@ import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
|
||||
describe('computeSchemaComponents', () => {
|
||||
faker.seed(1);
|
||||
it('should compute schema components', () => {
|
||||
expect(
|
||||
computeSchemaComponents([
|
||||
@ -18,6 +20,36 @@ describe('computeSchemaComponents', () => {
|
||||
{
|
||||
"ObjectName": {
|
||||
"description": undefined,
|
||||
"example": {
|
||||
"fieldCurrency": {
|
||||
"amountMicros": 284000000,
|
||||
"currencyCode": "EUR",
|
||||
},
|
||||
"fieldEmails": {
|
||||
"additionalEmails": null,
|
||||
"primaryEmail": "mina.gutmann9@hotmail.com",
|
||||
},
|
||||
"fieldFullName": {
|
||||
"firstName": "Shad",
|
||||
"lastName": "Osinski",
|
||||
},
|
||||
"fieldLinks": {
|
||||
"additionalLinks": [],
|
||||
"primaryLinkLabel": "",
|
||||
"primaryLinkUrl": "https://narrow-help.net/",
|
||||
},
|
||||
"fieldMultiSelect": [
|
||||
"OPTION_1",
|
||||
],
|
||||
"fieldNumber": 346.2151663160047,
|
||||
"fieldPhones": {
|
||||
"additionalPhones": [],
|
||||
"primaryPhoneCallingCode": "+33",
|
||||
"primaryPhoneCountryCode": "FR",
|
||||
"primaryPhoneNumber": "06 10 20 30 40",
|
||||
},
|
||||
"fieldSelect": "OPTION_1",
|
||||
},
|
||||
"properties": {
|
||||
"fieldActor": {
|
||||
"properties": {
|
||||
@ -444,6 +476,36 @@ describe('computeSchemaComponents', () => {
|
||||
},
|
||||
"ObjectNameForUpdate": {
|
||||
"description": undefined,
|
||||
"example": {
|
||||
"fieldCurrency": {
|
||||
"amountMicros": 253000000,
|
||||
"currencyCode": "EUR",
|
||||
},
|
||||
"fieldEmails": {
|
||||
"additionalEmails": null,
|
||||
"primaryEmail": "keegan_donnelly96@hotmail.com",
|
||||
},
|
||||
"fieldFullName": {
|
||||
"firstName": "Shad",
|
||||
"lastName": "Jones",
|
||||
},
|
||||
"fieldLinks": {
|
||||
"additionalLinks": [],
|
||||
"primaryLinkLabel": "",
|
||||
"primaryLinkUrl": "https://unlawful-blowgun.biz",
|
||||
},
|
||||
"fieldMultiSelect": [
|
||||
"OPTION_1",
|
||||
],
|
||||
"fieldNumber": 692.6302930536448,
|
||||
"fieldPhones": {
|
||||
"additionalPhones": [],
|
||||
"primaryPhoneCallingCode": "+33",
|
||||
"primaryPhoneCountryCode": "FR",
|
||||
"primaryPhoneNumber": "06 10 20 30 40",
|
||||
},
|
||||
"fieldSelect": "OPTION_1",
|
||||
},
|
||||
"properties": {
|
||||
"fieldActor": {
|
||||
"properties": {
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
NumberDataType,
|
||||
} from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
||||
|
||||
import {
|
||||
computeDepthParameters,
|
||||
@ -20,6 +21,8 @@ import {
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||
import { camelToTitleCase } from 'src/utils/camel-to-title-case';
|
||||
import { generateRandomFieldValue } from 'src/engine/core-modules/open-api/utils/generate-random-field-value.utils';
|
||||
|
||||
type Property = OpenAPIV3_1.SchemaObject;
|
||||
|
||||
@ -27,6 +30,8 @@ type Properties = {
|
||||
[name: string]: Property;
|
||||
};
|
||||
|
||||
type OpenApiExample = Record<string, FieldMetadataDefaultValue>;
|
||||
|
||||
const isFieldAvailable = (field: FieldMetadataEntity, forResponse: boolean) => {
|
||||
if (forResponse) {
|
||||
return true;
|
||||
@ -86,6 +91,47 @@ const getFieldProperties = (field: FieldMetadataEntity): Property => {
|
||||
}
|
||||
};
|
||||
|
||||
const getSchemaComponentsExample = (
|
||||
item: ObjectMetadataEntity,
|
||||
): OpenApiExample => {
|
||||
return item.fields.reduce((node, field) => {
|
||||
// If field is required
|
||||
if (!field.isNullable && field.defaultValue === null) {
|
||||
return { ...node, [field.name]: generateRandomFieldValue({ field }) };
|
||||
}
|
||||
|
||||
switch (field.type) {
|
||||
case FieldMetadataType.TEXT: {
|
||||
if (field.name !== 'name') {
|
||||
return node;
|
||||
}
|
||||
|
||||
return {
|
||||
...node,
|
||||
[field.name]: `${camelToTitleCase(item.nameSingular)} name`,
|
||||
};
|
||||
}
|
||||
|
||||
case FieldMetadataType.EMAILS:
|
||||
case FieldMetadataType.LINKS:
|
||||
case FieldMetadataType.CURRENCY:
|
||||
case FieldMetadataType.FULL_NAME:
|
||||
case FieldMetadataType.SELECT:
|
||||
case FieldMetadataType.MULTI_SELECT:
|
||||
case FieldMetadataType.PHONES: {
|
||||
return {
|
||||
...node,
|
||||
[field.name]: generateRandomFieldValue({ field }),
|
||||
};
|
||||
}
|
||||
|
||||
default: {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}, {});
|
||||
};
|
||||
|
||||
const getSchemaComponentsProperties = ({
|
||||
item,
|
||||
forResponse,
|
||||
@ -105,12 +151,13 @@ const getSchemaComponentsProperties = ({
|
||||
isFieldMetadataEntityOfType(field, FieldMetadataType.RELATION) &&
|
||||
field.settings?.relationType === RelationType.MANY_TO_ONE
|
||||
) {
|
||||
node[`${field.name}Id`] = {
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
return {
|
||||
...node,
|
||||
[`${field.name}Id`]: {
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
},
|
||||
};
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
if (
|
||||
@ -333,7 +380,7 @@ const getSchemaComponentsProperties = ({
|
||||
}
|
||||
|
||||
if (Object.keys(itemProperty).length) {
|
||||
node[field.name] = itemProperty;
|
||||
return { ...node, [field.name]: itemProperty };
|
||||
}
|
||||
|
||||
return node;
|
||||
@ -379,7 +426,7 @@ const getSchemaComponentsRelationProperties = (
|
||||
}
|
||||
|
||||
if (Object.keys(itemProperty).length) {
|
||||
node[field.name] = itemProperty;
|
||||
return { ...node, [field.name]: itemProperty };
|
||||
}
|
||||
|
||||
return node;
|
||||
@ -400,20 +447,23 @@ const getRequiredFields = (item: ObjectMetadataEntity): string[] => {
|
||||
|
||||
const computeSchemaComponent = ({
|
||||
item,
|
||||
withRequiredFields,
|
||||
forResponse,
|
||||
withRelations,
|
||||
forUpdate,
|
||||
}: {
|
||||
item: ObjectMetadataEntity;
|
||||
withRequiredFields: boolean;
|
||||
forResponse: boolean;
|
||||
withRelations: boolean;
|
||||
forUpdate: boolean;
|
||||
}): OpenAPIV3_1.SchemaObject => {
|
||||
const result = {
|
||||
const withRelations = forResponse && !forUpdate;
|
||||
|
||||
const withRequiredFields = !forResponse && !forUpdate;
|
||||
|
||||
const result: OpenAPIV3_1.SchemaObject = {
|
||||
type: 'object',
|
||||
description: item.description,
|
||||
properties: getSchemaComponentsProperties({ item, forResponse }),
|
||||
} as OpenAPIV3_1.SchemaObject;
|
||||
...(!forResponse ? { example: getSchemaComponentsExample(item) } : {}),
|
||||
};
|
||||
|
||||
if (withRelations) {
|
||||
result.properties = {
|
||||
@ -442,23 +492,20 @@ export const computeSchemaComponents = (
|
||||
(schemas, item) => {
|
||||
schemas[capitalize(item.nameSingular)] = computeSchemaComponent({
|
||||
item,
|
||||
withRequiredFields: true,
|
||||
forResponse: false,
|
||||
withRelations: false,
|
||||
forUpdate: false,
|
||||
});
|
||||
schemas[capitalize(item.nameSingular) + 'ForUpdate'] =
|
||||
computeSchemaComponent({
|
||||
item,
|
||||
withRequiredFields: false,
|
||||
forResponse: false,
|
||||
withRelations: false,
|
||||
forUpdate: true,
|
||||
});
|
||||
schemas[capitalize(item.nameSingular) + 'ForResponse'] =
|
||||
computeSchemaComponent({
|
||||
item,
|
||||
withRequiredFields: false,
|
||||
forResponse: true,
|
||||
withRelations: true,
|
||||
forUpdate: false,
|
||||
});
|
||||
|
||||
return schemas;
|
||||
|
||||
@ -0,0 +1,144 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { v4 } from 'uuid';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { assertUnreachable, isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
||||
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
|
||||
export const generateRandomFieldValue = ({
|
||||
field,
|
||||
}: {
|
||||
field: FieldMetadataEntity;
|
||||
}): FieldMetadataDefaultValue => {
|
||||
switch (field.type) {
|
||||
case FieldMetadataType.UUID: {
|
||||
return v4();
|
||||
}
|
||||
|
||||
case FieldMetadataType.TEXT: {
|
||||
return faker.string.fromCharacters(field.name);
|
||||
}
|
||||
|
||||
case FieldMetadataType.PHONES: {
|
||||
return {
|
||||
primaryPhoneNumber: '06 10 20 30 40',
|
||||
primaryPhoneCallingCode: '+33',
|
||||
primaryPhoneCountryCode: 'FR',
|
||||
additionalPhones: [],
|
||||
};
|
||||
}
|
||||
|
||||
case FieldMetadataType.EMAILS: {
|
||||
return {
|
||||
primaryEmail: faker.internet.email().toLowerCase(),
|
||||
additionalEmails: null,
|
||||
};
|
||||
}
|
||||
|
||||
case FieldMetadataType.DATE:
|
||||
case FieldMetadataType.DATE_TIME: {
|
||||
return faker.date.soon();
|
||||
}
|
||||
|
||||
case FieldMetadataType.BOOLEAN: {
|
||||
return false;
|
||||
}
|
||||
|
||||
case FieldMetadataType.NUMBER: {
|
||||
return faker.number.float({ min: 1, max: 1_000 });
|
||||
}
|
||||
|
||||
case FieldMetadataType.NUMERIC: {
|
||||
return faker.number.int({ min: 1, max: 1_000 });
|
||||
}
|
||||
|
||||
case FieldMetadataType.LINKS: {
|
||||
return {
|
||||
primaryLinkLabel: '',
|
||||
primaryLinkUrl: faker.internet.url(),
|
||||
additionalLinks: [],
|
||||
};
|
||||
}
|
||||
|
||||
case FieldMetadataType.CURRENCY: {
|
||||
return {
|
||||
amountMicros: faker.number.int({ min: 100, max: 1_000 }) * 1_000_000,
|
||||
currencyCode: 'EUR',
|
||||
};
|
||||
}
|
||||
|
||||
case FieldMetadataType.FULL_NAME: {
|
||||
return {
|
||||
firstName: faker.person.firstName(),
|
||||
lastName: faker.person.lastName(),
|
||||
};
|
||||
}
|
||||
|
||||
case FieldMetadataType.RATING: {
|
||||
return 'RATING_5';
|
||||
}
|
||||
|
||||
case FieldMetadataType.SELECT: {
|
||||
return isDefined(field.options[0].value) ? field.options[0].value : [];
|
||||
}
|
||||
|
||||
case FieldMetadataType.MULTI_SELECT: {
|
||||
return isDefined(field.options[0].value) ? [field.options[0].value] : [];
|
||||
}
|
||||
|
||||
case FieldMetadataType.RELATION: {
|
||||
return null;
|
||||
}
|
||||
|
||||
case FieldMetadataType.POSITION: {
|
||||
return 1;
|
||||
}
|
||||
|
||||
case FieldMetadataType.ADDRESS: {
|
||||
return {
|
||||
addressStreet1: faker.location.streetAddress(),
|
||||
addressStreet2: faker.location.secondaryAddress(),
|
||||
addressCity: faker.location.city(),
|
||||
addressState: faker.location.state(),
|
||||
addressCountry: faker.location.country(),
|
||||
addressPostcode: faker.location.zipCode(),
|
||||
addressLat: faker.location.latitude(),
|
||||
addressLng: faker.location.longitude(),
|
||||
};
|
||||
}
|
||||
|
||||
case FieldMetadataType.RAW_JSON: {
|
||||
return {};
|
||||
}
|
||||
|
||||
case FieldMetadataType.RICH_TEXT:
|
||||
case FieldMetadataType.RICH_TEXT_V2: {
|
||||
return '';
|
||||
}
|
||||
|
||||
case FieldMetadataType.ACTOR: {
|
||||
return {
|
||||
source: 'MANUAL',
|
||||
context: {},
|
||||
name: faker.person.fullName(),
|
||||
workspaceMemberId: null,
|
||||
};
|
||||
}
|
||||
|
||||
case FieldMetadataType.ARRAY: {
|
||||
return [];
|
||||
}
|
||||
|
||||
case FieldMetadataType.TS_VECTOR: {
|
||||
throw new Error(
|
||||
`We should not generate fake version for ${field.type} field`,
|
||||
);
|
||||
}
|
||||
|
||||
default: {
|
||||
assertUnreachable(field.type, `Unsupported field type '${field.type}'`);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -1,3 +1,5 @@
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
export const getRequestBody = (name: string) => {
|
||||
return {
|
||||
description: 'body',
|
||||
@ -59,8 +61,13 @@ export const getFindDuplicatesRequestBody = (name: string) => {
|
||||
},
|
||||
ids: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
},
|
||||
},
|
||||
},
|
||||
example: { ids: [v4()] },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
export const camelToTitleCase = (camelCaseText: string) =>
|
||||
capitalize(camelCaseText)
|
||||
.replace(/([A-Z])/g, ' $1')
|
||||
.replace(/^./, (str) => str.toUpperCase());
|
||||
capitalize(
|
||||
camelCaseText
|
||||
.replace(/([A-Z])/g, ' $1')
|
||||
.replace(/^./, (str) => str.toUpperCase()),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user