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

View File

@ -0,0 +1,22 @@
import { cleanEntityName } from 'src/engine/api/graphql/workspace-schema-builder/utils/clean-entity-name.util';
describe('cleanEntityName', () => {
test('should camelCase strings', () => {
expect(cleanEntityName('hello world')).toBe('helloWorld');
expect(cleanEntityName('my name is John')).toBe('myNameIsJohn');
});
test('should remove numbers at the beginning', () => {
expect(cleanEntityName('123hello')).toBe('hello');
expect(cleanEntityName('456hello world')).toBe('helloWorld');
});
test('should remove special characters', () => {
expect(cleanEntityName('hello$world')).toBe('helloWorld');
expect(cleanEntityName('some#special&chars')).toBe('someSpecialChars');
});
test('should handle empty strings', () => {
expect(cleanEntityName('')).toBe('');
});
});

View File

@ -0,0 +1,22 @@
import { FieldMetadataType } from 'src/engine-metadata/field-metadata/field-metadata.entity';
import { getFieldMetadataType } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-field-metadata-type.util';
describe('getFieldMetadataType', () => {
it.each([
['uuid', FieldMetadataType.UUID],
['timestamp', FieldMetadataType.DATE_TIME],
])(
'should return correct FieldMetadataType for type %s',
(type, expectedMetadataType) => {
expect(getFieldMetadataType(type)).toBe(expectedMetadataType);
},
);
it('should throw an error for an unknown type', () => {
const unknownType = 'unknownType';
expect(() => getFieldMetadataType(unknownType)).toThrow(
`Unknown type ${unknownType}`,
);
});
});

View File

@ -0,0 +1,59 @@
import { WorkspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { FieldMetadataType } from 'src/engine-metadata/field-metadata/field-metadata.entity';
import { InputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory';
import { getResolverArgs } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util';
describe('getResolverArgs', () => {
const expectedOutputs = {
findMany: {
first: { type: FieldMetadataType.NUMBER, isNullable: true },
last: { type: FieldMetadataType.NUMBER, isNullable: true },
before: { type: FieldMetadataType.TEXT, isNullable: true },
after: { type: FieldMetadataType.TEXT, isNullable: true },
filter: { kind: InputTypeDefinitionKind.Filter, isNullable: true },
orderBy: { kind: InputTypeDefinitionKind.OrderBy, isNullable: true },
},
findOne: {
filter: { kind: InputTypeDefinitionKind.Filter, isNullable: false },
},
createMany: {
data: {
kind: InputTypeDefinitionKind.Create,
isNullable: false,
isArray: true,
},
},
createOne: {
data: { kind: InputTypeDefinitionKind.Create, isNullable: false },
},
updateOne: {
id: { type: FieldMetadataType.UUID, isNullable: false },
data: { kind: InputTypeDefinitionKind.Update, isNullable: false },
},
deleteOne: {
id: { type: FieldMetadataType.UUID, isNullable: false },
},
executeQuickActionOnOne: {
id: { type: FieldMetadataType.UUID, isNullable: false },
},
};
// Test each resolver type
Object.entries(expectedOutputs).forEach(([resolverType, expectedOutput]) => {
it(`should return correct args for ${resolverType} resolver`, () => {
expect(
getResolverArgs(resolverType as WorkspaceResolverBuilderMethodNames),
).toEqual(expectedOutput);
});
});
// Test for an unknown resolver type
it('should throw an error for an unknown resolver type', () => {
const unknownType = 'unknownType';
expect(() =>
getResolverArgs(unknownType as WorkspaceResolverBuilderMethodNames),
).toThrow(`Unknown resolver type: ${unknownType}`);
});
});

View File

@ -0,0 +1,17 @@
import { camelCase } from 'src/utils/camel-case';
export const cleanEntityName = (entityName: string) => {
// Remove all leading numbers
let camelCasedEntityName = entityName.replace(/^[0-9]+/, '');
// Trim the string
camelCasedEntityName = camelCasedEntityName.trim();
// Camel case the string
camelCasedEntityName = camelCase(camelCasedEntityName);
// Remove all special characters but keep alphabets and numbers
camelCasedEntityName = camelCasedEntityName.replace(/[^a-zA-Z0-9]/g, '');
return camelCasedEntityName;
};

View File

@ -0,0 +1,17 @@
import { FieldMetadataType } from 'src/engine-metadata/field-metadata/field-metadata.entity';
const typeOrmTypeMapping = new Map<string, FieldMetadataType>([
['uuid', FieldMetadataType.UUID],
['timestamp', FieldMetadataType.DATE_TIME],
// Add more types here if we need to support more than id, and createdAt/updatedAt/deletedAt
]);
export const getFieldMetadataType = (type: string) => {
const fieldType = typeOrmTypeMapping.get(type);
if (fieldType === undefined || fieldType === null) {
throw new Error(`Unknown type ${type}`);
}
return fieldType;
};

View File

@ -0,0 +1,111 @@
import { WorkspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { ArgMetadata } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/param-metadata.interface';
import { FieldMetadataType } from 'src/engine-metadata/field-metadata/field-metadata.entity';
import { InputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory';
export const getResolverArgs = (
type: WorkspaceResolverBuilderMethodNames,
): { [key: string]: ArgMetadata } => {
switch (type) {
case 'findMany':
return {
first: {
type: FieldMetadataType.NUMBER,
isNullable: true,
},
last: {
type: FieldMetadataType.NUMBER,
isNullable: true,
},
before: {
type: FieldMetadataType.TEXT,
isNullable: true,
},
after: {
type: FieldMetadataType.TEXT,
isNullable: true,
},
filter: {
kind: InputTypeDefinitionKind.Filter,
isNullable: true,
},
orderBy: {
kind: InputTypeDefinitionKind.OrderBy,
isNullable: true,
},
};
case 'findOne':
case 'deleteMany':
return {
filter: {
kind: InputTypeDefinitionKind.Filter,
isNullable: false,
},
};
case 'createMany':
return {
data: {
kind: InputTypeDefinitionKind.Create,
isNullable: false,
isArray: true,
},
};
case 'createOne':
return {
data: {
kind: InputTypeDefinitionKind.Create,
isNullable: false,
},
};
case 'updateOne':
return {
id: {
type: FieldMetadataType.UUID,
isNullable: false,
},
data: {
kind: InputTypeDefinitionKind.Update,
isNullable: false,
},
};
case 'findDuplicates':
return {
id: {
type: FieldMetadataType.UUID,
isNullable: true,
},
data: {
kind: InputTypeDefinitionKind.Create,
isNullable: true,
},
};
case 'deleteOne':
return {
id: {
type: FieldMetadataType.UUID,
isNullable: false,
},
};
case 'executeQuickActionOnOne':
return {
id: {
type: FieldMetadataType.UUID,
isNullable: false,
},
};
case 'updateMany':
return {
data: {
kind: InputTypeDefinitionKind.Update,
isNullable: false,
},
filter: {
kind: InputTypeDefinitionKind.Filter,
isNullable: false,
},
};
default:
throw new Error(`Unknown resolver type: ${type}`);
}
};

View File

@ -0,0 +1,11 @@
import { ObjectMetadataInterface } from 'src/engine-metadata/field-metadata/interfaces/object-metadata.interface';
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
export const objectContainsRelationField = (
objectMetadata: ObjectMetadataInterface,
): boolean => {
return objectMetadata.fields.some((field) =>
isRelationFieldMetadataType(field.type),
);
};