diff --git a/packages/crud/src/crud/crud-routes.factory.ts b/packages/crud/src/crud/crud-routes.factory.ts index 57b33b3b..a0c1fdf5 100644 --- a/packages/crud/src/crud/crud-routes.factory.ts +++ b/packages/crud/src/crud/crud-routes.factory.ts @@ -1,9 +1,10 @@ import { RequestMethod } from '@nestjs/common'; import { RouteParamtypes } from '@nestjs/common/enums/route-paramtypes.enum'; import { - hasLength, + isFalse, isArrayFull, isObjectFull, + isFunction, objKeys, isIn, isEqual, @@ -24,6 +25,7 @@ import { CrudConfigService } from '../module'; export class CrudRoutesFactory { protected options: MergedCrudOptions; + protected swaggerModels: any = {}; constructor(private target: any, options: CrudOptions) { this.options = options; @@ -108,16 +110,30 @@ export class CrudRoutesFactory { if (!isObjectFull(this.options.serialize)) { this.options.serialize = {}; } - this.options.serialize.get = this.options.serialize.get || this.modelType; - this.options.serialize.getMany = - this.options.serialize.getMany || - SerializeHelper.createGetManyDto(this.options.serialize.get, this.modelName); - this.options.serialize.create = this.options.serialize.create || this.modelType; - this.options.serialize.update = this.options.serialize.update || this.modelType; - this.options.serialize.replace = this.options.serialize.replace || this.modelType; - this.options.serialize.delete = this.options.routes.deleteOneBase.returnDeleted - ? this.options.serialize.delete || this.modelType - : undefined; + this.options.serialize.get = isFalse(this.options.serialize.get) + ? false + : this.options.serialize.get || this.modelType; + this.options.serialize.getMany = isFalse(this.options.serialize.getMany) + ? false + : this.options.serialize.getMany + ? this.options.serialize.getMany + : isFalse(this.options.serialize.get) + ? /* istanbul ignore next */ false + : SerializeHelper.createGetManyDto(this.options.serialize.get, this.modelName); + this.options.serialize.create = isFalse(this.options.serialize.create) + ? false + : this.options.serialize.create || this.modelType; + this.options.serialize.update = isFalse(this.options.serialize.update) + ? false + : this.options.serialize.update || this.modelType; + this.options.serialize.replace = isFalse(this.options.serialize.replace) + ? false + : this.options.serialize.replace || this.modelType; + this.options.serialize.delete = + isFalse(this.options.serialize.delete) || + !this.options.routes.deleteOneBase.returnDeleted + ? false + : this.options.serialize.delete || this.modelType; R.setCrudOptions(this.options, this.target); } @@ -241,7 +257,28 @@ export class CrudRoutesFactory { } private setResponseModels() { - Swagger.setExtraModels(this.options); + const modelType = isFunction(this.modelType) + ? this.modelType + : SerializeHelper.createGetOneResponseDto(this.modelName); + this.swaggerModels.get = isFunction(this.options.serialize.get) + ? this.options.serialize.get + : modelType; + this.swaggerModels.getMany = + this.options.serialize.getMany || + SerializeHelper.createGetManyDto(this.swaggerModels.get, this.modelName); + this.swaggerModels.create = isFunction(this.options.serialize.create) + ? this.options.serialize.create + : modelType; + this.swaggerModels.update = isFunction(this.options.serialize.update) + ? this.options.serialize.update + : modelType; + this.swaggerModels.replace = isFunction(this.options.serialize.replace) + ? this.options.serialize.replace + : modelType; + this.swaggerModels.delete = isFunction(this.options.serialize.delete) + ? this.options.serialize.delete + : modelType; + Swagger.setExtraModels(this.swaggerModels); } private createRoutes(routesSchema: BaseRoute[]) { @@ -464,7 +501,8 @@ export class CrudRoutesFactory { private setSwaggerResponseOk(name: BaseRouteName) { const metadata = Swagger.getResponseOk(this.targetProto[name]); const metadataToAdd = - Swagger.createResponseMeta(name, this.options) || /* istanbul ignore next */ {}; + Swagger.createResponseMeta(name, this.options, this.swaggerModels) || + /* istanbul ignore next */ {}; Swagger.setResponseOk({ ...metadata, ...metadataToAdd }, this.targetProto[name]); } diff --git a/packages/crud/src/crud/serialize.helper.ts b/packages/crud/src/crud/serialize.helper.ts index 0c5176d2..83ae6ce6 100644 --- a/packages/crud/src/crud/serialize.helper.ts +++ b/packages/crud/src/crud/serialize.helper.ts @@ -1,6 +1,5 @@ -import { Type, Transform, classToPlainFromExist, classToPlain } from 'class-transformer'; +import { Type } from 'class-transformer'; import { GetManyDefaultResponse } from '../interfaces'; -import { safeRequire } from '../util'; import { ApiProperty } from './swagger.helper'; export class SerializeHelper { @@ -30,4 +29,15 @@ export class SerializeHelper { return GetManyResponseDto; } + + static createGetOneResponseDto(resourceName: string): any { + class GetOneResponseDto {} + + Object.defineProperty(GetOneResponseDto, 'name', { + writable: false, + value: `${resourceName}ResponseDto`, + }); + + return GetOneResponseDto; + } } diff --git a/packages/crud/src/crud/swagger.helper.ts b/packages/crud/src/crud/swagger.helper.ts index 0db12cfc..25299399 100644 --- a/packages/crud/src/crud/swagger.helper.ts +++ b/packages/crud/src/crud/swagger.helper.ts @@ -1,5 +1,5 @@ import { HttpStatus } from '@nestjs/common'; -import { objKeys, isString } from '@nestjsx/util'; +import { objKeys, isString, isFunction } from '@nestjsx/util'; import { RequestQueryBuilder } from '@nestjsx/crud-request'; import { safeRequire } from '../util'; @@ -38,18 +38,17 @@ export class Swagger { } } - static setExtraModels(options: MergedCrudOptions) { + static setExtraModels(swaggerModels: any) { /* istanbul ignore else */ if (swaggerConst) { - const { serialize } = options; - const meta = Swagger.getExtraModels(serialize.get); + const meta = Swagger.getExtraModels(swaggerModels.get); const models: any[] = [ ...meta, - ...objKeys(serialize) - .map((name) => serialize[name]) - .filter((one) => one && one.name !== serialize.get.name), + ...objKeys(swaggerModels) + .map((name) => swaggerModels[name]) + .filter((one) => one && one.name !== swaggerModels.get.name), ]; - R.set(swaggerConst.DECORATORS.API_EXTRA_MODELS, models, serialize.get); + R.set(swaggerConst.DECORATORS.API_EXTRA_MODELS, models, swaggerModels.get); } } @@ -80,25 +79,29 @@ export class Swagger { return swaggerConst ? R.get(swaggerConst.DECORATORS.API_RESPONSE, func) || {} : {}; } - static createResponseMeta(name: BaseRouteName, options: MergedCrudOptions): any { + static createResponseMeta( + name: BaseRouteName, + options: MergedCrudOptions, + swaggerModels: any, + ): any { /* istanbul ignore else */ if (swagger) { - const { routes, serialize, query } = options; + const { routes, query } = options; switch (name) { case 'getOneBase': - return { [HttpStatus.OK]: { type: serialize.get } }; + return { [HttpStatus.OK]: { type: swaggerModels.get } }; case 'getManyBase': return { [HttpStatus.OK]: query.alwaysPaginate - ? { type: serialize.getMany } + ? { type: swaggerModels.getMany } : { schema: { oneOf: [ - { $ref: swagger.getSchemaPath(serialize.getMany.name) }, + { $ref: swagger.getSchemaPath(swaggerModels.getMany.name) }, { type: 'array', - items: { $ref: swagger.getSchemaPath(serialize.get.name) }, + items: { $ref: swagger.getSchemaPath(swaggerModels.get.name) }, }, ], }, @@ -107,30 +110,30 @@ export class Swagger { case 'createOneBase': return { [HttpStatus.CREATED]: { - schema: { $ref: swagger.getSchemaPath(serialize.create.name) }, + schema: { $ref: swagger.getSchemaPath(swaggerModels.create.name) }, }, }; case 'createManyBase': return { - [HttpStatus.CREATED]: serialize.createMany + [HttpStatus.CREATED]: swaggerModels.createMany ? /* istanbul ignore next */ { - schema: { $ref: swagger.getSchemaPath(serialize.createMany.name) }, + schema: { $ref: swagger.getSchemaPath(swaggerModels.createMany.name) }, } : { schema: { type: 'array', - items: { $ref: swagger.getSchemaPath(serialize.create.name) }, + items: { $ref: swagger.getSchemaPath(swaggerModels.create.name) }, }, }, }; case 'deleteOneBase': return { [HttpStatus.OK]: routes.deleteOneBase.returnDeleted - ? { schema: { $ref: swagger.getSchemaPath(serialize.delete.name) } } + ? { schema: { $ref: swagger.getSchemaPath(swaggerModels.delete.name) } } : {}, }; default: - const dtoName = serialize[name.split('OneBase')[0]].name; + const dtoName = swaggerModels[name.split('OneBase')[0]].name; return { [HttpStatus.OK]: { schema: { $ref: swagger.getSchemaPath(dtoName) }, diff --git a/packages/crud/src/interceptors/crud-response.interceptor.ts b/packages/crud/src/interceptors/crud-response.interceptor.ts index 5b913269..9b9b6fa3 100644 --- a/packages/crud/src/interceptors/crud-response.interceptor.ts +++ b/packages/crud/src/interceptors/crud-response.interceptor.ts @@ -4,7 +4,7 @@ import { Injectable, NestInterceptor, } from '@nestjs/common'; -import { isNil } from '@nestjsx/util'; +import { isFalse, isObject, isFunction } from '@nestjsx/util'; import { classToPlain, classToPlainFromExist } from 'class-transformer'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @@ -32,11 +32,15 @@ export class CrudResponseInterceptor extends CrudBaseInterceptor return next.handle().pipe(map((data) => this.serialize(context, data))); } - protected transform(dto: any, data: any, forced = false) { - if (!(dto && data && data.constructor !== Object) && !forced) { + protected transform(dto: any, data: any) { + if (!isObject(data) || isFalse(dto)) { return data; } + if (!isFunction(dto)) { + return data.constructor !== Object ? classToPlain(data) : data; + } + return data instanceof dto ? classToPlain(data) : classToPlain(classToPlainFromExist(data, new dto())); @@ -50,17 +54,9 @@ export class CrudResponseInterceptor extends CrudBaseInterceptor switch (action) { case CrudActions.ReadAll: - const defaultPaginated = - !isNil(data) && - !isNil(data.data) && - !isNil(data.count) && - !isNil(data.total) && - !isNil(data.page) && - !isNil(data.pageCount); - return isArray ? (data as any[]).map((item) => this.transform(serialize.get, item)) - : this.transform(dto, data, defaultPaginated); + : this.transform(dto, data); case CrudActions.CreateMany: return isArray ? (data as any[]).map((item) => this.transform(dto, item)) diff --git a/packages/crud/src/interfaces/serialize-options.interface.ts b/packages/crud/src/interfaces/serialize-options.interface.ts index ce3e6224..6ac0c60f 100644 --- a/packages/crud/src/interfaces/serialize-options.interface.ts +++ b/packages/crud/src/interfaces/serialize-options.interface.ts @@ -1,9 +1,11 @@ +import { Type } from '@nestjs/common'; + export interface SerializeOptions { - getMany?: any; - get?: any; - create?: any; - createMany?: any; - update?: any; - replace?: any; - delete?: any; + getMany?: Type | false; + get?: Type | false; + create?: Type | false; + createMany?: Type | false; + update?: Type | false; + replace?: Type | false; + delete?: Type | false; } diff --git a/packages/crud/test/crud.serialize.options.spec.ts b/packages/crud/test/crud.serialize.options.spec.ts index a1701af1..0e714bb9 100644 --- a/packages/crud/test/crud.serialize.options.spec.ts +++ b/packages/crud/test/crud.serialize.options.spec.ts @@ -98,9 +98,33 @@ describe('#crud', () => { constructor(@Inject(SERVICE2_TOKEN) public service: TestSerializeService) {} } + @Crud({ + model: { + type: { name: 'SomeModel' }, + }, + serialize: { + get: false, + getMany: false, + create: false, + createMany: false, + update: false, + replace: false, + }, + }) + @Controller('test5') + class Test5Controller { + constructor(@Inject(SERVICE2_TOKEN) public service: TestSerializeService) {} + } + beforeAll(async () => { const fixture = await Test.createTestingModule({ - controllers: [TestController, Test2Controller, Test3Controller, Test4Controller], + controllers: [ + TestController, + Test2Controller, + Test3Controller, + Test4Controller, + Test5Controller, + ], providers: [ { provide: APP_FILTER, useClass: HttpExceptionFilter }, { @@ -160,7 +184,7 @@ describe('#crud', () => { }); }); - describe('#getManyBase', () => { + describe('#getOneBase', () => { it('should return model', (done) => { return request(server) .get('/test4/1') @@ -181,6 +205,18 @@ describe('#crud', () => { done(); }); }); + it('should return model without serializing', (done) => { + return request(server) + .get('/test5/1') + .expect(200) + .end((_, res) => { + expect(res.body.id).toBeDefined(); + expect(res.body.name).toBeDefined(); + expect(res.body.email).toBeDefined(); + expect(res.body.isActive).toBeDefined(); + done(); + }); + }); }); describe('#deleteManyBase', () => {