Throw an error if workspace id has no object (#2857)

* Throw an error if workspace id has no object

* Request only plurial object names

* Fix tests

* Fix query

* Handle graphql errors
This commit is contained in:
martmull 2023-12-07 12:32:29 +01:00 committed by GitHub
parent 06936c3c2a
commit 3cd1ec21e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 36 additions and 23 deletions

View File

@ -27,6 +27,7 @@ import OptionTable from '@site/src/theme/OptionTable'
['REDIS_HOST', '127.0.0.1', 'Redis connection host'], ['REDIS_HOST', '127.0.0.1', 'Redis connection host'],
['REDIS_PORT', '6379', 'Redis connection port'], ['REDIS_PORT', '6379', 'Redis connection port'],
['FRONT_BASE_URL', 'http://localhost:3001', 'Url to the hosted frontend'], ['FRONT_BASE_URL', 'http://localhost:3001', 'Url to the hosted frontend'],
['SERVER_URL', 'http://localhost:3000', 'Url to the hosted server'],
['PORT', '3000', 'Port'], ['PORT', '3000', 'Port'],
]}></OptionTable> ]}></OptionTable>
@ -97,4 +98,4 @@ import OptionTable from '@site/src/theme/OptionTable'
<OptionTable options={[ <OptionTable options={[
['DEBUG_MODE', 'true', 'Activate debug mode'], ['DEBUG_MODE', 'true', 'Activate debug mode'],
['SIGN_IN_PREFILLED', 'true', 'Prefill the Signin form for usage in a demo or dev enviroment'], ['SIGN_IN_PREFILLED', 'true', 'Prefill the Signin form for usage in a demo or dev enviroment'],
]}></OptionTable> ]}></OptionTable>

View File

@ -30,3 +30,4 @@ SIGN_IN_PREFILLED=true
# REDIS_HOST=127.0.0.1 # REDIS_HOST=127.0.0.1
# REDIS_PORT=6379 # REDIS_PORT=6379
# DEMO_WORKSPACE_IDS=REPLACE_ME_WITH_A_RANDOM_UUID # DEMO_WORKSPACE_IDS=REPLACE_ME_WITH_A_RANDOM_UUID
# SERVER_URL=http://localhost:3000

View File

@ -12,6 +12,7 @@ import { UpdateVariablesFactory } from 'src/core/api-rest/api-rest-query-builder
import { GetVariablesFactory } from 'src/core/api-rest/api-rest-query-builder/factories/get-variables.factory'; import { GetVariablesFactory } from 'src/core/api-rest/api-rest-query-builder/factories/get-variables.factory';
import { ObjectMetadataService } from 'src/metadata/object-metadata/object-metadata.service'; import { ObjectMetadataService } from 'src/metadata/object-metadata/object-metadata.service';
import { TokenService } from 'src/core/auth/services/token.service'; import { TokenService } from 'src/core/auth/services/token.service';
import { EnvironmentService } from 'src/integrations/environment/environment.service';
describe('ApiRestQueryBuilderFactory', () => { describe('ApiRestQueryBuilderFactory', () => {
let service: ApiRestQueryBuilderFactory; let service: ApiRestQueryBuilderFactory;
@ -31,6 +32,7 @@ describe('ApiRestQueryBuilderFactory', () => {
{ provide: GetVariablesFactory, useValue: {} }, { provide: GetVariablesFactory, useValue: {} },
{ provide: ObjectMetadataService, useValue: {} }, { provide: ObjectMetadataService, useValue: {} },
{ provide: TokenService, useValue: {} }, { provide: TokenService, useValue: {} },
{ provide: EnvironmentService, useValue: {} },
], ],
}).compile(); }).compile();

View File

