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:
Weiko
2024-03-15 18:37:09 +01:00
committed by GitHub
parent afb9b3e375
commit 2c09096edd
523 changed files with 1386 additions and 1856 deletions
@@ -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();
});
});
@@ -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();
});
});
});
@@ -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
}
`);
});
});
@@ -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,
});
});
});
@@ -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;
};
@@ -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`,
);
}
}
};
@@ -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
}
`;
}
};
@@ -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: '' };
}
}
};