Skip to content

Commit a516a62

Browse files
committed
feat(typeorm-crud): added class transform options for plainToClass
1 parent 7f3628f commit a516a62

File tree

6 files changed

+85
-33
lines changed

6 files changed

+85
-33
lines changed

packages/crud-request/src/interfaces/parsed-request.interface.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { ObjectLiteral } from '@nestjsx/util';
2+
import { ClassTransformOptions } from 'class-transformer';
23
import { QueryFields, QueryFilter, QueryJoin, QuerySort, SCondition } from '../types';
34

45
export interface ParsedRequestParams {
56
fields: QueryFields;
67
paramsFilter: QueryFilter[];
78
authPersist: ObjectLiteral;
9+
classTransformOptions: ClassTransformOptions;
810
search: SCondition;
911
filter: QueryFilter[];
1012
or: QueryFilter[];

packages/crud-request/src/request-query.parser.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
isNil,
1212
ObjectLiteral,
1313
} from '@nestjsx/util';
14+
import { ClassTransformOptions } from 'class-transformer';
1415

1516
import { RequestQueryException } from './exceptions';
1617
import { ParamsOptions, ParsedRequestParams, RequestQueryBuilderOptions } from './interfaces';
@@ -42,6 +43,8 @@ export class RequestQueryParser implements ParsedRequestParams {
4243

4344
public authPersist: ObjectLiteral = undefined;
4445

46+
public classTransformOptions: ClassTransformOptions = undefined;
47+
4548
public search: SCondition;
4649

4750
public filter: QueryFilter[] = [];
@@ -83,6 +86,7 @@ export class RequestQueryParser implements ParsedRequestParams {
8386
fields: this.fields,
8487
paramsFilter: this.paramsFilter,
8588
authPersist: this.authPersist,
89+
classTransformOptions: this.classTransformOptions,
8690
search: this.search,
8791
filter: this.filter,
8892
or: this.or,
@@ -144,6 +148,10 @@ export class RequestQueryParser implements ParsedRequestParams {
144148
this.authPersist = persist || /* istanbul ignore next */ {};
145149
}
146150

151+
setClassTransformOptions(options: ClassTransformOptions = {}) {
152+
this.classTransformOptions = options || /* istanbul ignore next */ {};
153+
}
154+
147155
convertFilterToSearch(filter: QueryFilter): SFields | SConditionAND {
148156
const isEmptyValue = {
149157
isnull: true,

packages/crud-request/test/request-query.parser.spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,13 +457,27 @@ describe('#request-query', () => {
457457
});
458458
});
459459

460+
describe('#setClassTransformOptions', () => {
461+
it('it should set classTransformOptions, 1', () => {
462+
qp.setClassTransformOptions();
463+
expect(qp.classTransformOptions).toMatchObject({});
464+
});
465+
it('it should set classTransformOptions, 2', () => {
466+
const testOptions = { groups: ['TEST'] };
467+
qp.setClassTransformOptions(testOptions);
468+
const parsed = qp.getParsed();
469+
expect(parsed.classTransformOptions).toMatchObject(testOptions);
470+
});
471+
});
472+
460473
describe('#getParsed', () => {
461474
it('should return parsed params', () => {
462475
const expected: ParsedRequestParams = {
463476
fields: [],
464477
paramsFilter: [],
465478
search: undefined,
466479
authPersist: undefined,
480+
classTransformOptions: undefined,
467481
filter: [],
468482
or: [],
469483
join: [],

packages/crud-typeorm/src/typeorm-crud.service.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,9 @@ export class TypeOrmCrudService<T> extends CrudService<T> {
169169
const toSave = !allowParamsOverride
170170
? { ...found, ...dto, ...paramsFilters, ...req.parsed.authPersist }
171171
: { ...found, ...dto, ...req.parsed.authPersist };
172-
const updated = await this.repo.save(plainToClass(this.entityType, toSave) as unknown as DeepPartial<T>);
172+
const updated = await this.repo.save(
173+
plainToClass(this.entityType, toSave, req.parsed.classTransformOptions) as unknown as DeepPartial<T>,
174+
);
173175

174176
if (returnShallow) {
175177
return updated;
@@ -209,7 +211,9 @@ export class TypeOrmCrudService<T> extends CrudService<T> {
209211
...dto,
210212
...req.parsed.authPersist,
211213
};
212-
const replaced = await this.repo.save(plainToClass(this.entityType, toSave) as unknown as DeepPartial<T>);
214+
const replaced = await this.repo.save(
215+
plainToClass(this.entityType, toSave, req.parsed.classTransformOptions) as unknown as DeepPartial<T>,
216+
);
213217

214218
if (returnShallow) {
215219
return replaced;
@@ -233,7 +237,9 @@ export class TypeOrmCrudService<T> extends CrudService<T> {
233237
public async deleteOne(req: CrudRequest): Promise<void | T> {
234238
const { returnDeleted } = req.options.routes.deleteOneBase;
235239
const found = await this.getOneOrFail(req, returnDeleted);
236-
const toReturn = returnDeleted ? plainToClass(this.entityType, { ...found }) : undefined;
240+
const toReturn = returnDeleted
241+
? plainToClass(this.entityType, { ...found }, req.parsed.classTransformOptions)
242+
: undefined;
237243
const deleted =
238244
req.options.query.softDelete === true
239245
? await this.repo.softRemove(found as unknown as DeepPartial<T>)
@@ -421,7 +427,7 @@ export class TypeOrmCrudService<T> extends CrudService<T> {
421427

422428
return dto instanceof this.entityType
423429
? Object.assign(dto, parsed.authPersist)
424-
: plainToClass(this.entityType, { ...dto, ...parsed.authPersist });
430+
: plainToClass(this.entityType, { ...dto, ...parsed.authPersist }, parsed.classTransformOptions);
425431
}
426432

427433
protected getAllowedColumns(columns: string[], options: QueryOptions): string[] {

packages/crud/src/interceptors/crud-request.interceptor.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { BadRequestException, CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
22
import { RequestQueryException, RequestQueryParser, SCondition, QueryFilter } from '@nestjsx/crud-request';
33
import { isNil, isFunction, isArrayFull, hasLength } from '@nestjsx/util';
4+
import { ClassTransformOptions } from 'class-transformer';
45

56
import { PARSED_CRUD_REQUEST_KEY } from '../constants';
67
import { CrudActions } from '../enums';
@@ -142,6 +143,16 @@ export class CrudRequestInterceptor extends CrudBaseInterceptor implements NestI
142143
if (isFunction(crudOptions.auth.persist)) {
143144
parser.setAuthPersist(crudOptions.auth.persist(userOrRequest));
144145
}
146+
147+
const options: ClassTransformOptions = {};
148+
if (isFunction(crudOptions.auth.classTransformOptions)) {
149+
Object.assign(options, crudOptions.auth.classTransformOptions(userOrRequest));
150+
}
151+
152+
if (isFunction(crudOptions.auth.groups)) {
153+
options.groups = crudOptions.auth.groups(userOrRequest);
154+
}
155+
parser.setClassTransformOptions(options);
145156
}
146157

147158
return auth;

packages/crud/test/crud-request.interceptor.spec.ts

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,4 @@
1-
import {
2-
Controller,
3-
Get,
4-
Param,
5-
ParseIntPipe,
6-
Query,
7-
UseInterceptors,
8-
} from '@nestjs/common';
1+
import { Controller, Get, Param, ParseIntPipe, Query, UseInterceptors } from '@nestjs/common';
92
import { NestApplication } from '@nestjs/core';
103
import { Test } from '@nestjs/testing';
114
import { RequestQueryBuilder } from '@nestjsx/crud-request';
@@ -61,10 +54,7 @@ describe('#crud', () => {
6154

6255
@UseInterceptors(CrudRequestInterceptor)
6356
@Get('other2/:id/twoParams/:someParam')
64-
async twoParams(
65-
@ParsedRequest() req: CrudRequest,
66-
@Param('someParam', ParseIntPipe) p: number,
67-
) {
57+
async twoParams(@ParsedRequest() req: CrudRequest, @Param('someParam', ParseIntPipe) p: number) {
6858
return { filter: req.parsed.paramsFilter };
6959
}
7060
}
@@ -123,6 +113,23 @@ describe('#crud', () => {
123113
constructor(public service: TestService<TestModel>) {}
124114
}
125115

116+
@Crud({
117+
model: { type: TestModel },
118+
})
119+
@CrudAuth({
120+
groups: () => ['TEST_2'],
121+
classTransformOptions: () => ({ groups: ['TEST_1'] }),
122+
})
123+
@Controller('test6')
124+
class Test6Controller {
125+
constructor(public service: TestService<TestModel>) {}
126+
127+
@Override('getManyBase')
128+
get(@ParsedRequest() req: CrudRequest) {
129+
return req;
130+
}
131+
}
132+
126133
let $: supertest.SuperTest<supertest.Test>;
127134
let app: NestApplication;
128135

@@ -135,6 +142,7 @@ describe('#crud', () => {
135142
Test3Controller,
136143
Test4Controller,
137144
Test5Controller,
145+
Test6Controller,
138146
],
139147
}).compile();
140148
app = module.createNestApplication();
@@ -158,8 +166,15 @@ describe('#crud', () => {
158166
const page = 2;
159167
const limit = 10;
160168
const fields = ['a', 'b', 'c'];
161-
const sorts: any[][] = [['a', 'ASC'], ['b', 'DESC']];
162-
const filters: any[][] = [['a', 'eq', 1], ['c', 'in', [1, 2, 3]], ['d', 'notnull']];
169+
const sorts: any[][] = [
170+
['a', 'ASC'],
171+
['b', 'DESC'],
172+
];
173+
const filters: any[][] = [
174+
['a', 'eq', 1],
175+
['c', 'in', [1, 2, 3]],
176+
['d', 'notnull'],
177+
];
163178

164179
qb.setPage(page).setLimit(limit);
165180
qb.select(fields);
@@ -170,9 +185,7 @@ describe('#crud', () => {
170185
qb.setFilter({ field: f[0], operator: f[1], value: f[2] });
171186
}
172187

173-
const res = await $.get('/test/query')
174-
.query(qb.query())
175-
.expect(200);
188+
const res = await $.get('/test/query').query(qb.query()).expect(200);
176189
expect(res.body.parsed).toHaveProperty('page', page);
177190
expect(res.body.parsed).toHaveProperty('limit', limit);
178191
expect(res.body.parsed).toHaveProperty('fields', fields);
@@ -190,9 +203,7 @@ describe('#crud', () => {
190203
});
191204

192205
it('should others working', async () => {
193-
const res = await $.get('/test/other')
194-
.query({ page: 2, per_page: 11 })
195-
.expect(200);
206+
const res = await $.get('/test/other').query({ page: 2, per_page: 11 }).expect(200);
196207
expect(res.body.page).toBe(2);
197208
});
198209

@@ -225,9 +236,7 @@ describe('#crud', () => {
225236
});
226237

227238
it('should handle authorized request, 1', async () => {
228-
const res = await $.post('/test3')
229-
.send({})
230-
.expect(201);
239+
const res = await $.post('/test3').send({}).expect(201);
231240
const authPersist = { bar: false };
232241
const { parsed } = res.body;
233242
expect(parsed.authPersist).toMatchObject(authPersist);
@@ -241,19 +250,21 @@ describe('#crud', () => {
241250

242251
it('should handle authorized request, 3', async () => {
243252
const query = qb.search({ name: 'test' }).query();
244-
const res = await $.get('/test4')
245-
.query(query)
246-
.expect(200);
253+
const res = await $.get('/test4').query(query).expect(200);
247254
const search = { $or: [{ id: 1 }, { $and: [{}, { name: 'test' }] }] };
248255
expect(res.body.parsed.search).toMatchObject(search);
249256
});
250257
it('should handle authorized request, 4', async () => {
251258
const query = qb.search({ name: 'test' }).query();
252-
const res = await $.get('/test3')
253-
.query(query)
254-
.expect(200);
259+
const res = await $.get('/test3').query(query).expect(200);
255260
const search = { $and: [{ user: 'test', buz: 1 }, { name: 'persist' }] };
256261
expect(res.body.parsed.search).toMatchObject(search);
257262
});
263+
264+
it('should handle classTransformOptions, 1', async () => {
265+
const res = await $.get('/test6').expect(200);
266+
const groups = ['TEST_2'];
267+
expect(res.body.parsed.classTransformOptions.groups).toMatchObject(groups);
268+
});
258269
});
259270
});

0 commit comments

Comments
 (0)