Skip to content

Commit

Permalink
fix(crud): updated CrudSerializeInterceptor, swagger response models
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelyali committed Dec 19, 2019
1 parent b5d2b9e commit 2864081
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 56 deletions.
64 changes: 51 additions & 13 deletions packages/crud/src/crud/crud-routes.factory.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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[]) {
Expand Down Expand Up @@ -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]);
}

Expand Down
14 changes: 12 additions & 2 deletions packages/crud/src/crud/serialize.helper.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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;
}
}
43 changes: 23 additions & 20 deletions packages/crud/src/crud/swagger.helper.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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) },
},
],
},
Expand All @@ -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) },
Expand Down
20 changes: 8 additions & 12 deletions packages/crud/src/interceptors/crud-response.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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()));
Expand All @@ -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))
Expand Down
16 changes: 9 additions & 7 deletions packages/crud/src/interfaces/serialize-options.interface.ts
Original file line number Diff line number Diff line change
@@ -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<any> | false;
get?: Type<any> | false;
create?: Type<any> | false;
createMany?: Type<any> | false;
update?: Type<any> | false;
replace?: Type<any> | false;
delete?: Type<any> | false;
}
40 changes: 38 additions & 2 deletions packages/crud/test/crud.serialize.options.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
{
Expand Down Expand Up @@ -160,7 +184,7 @@ describe('#crud', () => {
});
});

describe('#getManyBase', () => {
describe('#getOneBase', () => {
it('should return model', (done) => {
return request(server)
.get('/test4/1')
Expand All @@ -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', () => {
Expand Down

0 comments on commit 2864081

Please sign in to comment.