@ -17,6 +17,7 @@ import { parsePath } from 'src/core/api-rest/api-rest-query-builder/utils/parse-
import { computeDepth } from 'src/core/api-rest/api-rest-query-builder/utils/compute-depth.utils'; import { computeDepth } from 'src/core/api-rest/api-rest-query-builder/utils/compute-depth.utils';
import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity'; import { ObjectMetadataEntity } from 'src/metadata/object-metadata/object-metadata.entity';
import { ApiRestQuery } from 'src/core/api-rest/types/api-rest-query.type'; import { ApiRestQuery } from 'src/core/api-rest/types/api-rest-query.type';
import { EnvironmentService } from 'src/integrations/environment/environment.service';
@Injectable() @Injectable()
export class ApiRestQueryBuilderFactory { export class ApiRestQueryBuilderFactory {
@ -32,6 +33,7 @@ export class ApiRestQueryBuilderFactory {
private readonly getVariablesFactory: GetVariablesFactory, private readonly getVariablesFactory: GetVariablesFactory,
private readonly objectMetadataService: ObjectMetadataService, private readonly objectMetadataService: ObjectMetadataService,
private readonly tokenService: TokenService, private readonly tokenService: TokenService,
private readonly environmentService: EnvironmentService,
) {} ) {}
async getObjectMetadata(request: Request): Promise<{ async getObjectMetadata(request: Request): Promise<{
@ -43,29 +45,27 @@ export class ApiRestQueryBuilderFactory {
const objectMetadataItems = const objectMetadataItems =
await this.objectMetadataService.findManyWithinWorkspace(workspaceId); await this.objectMetadataService.findManyWithinWorkspace(workspaceId);
const { id, object: parsedObject } = parsePath(request); if (!objectMetadataItems.length) {
throw new BadRequestException(
let objectNameKey = 'namePlural'; `No object was found for the workspace associated with this API key. You may generate a new one here ${this.environmentService.getFrontBaseUrl()}/settings/developers/api-keys`,
let wrongObjectNameKey = 'nameSingular'; );
if (id) {
objectNameKey = 'nameSingular';
wrongObjectNameKey = 'namePlural';
} }
const { object: parsedObject } = parsePath(request);
const [objectMetadata] = objectMetadataItems.filter( const [objectMetadata] = objectMetadataItems.filter(
(object) => object[objectNameKey] === parsedObject, (object) => object.namePlural === parsedObject,
); );
if (!objectMetadata) { if (!objectMetadata) {
const [wrongObjectMetadata] = objectMetadataItems.filter( const [wrongObjectMetadata] = objectMetadataItems.filter(
(object) => object[wrongObjectNameKey] === parsedObject, (object) => object.nameSingular === parsedObject,
); );
let hint = 'eg: companies'; let hint = 'eg: companies';
if (wrongObjectMetadata) { if (wrongObjectMetadata) {
hint = `Did you mean '${wrongObjectMetadata[objectNameKey]}'?`; hint = `Did you mean '${wrongObjectMetadata.namePlural}'?`;
} }
throw new BadRequestException( throw new BadRequestException(

View File

@ -7,7 +7,7 @@ import { ApiRestResponse } from 'src/core/api-rest/types/api-rest-response.type'
const handleResult = (res: Response, result: ApiRestResponse) => { const handleResult = (res: Response, result: ApiRestResponse) => {
if (result.data.error) { if (result.data.error) {
res.status(400).send(result.data); res.status(result.data.status || 400).send({ error: result.data.error });
} else { } else {
res.send(result.data); res.send(result.data);
} }

View File

@ -21,15 +21,15 @@ export class ApiRestService {
request: Request, request: Request,
data: ApiRestQuery, data: ApiRestQuery,
): Promise<ApiRestResponse> { ): Promise<ApiRestResponse> {
return await axios.post( const baseUrl =
`${request.protocol}://${request.get('host')}/graphql`, this.environmentService.getServerUrl() ||
data, `${request.protocol}://${request.get('host')}`;
{
headers: { return await axios.post(`${baseUrl}/graphql`, data, {
Authorization: request.headers.authorization, headers: {
}, Authorization: request.headers.authorization,
}, },
); });
} }
async get(request: Request): Promise<ApiRestResponse> { async get(request: Request): Promise<ApiRestResponse> {
@ -38,7 +38,7 @@ export class ApiRestService {
return await this.callGraphql(request, data); return await this.callGraphql(request, data);
} catch (err) { } catch (err) {
return { data: { error: `${err}` } }; return { data: { error: `${err}`, status: err.response.status } };
} }
} }

View File

@ -1 +1 @@
export type ApiRestResponse = { data: { error?: string } }; export type ApiRestResponse = { data: { error?: string; status?: number } };

View File

@ -50,6 +50,10 @@ export class EnvironmentService {
return this.configService.get<string>('FRONT_BASE_URL')!; return this.configService.get<string>('FRONT_BASE_URL')!;
} }
getServerUrl(): string {
return this.configService.get<string>('SERVER_URL')!;
}
getAccessTokenSecret(): string { getAccessTokenSecret(): string {
return this.configService.get<string>('ACCESS_TOKEN_SECRET')!; return this.configService.get<string>('ACCESS_TOKEN_SECRET')!;
} }

View File

@ -60,6 +60,11 @@ export class EnvironmentVariables {
@IsUrl({ require_tld: false }) @IsUrl({ require_tld: false })
FRONT_BASE_URL: string; FRONT_BASE_URL: string;
// Frontend URL
@IsUrl({ require_tld: false })
@IsOptional()
SERVER_URL: string;
// Json Web Token // Json Web Token
@IsString() @IsString()
ACCESS_TOKEN_SECRET: string; ACCESS_TOKEN_SECRET: string;