feat(ai): add mcp-metadata (#13150)
Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
@ -1,11 +1,11 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { Request } from 'express';
|
||||
import { RequestContext } from 'src/engine/api/rest/types/RequestContext';
|
||||
|
||||
@Injectable()
|
||||
export class EndingBeforeInputFactory {
|
||||
create(request: Request): string | undefined {
|
||||
const cursorQuery = request.query.ending_before;
|
||||
create(request: RequestContext): string | undefined {
|
||||
const cursorQuery = request.query?.ending_before;
|
||||
|
||||
if (typeof cursorQuery !== 'string') {
|
||||
return undefined;
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
|
||||
import { Request } from 'express';
|
||||
import { RequestContext } from 'src/engine/api/rest/types/RequestContext';
|
||||
|
||||
@Injectable()
|
||||
export class LimitInputFactory {
|
||||
create(request: Request, defaultLimit = 60): number {
|
||||
if (!request.query.limit) {
|
||||
create(request: RequestContext, defaultLimit = 60): number {
|
||||
if (!request.query?.limit) {
|
||||
return defaultLimit;
|
||||
}
|
||||
const limit = +request.query.limit;
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { Request } from 'express';
|
||||
import { RequestContext } from 'src/engine/api/rest/types/RequestContext';
|
||||
|
||||
@Injectable()
|
||||
export class StartingAfterInputFactory {
|
||||
create(request: Request): string | undefined {
|
||||
const cursorQuery = request.query.starting_after;
|
||||
create(request: RequestContext): string | undefined {
|
||||
const cursorQuery = request.query?.starting_after;
|
||||
|
||||
if (typeof cursorQuery !== 'string') {
|
||||
return undefined;
|
||||
|
||||
@ -3,16 +3,25 @@ import { Injectable } from '@nestjs/common';
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
|
||||
import { fetchMetadataFields } from 'src/engine/api/rest/metadata/query-builder/utils/fetch-metadata-fields.utils';
|
||||
import {
|
||||
ObjectName,
|
||||
Singular,
|
||||
} from 'src/engine/api/rest/metadata/types/metadata-entity.type';
|
||||
import { Selectors } from 'src/engine/api/rest/metadata/types/metadata-query.type';
|
||||
|
||||
@Injectable()
|
||||
export class CreateMetadataQueryFactory {
|
||||
create(objectNameSingular: string, objectNamePlural: string): string {
|
||||
create(
|
||||
objectNameSingular: Singular<ObjectName>,
|
||||
objectNamePlural: ObjectName,
|
||||
selectors: Selectors,
|
||||
): string {
|
||||
const objectNameCapitalized = capitalize(objectNameSingular);
|
||||
|
||||
const fields = fetchMetadataFields(objectNamePlural);
|
||||
const fields = fetchMetadataFields(objectNamePlural, selectors);
|
||||
|
||||
return `
|
||||
mutation Create${objectNameCapitalized}($input: CreateOne${objectNameCapitalized}${
|
||||
mutation CreateOne${objectNameCapitalized}($input: CreateOne${objectNameCapitalized}${
|
||||
objectNameSingular === 'field' ? 'Metadata' : ''
|
||||
}Input!) {
|
||||
createOne${objectNameCapitalized}(input: $input) {
|
||||
|
||||
@ -2,9 +2,14 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
ObjectName,
|
||||
Singular,
|
||||
} from 'src/engine/api/rest/metadata/types/metadata-entity.type';
|
||||
|
||||
@Injectable()
|
||||
export class DeleteMetadataQueryFactory {
|
||||
create(objectNameSingular: string): string {
|
||||
create(objectNameSingular: Singular<ObjectName>): string {
|
||||
const objectNameCapitalized = capitalize(objectNameSingular);
|
||||
const formattedObjectName =
|
||||
objectNameCapitalized === 'RelationMetadata'
|
||||
|
||||
@ -3,12 +3,13 @@ import { Injectable } from '@nestjs/common';
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
|
||||
import { fetchMetadataFields } from 'src/engine/api/rest/metadata/query-builder/utils/fetch-metadata-fields.utils';
|
||||
import { ObjectName } from 'src/engine/api/rest/metadata/types/metadata-entity.type';
|
||||
import { Selectors } from 'src/engine/api/rest/metadata/types/metadata-query.type';
|
||||
|
||||
@Injectable()
|
||||
export class FindManyMetadataQueryFactory {
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
create(objectNamePlural): string {
|
||||
const fields = fetchMetadataFields(objectNamePlural);
|
||||
create(objectNamePlural: ObjectName, selectors: Selectors): string {
|
||||
const fields = fetchMetadataFields(objectNamePlural, selectors);
|
||||
|
||||
return `
|
||||
query FindMany${capitalize(objectNamePlural)}(
|
||||
|
||||
@ -3,11 +3,20 @@ import { Injectable } from '@nestjs/common';
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
|
||||
import { fetchMetadataFields } from 'src/engine/api/rest/metadata/query-builder/utils/fetch-metadata-fields.utils';
|
||||
import {
|
||||
ObjectName,
|
||||
Singular,
|
||||
} from 'src/engine/api/rest/metadata/types/metadata-entity.type';
|
||||
import { Selectors } from 'src/engine/api/rest/metadata/types/metadata-query.type';
|
||||
|
||||
@Injectable()
|
||||
export class FindOneMetadataQueryFactory {
|
||||
create(objectNameSingular: string, objectNamePlural: string): string {
|
||||
const fields = fetchMetadataFields(objectNamePlural);
|
||||
create(
|
||||
objectNameSingular: Singular<ObjectName>,
|
||||
objectNamePlural: ObjectName,
|
||||
selectors: Selectors,
|
||||
): string {
|
||||
const fields = fetchMetadataFields(objectNamePlural, selectors);
|
||||
|
||||
return `
|
||||
query FindOne${capitalize(objectNameSingular)}(
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
|
||||
import { Request } from 'express';
|
||||
|
||||
import { LimitInputFactory } from 'src/engine/api/rest/input-factories/limit-input.factory';
|
||||
import { EndingBeforeInputFactory } from 'src/engine/api/rest/input-factories/ending-before-input.factory';
|
||||
import { StartingAfterInputFactory } from 'src/engine/api/rest/input-factories/starting-after-input.factory';
|
||||
import { MetadataQueryVariables } from 'src/engine/api/rest/metadata/types/metadata-query-variables.type';
|
||||
import { RequestContext } from 'src/engine/api/rest/types/RequestContext';
|
||||
|
||||
@Injectable()
|
||||
export class GetMetadataVariablesFactory {
|
||||
@ -15,14 +14,17 @@ export class GetMetadataVariablesFactory {
|
||||
private readonly limitInputFactory: LimitInputFactory,
|
||||
) {}
|
||||
|
||||
create(id: string | undefined, request: Request): MetadataQueryVariables {
|
||||
create(
|
||||
id: string | undefined,
|
||||
requestContext: RequestContext,
|
||||
): MetadataQueryVariables {
|
||||
if (id) {
|
||||
return { id };
|
||||
}
|
||||
|
||||
const limit = this.limitInputFactory.create(request, 1000);
|
||||
const before = this.endingBeforeInputFactory.create(request);
|
||||
const after = this.startingAfterInputFactory.create(request);
|
||||
const limit = this.limitInputFactory.create(requestContext, 1000);
|
||||
const before = this.endingBeforeInputFactory.create(requestContext);
|
||||
const after = this.startingAfterInputFactory.create(requestContext);
|
||||
|
||||
if (before && after) {
|
||||
throw new BadRequestException(
|
||||
|
||||
@ -3,13 +3,22 @@ import { Injectable } from '@nestjs/common';
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
|
||||
import { fetchMetadataFields } from 'src/engine/api/rest/metadata/query-builder/utils/fetch-metadata-fields.utils';
|
||||
import {
|
||||
ObjectName,
|
||||
Singular,
|
||||
} from 'src/engine/api/rest/metadata/types/metadata-entity.type';
|
||||
import { Selectors } from 'src/engine/api/rest/metadata/types/metadata-query.type';
|
||||
|
||||
@Injectable()
|
||||
export class UpdateMetadataQueryFactory {
|
||||
create(objectNameSingular: string, objectNamePlural: string): string {
|
||||
create(
|
||||
objectNameSingular: Singular<ObjectName>,
|
||||
objectNamePlural: ObjectName,
|
||||
selectors: Selectors,
|
||||
): string {
|
||||
const objectNameCapitalized = capitalize(objectNameSingular);
|
||||
|
||||
const fields = fetchMetadataFields(objectNamePlural);
|
||||
const fields = fetchMetadataFields(objectNamePlural, selectors);
|
||||
|
||||
return `
|
||||
mutation Update${objectNameCapitalized}($input: UpdateOne${objectNameCapitalized}${
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
|
||||
import { Request } from 'express';
|
||||
|
||||
import { GetMetadataVariablesFactory } from 'src/engine/api/rest/metadata/query-builder/factories/get-metadata-variables.factory';
|
||||
import { FindOneMetadataQueryFactory } from 'src/engine/api/rest/metadata/query-builder/factories/find-one-metadata-query.factory';
|
||||
import { FindManyMetadataQueryFactory } from 'src/engine/api/rest/metadata/query-builder/factories/find-many-metadata-query.factory';
|
||||
@ -9,7 +7,11 @@ import { parseMetadataPath } from 'src/engine/api/rest/metadata/query-builder/ut
|
||||
import { CreateMetadataQueryFactory } from 'src/engine/api/rest/metadata/query-builder/factories/create-metadata-query.factory';
|
||||
import { UpdateMetadataQueryFactory } from 'src/engine/api/rest/metadata/query-builder/factories/update-metadata-query.factory';
|
||||
import { DeleteMetadataQueryFactory } from 'src/engine/api/rest/metadata/query-builder/factories/delete-metadata-query.factory';
|
||||
import { MetadataQuery } from 'src/engine/api/rest/metadata/types/metadata-query.type';
|
||||
import {
|
||||
MetadataQuery,
|
||||
Selectors,
|
||||
} from 'src/engine/api/rest/metadata/types/metadata-query.type';
|
||||
import { RequestContext } from 'src/engine/api/rest/types/RequestContext';
|
||||
|
||||
@Injectable()
|
||||
export class MetadataQueryBuilderFactory {
|
||||
@ -22,37 +24,53 @@ export class MetadataQueryBuilderFactory {
|
||||
private readonly getMetadataVariablesFactory: GetMetadataVariablesFactory,
|
||||
) {}
|
||||
|
||||
async get(request: Request): Promise<MetadataQuery> {
|
||||
const { id, objectNameSingular, objectNamePlural } =
|
||||
parseMetadataPath(request);
|
||||
async get(
|
||||
request: RequestContext,
|
||||
selectors?: Selectors,
|
||||
): Promise<MetadataQuery> {
|
||||
const { id, objectNameSingular, objectNamePlural } = parseMetadataPath(
|
||||
request.path,
|
||||
);
|
||||
|
||||
return {
|
||||
query: id
|
||||
? this.findOneQueryFactory.create(objectNameSingular, objectNamePlural)
|
||||
: this.findManyQueryFactory.create(objectNamePlural),
|
||||
? this.findOneQueryFactory.create(
|
||||
objectNameSingular,
|
||||
objectNamePlural,
|
||||
selectors,
|
||||
)
|
||||
: this.findManyQueryFactory.create(objectNamePlural, selectors),
|
||||
variables: this.getMetadataVariablesFactory.create(id, request),
|
||||
};
|
||||
}
|
||||
|
||||
async create(request: Request): Promise<MetadataQuery> {
|
||||
const { objectNameSingular, objectNamePlural } = parseMetadataPath(request);
|
||||
async create(
|
||||
{ path, body }: Pick<RequestContext, 'path' | 'body'>,
|
||||
selectors?: Selectors,
|
||||
): Promise<MetadataQuery> {
|
||||
const { objectNameSingular, objectNamePlural } = parseMetadataPath(path);
|
||||
|
||||
return {
|
||||
query: this.createQueryFactory.create(
|
||||
objectNameSingular,
|
||||
objectNamePlural,
|
||||
selectors,
|
||||
),
|
||||
variables: {
|
||||
input: {
|
||||
[objectNameSingular]: request.body,
|
||||
[objectNameSingular]: body,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async update(request: Request): Promise<MetadataQuery> {
|
||||
const { objectNameSingular, objectNamePlural, id } =
|
||||
parseMetadataPath(request);
|
||||
async update(
|
||||
request: Pick<RequestContext, 'path' | 'body'>,
|
||||
selectors?: Selectors,
|
||||
): Promise<MetadataQuery> {
|
||||
const { objectNameSingular, objectNamePlural, id } = parseMetadataPath(
|
||||
request.path,
|
||||
);
|
||||
|
||||
if (!id) {
|
||||
throw new BadRequestException(
|
||||
@ -64,6 +82,7 @@ export class MetadataQueryBuilderFactory {
|
||||
query: this.updateQueryFactory.create(
|
||||
objectNameSingular,
|
||||
objectNamePlural,
|
||||
selectors,
|
||||
),
|
||||
variables: {
|
||||
input: {
|
||||
@ -74,8 +93,10 @@ export class MetadataQueryBuilderFactory {
|
||||
};
|
||||
}
|
||||
|
||||
async delete(request: Request): Promise<MetadataQuery> {
|
||||
const { objectNameSingular, id } = parseMetadataPath(request);
|
||||
async delete(
|
||||
request: Pick<RequestContext, 'path' | 'body'>,
|
||||
): Promise<MetadataQuery> {
|
||||
const { objectNameSingular, id } = parseMetadataPath(request.path);
|
||||
|
||||
if (!id) {
|
||||
throw new BadRequestException(
|
||||
|
||||
@ -4,7 +4,7 @@ describe('parseMetadataPath', () => {
|
||||
it('should parse object from request path with uuid', () => {
|
||||
const request: any = { path: '/rest/metadata/fields/uuid' };
|
||||
|
||||
expect(parseMetadataPath(request)).toEqual({
|
||||
expect(parseMetadataPath(request.path)).toEqual({
|
||||
objectNameSingular: 'field',
|
||||
objectNamePlural: 'fields',
|
||||
id: 'uuid',
|
||||
@ -14,7 +14,7 @@ describe('parseMetadataPath', () => {
|
||||
it('should parse object from request path', () => {
|
||||
const request: any = { path: '/rest/metadata/fields' };
|
||||
|
||||
expect(parseMetadataPath(request)).toEqual({
|
||||
expect(parseMetadataPath(request.path)).toEqual({
|
||||
objectNameSingular: 'field',
|
||||
objectNamePlural: 'fields',
|
||||
id: undefined,
|
||||
@ -24,7 +24,7 @@ describe('parseMetadataPath', () => {
|
||||
it('should throw for wrong request path', () => {
|
||||
const request: any = { path: '/rest/metadata/INVALID' };
|
||||
|
||||
expect(() => parseMetadataPath(request)).toThrow(
|
||||
expect(() => parseMetadataPath(request.path)).toThrow(
|
||||
'Query path \'/rest/metadata/INVALID\' invalid. Metadata path "INVALID" does not exist. Valid examples: /rest/metadata/fields or /rest/metadata/objects',
|
||||
);
|
||||
});
|
||||
@ -32,7 +32,7 @@ describe('parseMetadataPath', () => {
|
||||
it('should throw for wrong request path', () => {
|
||||
const request: any = { path: '/rest/metadata/fields/uuid/toto' };
|
||||
|
||||
expect(() => parseMetadataPath(request)).toThrow(
|
||||
expect(() => parseMetadataPath(request.path)).toThrow(
|
||||
"Query path '/rest/metadata/fields/uuid/toto' invalid. Valid examples: /rest/metadata/fields or /rest/metadata/objects/id",
|
||||
);
|
||||
});
|
||||
|
||||
@ -1,68 +1,87 @@
|
||||
export const fetchMetadataFields = (objectNamePlural: string) => {
|
||||
const fields = `
|
||||
type
|
||||
name
|
||||
label
|
||||
description
|
||||
icon
|
||||
isCustom
|
||||
isActive
|
||||
isSystem
|
||||
isNullable
|
||||
createdAt
|
||||
updatedAt
|
||||
defaultValue
|
||||
options
|
||||
relation {
|
||||
type
|
||||
targetObjectMetadata {
|
||||
id
|
||||
nameSingular
|
||||
namePlural
|
||||
}
|
||||
targetFieldMetadata {
|
||||
id
|
||||
name
|
||||
}
|
||||
sourceObjectMetadata {
|
||||
id
|
||||
nameSingular
|
||||
namePlural
|
||||
}
|
||||
sourceFieldMetadata {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
`;
|
||||
import { Selectors } from 'src/engine/api/rest/metadata/types/metadata-query.type';
|
||||
|
||||
export const fetchMetadataFields = (
|
||||
objectNamePlural: string,
|
||||
selector: Selectors,
|
||||
) => {
|
||||
const defaultFields = `
|
||||
type
|
||||
name
|
||||
label
|
||||
description
|
||||
icon
|
||||
isCustom
|
||||
isActive
|
||||
isSystem
|
||||
isNullable
|
||||
createdAt
|
||||
updatedAt
|
||||
defaultValue
|
||||
options
|
||||
relation {
|
||||
type
|
||||
targetObjectMetadata {
|
||||
id
|
||||
nameSingular
|
||||
namePlural
|
||||
}
|
||||
targetFieldMetadata {
|
||||
id
|
||||
name
|
||||
}
|
||||
sourceObjectMetadata {
|
||||
id
|
||||
nameSingular
|
||||
namePlural
|
||||
}
|
||||
sourceFieldMetadata {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const fieldsSelection = selector?.fields?.join('\n') ?? defaultFields;
|
||||
|
||||
switch (objectNamePlural) {
|
||||
case 'objects':
|
||||
return `
|
||||
dataSourceId
|
||||
nameSingular
|
||||
namePlural
|
||||
labelSingular
|
||||
labelPlural
|
||||
description
|
||||
icon
|
||||
isCustom
|
||||
isActive
|
||||
isSystem
|
||||
createdAt
|
||||
updatedAt
|
||||
labelIdentifierFieldMetadataId
|
||||
imageIdentifierFieldMetadataId
|
||||
fields(paging: { first: 1000 }) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
${fields}
|
||||
}
|
||||
case 'objects': {
|
||||
const objectsSelection =
|
||||
selector?.objects?.join('\n') ??
|
||||
`
|
||||
dataSourceId
|
||||
nameSingular
|
||||
namePlural
|
||||
labelSingular
|
||||
labelPlural
|
||||
description
|
||||
icon
|
||||
isCustom
|
||||
isActive
|
||||
isSystem
|
||||
createdAt
|
||||
updatedAt
|
||||
labelIdentifierFieldMetadataId
|
||||
imageIdentifierFieldMetadataId
|
||||
`;
|
||||
|
||||
const fieldsPart = selector?.fields
|
||||
? `
|
||||
fields(paging: { first: 1000 }) {
|
||||
edges {
|
||||
node {
|
||||
${fieldsSelection}
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
`
|
||||
: '';
|
||||
|
||||
return `
|
||||
${objectsSelection}
|
||||
${fieldsPart}
|
||||
`;
|
||||
}
|
||||
case 'fields':
|
||||
return fields;
|
||||
return fieldsSelection;
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,51 +1,48 @@
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
|
||||
import { Request } from 'express';
|
||||
import {
|
||||
ObjectName,
|
||||
ObjectNameSingularAndPlural,
|
||||
} from 'src/engine/api/rest/metadata/types/metadata-entity.type';
|
||||
|
||||
const getObjectNames = (
|
||||
objectName: ObjectName,
|
||||
): ObjectNameSingularAndPlural => {
|
||||
return {
|
||||
objectNameSingular: objectName.substring(
|
||||
0,
|
||||
objectName.length - 1,
|
||||
) as ObjectNameSingularAndPlural['objectNameSingular'],
|
||||
objectNamePlural: objectName,
|
||||
};
|
||||
};
|
||||
|
||||
export const parseMetadataPath = (
|
||||
request: Request,
|
||||
): { objectNameSingular: string; objectNamePlural: string; id?: string } => {
|
||||
const queryAction = request.path.replace('/rest/metadata/', '').split('/');
|
||||
path: string,
|
||||
): ObjectNameSingularAndPlural => {
|
||||
const queryAction = 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`,
|
||||
`Query path '${path}' invalid. Valid examples: /rest/metadata/fields or /rest/metadata/objects/id`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!['fields', 'objects'].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`,
|
||||
`Query path '${path}' invalid. Metadata path "${queryAction[0]}" does not exist. Valid examples: /rest/metadata/fields or /rest/metadata/objects`,
|
||||
);
|
||||
}
|
||||
|
||||
const objectName = queryAction[0];
|
||||
const hasId = queryAction.length === 2;
|
||||
|
||||
if (queryAction.length === 2) {
|
||||
switch (objectName) {
|
||||
case 'fields':
|
||||
return {
|
||||
objectNameSingular: 'field',
|
||||
objectNamePlural: 'fields',
|
||||
id: queryAction[1],
|
||||
};
|
||||
case 'objects':
|
||||
return {
|
||||
objectNameSingular: 'object',
|
||||
objectNamePlural: 'objects',
|
||||
id: queryAction[1],
|
||||
};
|
||||
default:
|
||||
return { objectNameSingular: '', objectNamePlural: '', id: '' };
|
||||
}
|
||||
} else {
|
||||
switch (objectName) {
|
||||
case 'fields':
|
||||
return { objectNameSingular: 'field', objectNamePlural: 'fields' };
|
||||
case 'objects':
|
||||
return { objectNameSingular: 'object', objectNamePlural: 'objects' };
|
||||
default:
|
||||
return { objectNameSingular: '', objectNamePlural: '' };
|
||||
}
|
||||
}
|
||||
const { objectNameSingular, objectNamePlural } = getObjectNames(
|
||||
queryAction[0] as ObjectName,
|
||||
);
|
||||
|
||||
return {
|
||||
objectNameSingular,
|
||||
objectNamePlural,
|
||||
...(hasId ? { id: queryAction[1] } : {}),
|
||||
};
|
||||
};
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
export type Singular<S extends string> = S extends `${infer Stem}s` ? Stem : S;
|
||||
|
||||
export type ObjectName = 'fields' | 'objects';
|
||||
|
||||
export type ObjectNameSingularAndPlural<
|
||||
ObjectNameGeneric extends ObjectName = ObjectName,
|
||||
> = {
|
||||
objectNameSingular: Singular<ObjectNameGeneric>;
|
||||
objectNamePlural: ObjectNameGeneric;
|
||||
id?: string;
|
||||
};
|
||||
@ -4,3 +4,7 @@ export type MetadataQuery = {
|
||||
query: string;
|
||||
variables: MetadataQueryVariables;
|
||||
};
|
||||
|
||||
export type Selectors =
|
||||
| { fields?: Array<string>; objects?: Array<string> }
|
||||
| undefined;
|
||||
|
||||
@ -19,5 +19,6 @@ import { RestApiMetadataController } from 'src/engine/api/rest/metadata/rest-api
|
||||
],
|
||||
controllers: [RestApiMetadataController],
|
||||
providers: [RestApiService, RestApiMetadataService],
|
||||
exports: [RestApiMetadataService, RestApiService],
|
||||
})
|
||||
export class RestApiModule {}
|
||||
|
||||
@ -2,12 +2,10 @@ import { HttpService } from '@nestjs/axios';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { Request } from 'express';
|
||||
|
||||
import { Query } from 'src/engine/api/rest/core/types/query.type';
|
||||
import { RestApiException } from 'src/engine/api/rest/errors/RestApiException';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { getServerUrl } from 'src/utils/get-server-url';
|
||||
import { RequestContext } from 'src/engine/api/rest/types/RequestContext';
|
||||
|
||||
export enum GraphqlApiType {
|
||||
CORE = 'core',
|
||||
@ -16,18 +14,15 @@ export enum GraphqlApiType {
|
||||
|
||||
@Injectable()
|
||||
export class RestApiService {
|
||||
constructor(
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
private readonly httpService: HttpService,
|
||||
) {}
|
||||
constructor(private readonly httpService: HttpService) {}
|
||||
|
||||
async call(graphqlApiType: GraphqlApiType, request: Request, data: Query) {
|
||||
const baseUrl = getServerUrl(
|
||||
request,
|
||||
this.twentyConfigService.get('SERVER_URL'),
|
||||
);
|
||||
async call(
|
||||
graphqlApiType: GraphqlApiType,
|
||||
requestContext: RequestContext,
|
||||
data: Query,
|
||||
) {
|
||||
let response: AxiosResponse;
|
||||
const url = `${baseUrl}/${
|
||||
const url = `${requestContext.baseUrl}/${
|
||||
graphqlApiType === GraphqlApiType.CORE
|
||||
? 'graphql'
|
||||
: GraphqlApiType.METADATA
|
||||
@ -37,7 +32,7 @@ export class RestApiService {
|
||||
response = await this.httpService.axiosRef.post(url, data, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: request.headers.authorization,
|
||||
Authorization: requestContext.headers.authorization,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
import { Request } from 'express';
|
||||
|
||||
export type RequestContext = {
|
||||
headers: Request['headers'];
|
||||
baseUrl: string;
|
||||
path: Request['path'];
|
||||
body?: Request['body'];
|
||||
query?: Request['query'];
|
||||
};
|
||||
Reference in New Issue
Block a user