feat: dynamic graphQL schema generation based on user workspace (#1725)

* wip: refacto and start creating custom resolver

* feat: findMany & findUnique of a custom entity

* feat: wip pagination

* feat: initial metadata migration

* feat: universal findAll with pagination

* fix: clean small stuff in pagination

* fix: test

* fix: miss file

* feat: rename custom into universal

* feat: create metadata schema in default database

* Multi-tenant db schemas POC

fix tests and use query builders

remove synchronize

restore updatedAt

remove unnecessary import

use queryRunner

fix camelcase

add migrations for standard objects

Multi-tenant db schemas POC

fix tests and use query builders

remove synchronize

restore updatedAt

remove unnecessary import

use queryRunner

fix camelcase

add migrations for standard objects

poc: conditional schema at runtime

wip: try to create resolver in Nest.JS context

fix

* feat: wip add pg_graphql

* feat: setup pg_graphql during database init

* wip: dynamic resolver

* poc: dynamic resolver and query using pg_graphql

* feat: pg_graphql use ARG in Dockerfile

* feat: clean findMany & findOne dynamic resolver

* feat: get correct schema based on access token

* fix: remove old file

* fix: tests

* fix: better comment

* fix: e2e test not working, error format change due to yoga

* remove typeorm entity generation + fix jwt + fix search_path + remove anon

* fix conflict

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
Co-authored-by: corentin <corentin@twenty.com>
This commit is contained in:
Jérémy M
2023-09-28 16:27:34 +02:00
committed by GitHub
parent 485bc64b4f
commit 629bdbbf50
35 changed files with 1860 additions and 124 deletions

View File

@ -1,5 +1,5 @@
import { Type } from '@nestjs/common';
import { ArgsType, Directive, Field, ObjectType } from '@nestjs/graphql';
import { ArgsType, Field, ObjectType } from '@nestjs/graphql';
import { IsNumber, IsOptional, IsString } from 'class-validator';
@ -50,7 +50,6 @@ export function Paginated<T>(classRef: Type<T>): Type<IConnection<T>> {
public cursor!: ConnectionCursor;
@Field(() => classRef, { nullable: true })
@Directive(`@cacheControl(inheritMaxAge: true)`)
public node!: T;
}
@ -59,11 +58,9 @@ export function Paginated<T>(classRef: Type<T>): Type<IConnection<T>> {
public name = `${classRef.name}Connection`;
@Field(() => [Edge], { nullable: true })
@Directive(`@cacheControl(inheritMaxAge: true)`)
public edges!: IEdge<T>[];
@Field(() => PageInfo, { nullable: true })
@Directive(`@cacheControl(inheritMaxAge: true)`)
public pageInfo!: IPageInfo;
@Field()

View File

@ -0,0 +1,32 @@
import isObject from 'lodash.isobject';
import lodashCamelCase from 'lodash.camelcase';
import { PascalCase, PascalCasedPropertiesDeep } from 'type-fest';
export const capitalizeFirstLetter = (str: string) => {
return str.charAt(0).toUpperCase() + str.slice(1);
};
export const pascalCase = <T>(text: T) =>
capitalizeFirstLetter(
lodashCamelCase(text as unknown as string),
) as PascalCase<T>;
export const pascalCaseDeep = <T>(value: T): PascalCasedPropertiesDeep<T> => {
// Check if it's an array
if (Array.isArray(value)) {
return value.map(pascalCaseDeep) as PascalCasedPropertiesDeep<T>;
}
// Check if it's an object
if (isObject(value)) {
const result: Record<string, any> = {};
for (const key in value) {
result[pascalCase(key)] = pascalCaseDeep(value[key]);
}
return result as PascalCasedPropertiesDeep<T>;
}
return value as PascalCasedPropertiesDeep<T>;
};