Skip to content

Commit

Permalink
Merge pull request #142 from Pigrabbit/feature/fix-read-many-swagger-…
Browse files Browse the repository at this point in the history
…query

Feature/fix read many swagger query
  • Loading branch information
Pigrabbit committed May 21, 2023
2 parents df70f6c + b56701e commit 4d169fb
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 27 deletions.
27 changes: 26 additions & 1 deletion spec/base/base.controller.swagger.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ describe('BaseController Swagger Decorator', () => {
expect(routeSet[readMany].root?.method).toEqual('get');
expect(routeSet[readMany].root?.summary).toEqual("read many from 'Base' Table");
expect(routeSet[readMany].root?.description).toEqual("Fetch multiple entities in 'Base' Table");
expect(routeSet[readMany].root?.parameters).toHaveLength(2);
expect(routeSet[readMany].root?.parameters).toHaveLength(4);
expect(routeSet[readMany].root?.parameters).toEqual(
expect.arrayContaining([
{
Expand All @@ -77,6 +77,31 @@ describe('BaseController Swagger Decorator', () => {
description: 'Query parameters for Cursor Pagination',
schema: { type: 'string' },
},
{
name: 'name',
in: 'query',
required: false,
description: 'Query string filter by BaseEntity.name',
schema: { type: 'string' },
},
{
name: 'type',
in: 'query',
required: false,
description: 'Query string filter by BaseEntity.type',
schema: { type: 'string' },
},
{
name: 'description',
in: 'query',
required: false,
description: 'Query string filter by BaseEntity.description',
schema: { type: 'string' },
},
]),
);
expect(routeSet[readMany].root?.parameters).not.toEqual(
expect.arrayContaining([
{
name: 'query',
in: 'query',
Expand Down
31 changes: 19 additions & 12 deletions src/lib/crud.route.factory.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/* eslint-disable @typescript-eslint/ban-types */
import { ExecutionContext, HttpStatus, Type } from '@nestjs/common';
import {
INTERCEPTORS_METADATA,
CUSTOM_ROUTE_ARGS_METADATA,
PATH_METADATA,
HTTP_CODE_METADATA,
INTERCEPTORS_METADATA,
METHOD_METADATA,
ROUTE_ARGS_METADATA,
PARAMTYPES_METADATA,
HTTP_CODE_METADATA,
PATH_METADATA,
ROUTE_ARGS_METADATA,
} from '@nestjs/common/constants';
import { DECORATORS } from '@nestjs/swagger/dist/constants';
import { BaseEntity, getMetadataArgsStorage } from 'typeorm';
Expand All @@ -16,22 +16,22 @@ import { MetadataUtils } from 'typeorm/metadata-builder/MetadataUtils';
import { CRUD_ROUTE_ARGS, OVERRIDE_METHOD_METADATA } from './constants';
import { CRUD_POLICY } from './crud.policy';
import { RequestSearchDto } from './dto/request-search.dto';
import { CreateRequestDto } from './dto/request.dto';
import { CreateRequestDto, getPropertyNamesFromMetadata } from './dto/request.dto';
import {
Column,
CrudCreateRequest,
CrudDeleteOneRequest,
CrudOptions,
Method,
CrudReadManyRequest,
CrudReadOneRequest,
CrudRecoverRequest,
CrudSearchRequest,
CrudUpdateOneRequest,
CrudCreateRequest,
CrudDeleteOneRequest,
PrimaryKey,
Column,
CrudRecoverRequest,
FactoryOption,
Method,
PaginationType,
PAGINATION_SWAGGER_QUERY,
FactoryOption,
PrimaryKey,
} from './interface';
import { CrudLogger } from './provider/crud-logger';
import { capitalizeFirstLetter, isSomeEnum } from './util';
Expand Down Expand Up @@ -258,6 +258,13 @@ export class CrudRouteFactory {
required: false,
description: `Query parameters for ${capitalizeFirstLetter(this.paginationType)} Pagination`,
})),
...getPropertyNamesFromMetadata(this.crudOptions.entity, method).map((property) => ({
name: property,
type: 'string',
in: 'query',
required: false,
description: `Query string filter by ${this.crudOptions.entity.name}.${property}`,
})),
);
}
if (method === Method.READ_ONE) {
Expand Down
25 changes: 16 additions & 9 deletions src/lib/dto/request.dto.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,40 @@
/* eslint-disable max-classes-per-file */
import { mixin } from '@nestjs/common';
import { PickType } from '@nestjs/swagger';
import { MetadataStorage, getMetadataStorage } from 'class-validator';
import { getMetadataStorage, MetadataStorage } from 'class-validator';
import { ValidationMetadata } from 'class-validator/types/metadata/ValidationMetadata';
import { BaseEntity } from 'typeorm';

import { Method } from '../interface';
import { capitalizeFirstLetter } from '../util';

export function CreateRequestDto(parentClass: typeof BaseEntity, group: Method) {
const propertyNamesAppliedValidation = getPropertyNamesFromMetadata(parentClass, group);

class PickClass extends PickType(parentClass, propertyNamesAppliedValidation as Array<keyof BaseEntity>) {}
const requestDto = mixin(PickClass);
Object.defineProperty(requestDto, 'name', {
value: `${capitalizeFirstLetter(group)}${parentClass.name}Dto`,
});

return requestDto;
}

export function getPropertyNamesFromMetadata(parentClass: typeof BaseEntity, group: Method): string[] {
const metadataStorage: MetadataStorage = getMetadataStorage();

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const getTargetValidationMetadatasArgs = [parentClass, null!, false, false];
const targetMetadata: ReturnType<typeof metadataStorage.getTargetValidationMetadatas> = (
metadataStorage.getTargetValidationMetadatas as (...args: unknown[]) => ValidationMetadata[]
)(...getTargetValidationMetadatasArgs);

const propertyNamesAppliedValidation = [
...new Set(
targetMetadata
.filter(({ groups, always }) => always === true || (groups ?? []).includes(group))
.map(({ propertyName }) => propertyName),
),
];

class PickClass extends PickType(parentClass, propertyNamesAppliedValidation as Array<keyof BaseEntity>) {}
const requestDto = mixin(PickClass);
Object.defineProperty(requestDto, 'name', {
value: `${capitalizeFirstLetter(group)}${parentClass.name}Dto`,
});

return requestDto;
return propertyNamesAppliedValidation;
}
6 changes: 1 addition & 5 deletions src/lib/interface/pagination.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,8 @@ export const PAGINATION_SWAGGER_QUERY: Record<PaginationType, Array<{ name: stri
[PaginationType.OFFSET]: [
{ name: 'limit', type: 'integer' },
{ name: 'offset', type: 'integer' },
{ name: 'query', type: 'string' },
],
[PaginationType.CURSOR]: [
{ name: 'nextCursor', type: 'string' },
{ name: 'query', type: 'string' },
],
[PaginationType.CURSOR]: [{ name: 'nextCursor', type: 'string' }],
};

export interface PaginationRequestAbstract {
Expand Down

0 comments on commit 4d169fb

Please sign in to comment.