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:
@ -0,0 +1,104 @@
|
||||
import { FieldMetadataInterface } from 'src/engine-metadata/field-metadata/interfaces/field-metadata.interface';
|
||||
import { RelationMetadataInterface } from 'src/engine-metadata/field-metadata/interfaces/relation-metadata.interface';
|
||||
|
||||
import { FieldMetadataType } from 'src/engine-metadata/field-metadata/field-metadata.entity';
|
||||
import { RelationMetadataType } from 'src/engine-metadata/relation-metadata/relation-metadata.entity';
|
||||
import {
|
||||
deduceRelationDirection,
|
||||
RelationDirection,
|
||||
} from 'src/engine/utils/deduce-relation-direction.util';
|
||||
|
||||
describe('deduceRelationDirection', () => {
|
||||
it('should return FROM when the current object Metadata ID matches fromObjectMetadataId and id matches fromFieldMetadataId', () => {
|
||||
const fieldMetadata: FieldMetadataInterface = {
|
||||
id: 'field_id',
|
||||
objectMetadataId: 'from_object_id',
|
||||
type: FieldMetadataType.RELATION,
|
||||
name: 'field_name',
|
||||
label: 'Field Name',
|
||||
description: 'Field Description',
|
||||
targetColumnMap: {
|
||||
default: 'default_column',
|
||||
},
|
||||
};
|
||||
|
||||
const relationMetadata = {
|
||||
id: 'relation_id',
|
||||
fromObjectMetadataId: fieldMetadata.objectMetadataId,
|
||||
toObjectMetadataId: 'to_object_id',
|
||||
fromFieldMetadataId: fieldMetadata.id,
|
||||
toFieldMetadataId: 'to_field_id',
|
||||
relationType: RelationMetadataType.ONE_TO_ONE,
|
||||
};
|
||||
|
||||
const result = deduceRelationDirection(
|
||||
fieldMetadata,
|
||||
relationMetadata as RelationMetadataInterface,
|
||||
);
|
||||
|
||||
expect(result).toBe(RelationDirection.FROM);
|
||||
});
|
||||
|
||||
it('should return TO when the current object Metadata ID matches toObjectMetadataId and id matches toFieldMetadataId', () => {
|
||||
// Arrange
|
||||
const fieldMetadata: FieldMetadataInterface = {
|
||||
id: 'field_id',
|
||||
objectMetadataId: 'to_object_id',
|
||||
type: FieldMetadataType.RELATION,
|
||||
name: 'field_name',
|
||||
label: 'Field Name',
|
||||
description: 'Field Description',
|
||||
targetColumnMap: {
|
||||
default: 'default_column',
|
||||
},
|
||||
};
|
||||
|
||||
const relationMetadata = {
|
||||
id: 'relation_id',
|
||||
fromObjectMetadataId: 'from_object_id',
|
||||
toObjectMetadataId: fieldMetadata.objectMetadataId,
|
||||
fromFieldMetadataId: 'from_field_id',
|
||||
toFieldMetadataId: fieldMetadata.id,
|
||||
relationType: RelationMetadataType.ONE_TO_ONE,
|
||||
};
|
||||
|
||||
const result = deduceRelationDirection(
|
||||
fieldMetadata,
|
||||
relationMetadata as RelationMetadataInterface,
|
||||
);
|
||||
|
||||
expect(result).toBe(RelationDirection.TO);
|
||||
});
|
||||
|
||||
it('should throw an error when the current object Metadata ID does not match any object metadata ID', () => {
|
||||
const fieldMetadata: FieldMetadataInterface = {
|
||||
id: 'field_id',
|
||||
objectMetadataId: 'unrelated_object_id',
|
||||
type: FieldMetadataType.RELATION,
|
||||
name: 'field_name',
|
||||
label: 'Field Name',
|
||||
description: 'Field Description',
|
||||
targetColumnMap: {
|
||||
default: 'default_column',
|
||||
},
|
||||
};
|
||||
|
||||
const relationMetadata = {
|
||||
id: 'relation_id',
|
||||
fromObjectMetadataId: 'from_object_id',
|
||||
toObjectMetadataId: 'to_object_id',
|
||||
fromFieldMetadataId: 'from_field_id',
|
||||
toFieldMetadataId: 'to_field_id',
|
||||
relationType: RelationMetadataType.ONE_TO_ONE,
|
||||
};
|
||||
|
||||
expect(() =>
|
||||
deduceRelationDirection(
|
||||
fieldMetadata,
|
||||
relationMetadata as RelationMetadataInterface,
|
||||
),
|
||||
).toThrow(
|
||||
`Relation metadata ${relationMetadata.id} is not related to object ${fieldMetadata.objectMetadataId}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,35 @@
|
||||
import { WorkspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
import { getResolverName } from 'src/engine/utils/get-resolver-name.util';
|
||||
|
||||
describe('getResolverName', () => {
|
||||
const metadata = {
|
||||
nameSingular: 'entity',
|
||||
namePlural: 'entities',
|
||||
};
|
||||
|
||||
it.each([
|
||||
['findMany', 'entities'],
|
||||
['findOne', 'entity'],
|
||||
['createMany', 'createEntities'],
|
||||
['createOne', 'createEntity'],
|
||||
['updateOne', 'updateEntity'],
|
||||
['deleteOne', 'deleteEntity'],
|
||||
['executeQuickActionOnOne', 'executeQuickActionOnEntity'],
|
||||
])('should return correct name for %s resolver', (type, expectedResult) => {
|
||||
expect(
|
||||
getResolverName(metadata, type as WorkspaceResolverBuilderMethodNames),
|
||||
).toBe(expectedResult);
|
||||
});
|
||||
|
||||
it('should throw an error for an unknown resolver type', () => {
|
||||
const unknownType = 'unknownType';
|
||||
|
||||
expect(() =>
|
||||
getResolverName(
|
||||
metadata,
|
||||
unknownType as WorkspaceResolverBuilderMethodNames,
|
||||
),
|
||||
).toThrow(`Unknown resolver type: ${unknownType}`);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,5 @@
|
||||
export const customNamePrefix = '_';
|
||||
|
||||
export const computeCustomName = (name: string, isCustom: boolean) => {
|
||||
return isCustom ? `${customNamePrefix}${name}` : name;
|
||||
};
|
||||
@ -0,0 +1,12 @@
|
||||
import { ObjectMetadataInterface } from 'src/engine-metadata/field-metadata/interfaces/object-metadata.interface';
|
||||
|
||||
import { computeCustomName } from './compute-custom-name.util';
|
||||
|
||||
export const computeObjectTargetTable = (
|
||||
objectMetadata: ObjectMetadataInterface,
|
||||
) => {
|
||||
return computeCustomName(
|
||||
objectMetadata.nameSingular,
|
||||
objectMetadata.isCustom,
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,30 @@
|
||||
import { FieldMetadataInterface } from 'src/engine-metadata/field-metadata/interfaces/field-metadata.interface';
|
||||
import { RelationMetadataInterface } from 'src/engine-metadata/field-metadata/interfaces/relation-metadata.interface';
|
||||
|
||||
export enum RelationDirection {
|
||||
FROM = 'from',
|
||||
TO = 'to',
|
||||
}
|
||||
|
||||
export const deduceRelationDirection = (
|
||||
fieldMetadata: FieldMetadataInterface,
|
||||
relationMetadata: RelationMetadataInterface,
|
||||
): RelationDirection => {
|
||||
if (
|
||||
relationMetadata.fromObjectMetadataId === fieldMetadata.objectMetadataId &&
|
||||
relationMetadata.fromFieldMetadataId === fieldMetadata.id
|
||||
) {
|
||||
return RelationDirection.FROM;
|
||||
}
|
||||
|
||||
if (
|
||||
relationMetadata.toObjectMetadataId === fieldMetadata.objectMetadataId &&
|
||||
relationMetadata.toFieldMetadataId === fieldMetadata.id
|
||||
) {
|
||||
return RelationDirection.TO;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Relation metadata ${relationMetadata.id} is not related to object ${fieldMetadata.objectMetadataId}`,
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,35 @@
|
||||
import { WorkspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
import { ObjectMetadataInterface } from 'src/engine-metadata/field-metadata/interfaces/object-metadata.interface';
|
||||
|
||||
import { camelCase } from 'src/utils/camel-case';
|
||||
import { pascalCase } from 'src/utils/pascal-case';
|
||||
|
||||
export const getResolverName = (
|
||||
objectMetadata: Pick<ObjectMetadataInterface, 'namePlural' | 'nameSingular'>,
|
||||
type: WorkspaceResolverBuilderMethodNames,
|
||||
) => {
|
||||
switch (type) {
|
||||
case 'findMany':
|
||||
return `${camelCase(objectMetadata.namePlural)}`;
|
||||
case 'findOne':
|
||||
return `${camelCase(objectMetadata.nameSingular)}`;
|
||||
case 'findDuplicates':
|
||||
return `${camelCase(objectMetadata.nameSingular)}Duplicates`;
|
||||
case 'createMany':
|
||||
return `create${pascalCase(objectMetadata.namePlural)}`;
|
||||
case 'createOne':
|
||||
return `create${pascalCase(objectMetadata.nameSingular)}`;
|
||||
case 'updateOne':
|
||||
return `update${pascalCase(objectMetadata.nameSingular)}`;
|
||||
case 'deleteOne':
|
||||
return `delete${pascalCase(objectMetadata.nameSingular)}`;
|
||||
case 'executeQuickActionOnOne':
|
||||
return `executeQuickActionOn${pascalCase(objectMetadata.nameSingular)}`;
|
||||
case 'updateMany':
|
||||
return `update${pascalCase(objectMetadata.namePlural)}`;
|
||||
case 'deleteMany':
|
||||
return `delete${pascalCase(objectMetadata.namePlural)}`;
|
||||
default:
|
||||
throw new Error(`Unknown resolver type: ${type}`);
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,7 @@
|
||||
import { FieldMetadataType } from 'src/engine-metadata/field-metadata/field-metadata.entity';
|
||||
|
||||
export const isRelationFieldMetadataType = (
|
||||
type: FieldMetadataType,
|
||||
): type is FieldMetadataType.RELATION => {
|
||||
return type === FieldMetadataType.RELATION;
|
||||
};
|
||||
@ -0,0 +1,25 @@
|
||||
interface ApolloPlaygroundOptions {
|
||||
path?: string;
|
||||
}
|
||||
|
||||
export const renderApolloPlayground = ({
|
||||
path = 'graphql',
|
||||
}: ApolloPlaygroundOptions = {}) => {
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<body style="margin: 0; overflow-x: hidden; overflow-y: hidden">
|
||||
<div id="sandbox" style="height:100vh; width:100vw;"></div>
|
||||
<script src="https://embeddable-sandbox.cdn.apollographql.com/_latest/embeddable-sandbox.umd.production.min.js"></script>
|
||||
<script>
|
||||
new window.EmbeddedSandbox({
|
||||
target: "#sandbox",
|
||||
// Pass through your server href if you are embedding on an endpoint.
|
||||
// Otherwise, you can pass whatever endpoint you want Sandbox to start up with here.
|
||||
initialEndpoint: "http://localhost:3000/${path}",
|
||||
});
|
||||
// advanced options: https://www.apollographql.com/docs/studio/explorer/sandbox#embedding-sandbox
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
};
|
||||
Reference in New Issue
Block a user