Refactor backend folder structure (#4505)
* Refactor backend folder structure Co-authored-by: Charles Bochet <charles@twenty.com> * fix tests * fix * move yoga hooks --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
+26
@@ -0,0 +1,26 @@
|
||||
import { computeDepth } from 'src/engine/api/rest/api-rest-query-builder/utils/compute-depth.utils';
|
||||
|
||||
describe('computeDepth', () => {
|
||||
it('should compute depth from query', () => {
|
||||
const request: any = {
|
||||
query: { depth: '1' },
|
||||
};
|
||||
|
||||
expect(computeDepth(request)).toEqual(1);
|
||||
});
|
||||
|
||||
it('should return default depth if missing', () => {
|
||||
const request: any = { query: {} };
|
||||
|
||||
expect(computeDepth(request)).toEqual(undefined);
|
||||
});
|
||||
it('should raise if wrong depth', () => {
|
||||
const request: any = { query: { depth: '100' } };
|
||||
|
||||
expect(() => computeDepth(request)).toThrow();
|
||||
|
||||
request.query.depth = '0';
|
||||
|
||||
expect(() => computeDepth(request)).toThrow();
|
||||
});
|
||||
});
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
import { objectMetadataItemMock } from 'src/engine/api/__mocks__/object-metadata-item.mock';
|
||||
import {
|
||||
checkFields,
|
||||
getFieldType,
|
||||
} from 'src/engine/api/rest/api-rest-query-builder/utils/fields.utils';
|
||||
import { FieldMetadataType } from 'src/engine-metadata/field-metadata/field-metadata.entity';
|
||||
|
||||
describe('FieldUtils', () => {
|
||||
describe('getFieldType', () => {
|
||||
it('should get field type', () => {
|
||||
expect(getFieldType(objectMetadataItemMock, 'fieldNumber')).toEqual(
|
||||
FieldMetadataType.NUMBER,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkFields', () => {
|
||||
it('should check field types', () => {
|
||||
expect(() =>
|
||||
checkFields(objectMetadataItemMock, ['fieldNumber']),
|
||||
).not.toThrow();
|
||||
|
||||
expect(() => checkFields(objectMetadataItemMock, ['wrongField'])).toThrow();
|
||||
|
||||
expect(() =>
|
||||
checkFields(objectMetadataItemMock, ['fieldNumber', 'wrongField']),
|
||||
).toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import {
|
||||
fieldCurrencyMock,
|
||||
fieldLinkMock,
|
||||
fieldNumberMock,
|
||||
fieldStringMock,
|
||||
objectMetadataItemMock,
|
||||
} from 'src/engine/api/__mocks__/object-metadata-item.mock';
|
||||
import { mapFieldMetadataToGraphqlQuery } from 'src/engine/api/rest/api-rest-query-builder/utils/map-field-metadata-to-graphql-query.utils';
|
||||
|
||||
describe('mapFieldMetadataToGraphqlQuery', () => {
|
||||
it('should map properly', () => {
|
||||
expect(
|
||||
mapFieldMetadataToGraphqlQuery(objectMetadataItemMock, fieldNumberMock),
|
||||
).toEqual('fieldNumber');
|
||||
expect(
|
||||
mapFieldMetadataToGraphqlQuery(objectMetadataItemMock, fieldStringMock),
|
||||
).toEqual('fieldString');
|
||||
expect(
|
||||
mapFieldMetadataToGraphqlQuery(objectMetadataItemMock, fieldLinkMock),
|
||||
).toEqual(`
|
||||
fieldLink
|
||||
{
|
||||
label
|
||||
url
|
||||
}
|
||||
`);
|
||||
expect(
|
||||
mapFieldMetadataToGraphqlQuery(objectMetadataItemMock, fieldCurrencyMock),
|
||||
).toEqual(`
|
||||
fieldCurrency
|
||||
{
|
||||
amountMicros
|
||||
currencyCode
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { parsePath } from 'src/engine/api/rest/api-rest-query-builder/utils/parse-path.utils';
|
||||
|
||||
describe('parsePath', () => {
|
||||
it('should parse object from request path', () => {
|
||||
const request: any = { path: '/rest/companies/uuid' };
|
||||
|
||||
expect(parsePath(request)).toEqual({
|
||||
object: 'companies',
|
||||
id: 'uuid',
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse object from request path', () => {
|
||||
const request: any = { path: '/rest/companies' };
|
||||
|
||||
expect(parsePath(request)).toEqual({
|
||||
object: 'companies',
|
||||
id: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
|
||||
import { Request } from 'express';
|
||||
|
||||
const ALLOWED_DEPTH_VALUES = [1, 2];
|
||||
|
||||
export const computeDepth = (request: Request): number | undefined => {
|
||||
if (!request.query.depth) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const depth = +request.query.depth;
|
||||
|
||||
if (isNaN(depth) || !ALLOWED_DEPTH_VALUES.includes(depth)) {
|
||||
throw new BadRequestException(
|
||||
`'depth=${
|
||||
request.query.depth
|
||||
}' parameter invalid. Allowed values are ${ALLOWED_DEPTH_VALUES.join(
|
||||
', ',
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
|
||||
return depth;
|
||||
};
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
|
||||
import { FieldMetadataType } from 'src/engine-metadata/field-metadata/field-metadata.entity';
|
||||
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||
|
||||
export const getFieldType = (
|
||||
objectMetadataItem,
|
||||
fieldName,
|
||||
): FieldMetadataType | undefined => {
|
||||
for (const itemField of objectMetadataItem.fields) {
|
||||
if (fieldName === itemField.name) {
|
||||
return itemField.type;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const checkFields = (objectMetadataItem, fieldNames): void => {
|
||||
for (const fieldName of fieldNames) {
|
||||
if (
|
||||
!objectMetadataItem.fields
|
||||
.reduce(
|
||||
(acc, itemField) => [
|
||||
...acc,
|
||||
itemField.name,
|
||||
...Object.keys(itemField.targetColumnMap),
|
||||
],
|
||||
[],
|
||||
)
|
||||
.includes(fieldName)
|
||||
) {
|
||||
throw new BadRequestException(
|
||||
`field '${fieldName}' does not exist in '${computeObjectTargetTable(
|
||||
objectMetadataItem,
|
||||
)}' object`,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
import { FieldMetadataType } from 'src/engine-metadata/field-metadata/field-metadata.entity';
|
||||
import { RelationMetadataType } from 'src/engine-metadata/relation-metadata/relation-metadata.entity';
|
||||
|
||||
const DEFAULT_DEPTH_VALUE = 2;
|
||||
|
||||
export const mapFieldMetadataToGraphqlQuery = (
|
||||
objectMetadataItems,
|
||||
field,
|
||||
maxDepthForRelations = DEFAULT_DEPTH_VALUE,
|
||||
): string | undefined => {
|
||||
if (maxDepthForRelations <= 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const fieldType = field.type;
|
||||
|
||||
const fieldIsSimpleValue = [
|
||||
FieldMetadataType.UUID,
|
||||
FieldMetadataType.TEXT,
|
||||
FieldMetadataType.PHONE,
|
||||
FieldMetadataType.DATE_TIME,
|
||||
FieldMetadataType.EMAIL,
|
||||
FieldMetadataType.NUMBER,
|
||||
FieldMetadataType.BOOLEAN,
|
||||
FieldMetadataType.POSITION,
|
||||
].includes(fieldType);
|
||||
|
||||
if (fieldIsSimpleValue) {
|
||||
return field.name;
|
||||
} else if (
|
||||
fieldType === FieldMetadataType.RELATION &&
|
||||
field.toRelationMetadata?.relationType === RelationMetadataType.ONE_TO_MANY
|
||||
) {
|
||||
const relationMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.id ===
|
||||
(field.toRelationMetadata as any)?.fromObjectMetadataId,
|
||||
);
|
||||
|
||||
return `${field.name}
|
||||
{
|
||||
id
|
||||
${(relationMetadataItem?.fields ?? [])
|
||||
.filter((field) => field.type !== FieldMetadataType.RELATION)
|
||||
.map((field) =>
|
||||
mapFieldMetadataToGraphqlQuery(
|
||||
objectMetadataItems,
|
||||
field,
|
||||
maxDepthForRelations - 1,
|
||||
),
|
||||
)
|
||||
.join('\n')}
|
||||
}`;
|
||||
} else if (
|
||||
fieldType === FieldMetadataType.RELATION &&
|
||||
field.fromRelationMetadata?.relationType ===
|
||||
RelationMetadataType.ONE_TO_MANY
|
||||
) {
|
||||
const relationMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.id ===
|
||||
(field.fromRelationMetadata as any)?.toObjectMetadataId,
|
||||
);
|
||||
|
||||
return `${field.name}
|
||||
{
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
${(relationMetadataItem?.fields ?? [])
|
||||
.filter((field) => field.type !== FieldMetadataType.RELATION)
|
||||
.map((field) =>
|
||||
mapFieldMetadataToGraphqlQuery(
|
||||
objectMetadataItems,
|
||||
field,
|
||||
maxDepthForRelations - 1,
|
||||
),
|
||||
)
|
||||
.join('\n')}
|
||||
}
|
||||
}
|
||||
}`;
|
||||
} else if (fieldType === FieldMetadataType.LINK) {
|
||||
return `
|
||||
${field.name}
|
||||
{
|
||||
label
|
||||
url
|
||||
}
|
||||
`;
|
||||
} else if (fieldType === FieldMetadataType.CURRENCY) {
|
||||
return `
|
||||
${field.name}
|
||||
{
|
||||
amountMicros
|
||||
currencyCode
|
||||
}
|
||||
`;
|
||||
} else if (fieldType === FieldMetadataType.FULL_NAME) {
|
||||
return `
|
||||
${field.name}
|
||||
{
|
||||
firstName
|
||||
lastName
|
||||
}
|
||||
`;
|
||||
}
|
||||
};
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
|
||||
import { Request } from 'express';
|
||||
|
||||
export const parsePath = (
|
||||
request: Request,
|
||||
): { object: string; id?: string } => {
|
||||
const queryAction = request.path.replace('/rest/', '').split('/');
|
||||
|
||||
if (queryAction.length > 2) {
|
||||
throw new BadRequestException(
|
||||
`Query path '${request.path}' invalid. Valid examples: /rest/companies/id or /rest/companies`,
|
||||
);
|
||||
}
|
||||
|
||||
if (queryAction.length === 1) {
|
||||
return { object: queryAction[0] };
|
||||
}
|
||||
|
||||
return { object: queryAction[0], id: queryAction[1] };
|
||||
};
|
||||
|
||||
export const parseMetadataPath = (
|
||||
request: Request,
|
||||
): { objectNameSingular: string; objectNamePlural: string; id?: string } => {
|
||||
const queryAction = request.path.replace('/rest/metadata/', '').split('/');
|
||||
|
||||
if (queryAction.length > 3 || queryAction.length === 0) {
|
||||
throw new BadRequestException(
|
||||
`Query path '${request.path}' invalid. Valid examples: /rest/metadata/fields or /rest/metadata/objects/id`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!['fields', 'objects', 'relations'].includes(queryAction[0])) {
|
||||
throw new BadRequestException(
|
||||
`Query path '${request.path}' invalid. Metadata path "${queryAction[0]}" does not exist. Valid examples: /rest/metadata/fields or /rest/metadata/objects or /rest/metadata/relations`,
|
||||
);
|
||||
}
|
||||
|
||||
const objectName = queryAction[0];
|
||||
|
||||
if (queryAction.length === 2) {
|
||||
switch (objectName) {
|
||||
case 'fields':
|
||||
return {
|
||||
objectNameSingular: 'field',
|
||||
objectNamePlural: objectName,
|
||||
id: queryAction[1],
|
||||
};
|
||||
case 'objects':
|
||||
return {
|
||||
objectNameSingular: 'object',
|
||||
objectNamePlural: objectName,
|
||||
id: queryAction[1],
|
||||
};
|
||||
case 'relations':
|
||||
return {
|
||||
objectNameSingular: 'relation',
|
||||
objectNamePlural: objectName,
|
||||
id: queryAction[1],
|
||||
};
|
||||
default:
|
||||
return { objectNameSingular: '', objectNamePlural: '', id: '' };
|
||||
}
|
||||
} else {
|
||||
switch (objectName) {
|
||||
case 'fields':
|
||||
return { objectNameSingular: 'field', objectNamePlural: objectName };
|
||||
case 'objects':
|
||||
return { objectNameSingular: 'object', objectNamePlural: objectName };
|
||||
case 'relations':
|
||||
return { objectNameSingular: 'relation', objectNamePlural: objectName };
|
||||
default:
|
||||
return { objectNameSingular: '', objectNamePlural: '' };
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user