Skip to content

Commit 18f68d9

Browse files
Merge branch 'thg303-feature/add-FileFieldsInterceptor'
2 parents 292fe21 + ecd61e3 commit 18f68d9

9 files changed

Lines changed: 142 additions & 10 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import * as multer from 'multer';
2+
import { Observable } from 'rxjs';
3+
import { MulterOptions } from '../interfaces/external/multer-options.interface';
4+
import { ExecutionContext } from '../interfaces';
5+
export declare function FileFieldsInterceptor(uploadFields: multer.Field[], options?: MulterOptions): {
6+
new (): {
7+
readonly upload: any;
8+
intercept(context: ExecutionContext, call$: Observable<any>): Promise<Observable<any>>;
9+
};
10+
};
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"use strict";
2+
Object.defineProperty(exports, "__esModule", { value: true });
3+
const multer = require("multer");
4+
const multer_utils_1 = require("./multer/multer.utils");
5+
function FileFieldsInterceptor(uploadFields, options) {
6+
const Interceptor = class {
7+
constructor() {
8+
this.upload = multer(options);
9+
}
10+
async intercept(context, call$) {
11+
const ctx = context.switchToHttp();
12+
await new Promise((resolve, reject) => this.upload.fields(uploadFields)(ctx.getRequest(), ctx.getResponse(), err => {
13+
if (err) {
14+
const error = multer_utils_1.transformException(err);
15+
return reject(error);
16+
}
17+
resolve();
18+
}));
19+
return call$;
20+
}
21+
};
22+
return Interceptor;
23+
}
24+
exports.FileFieldsInterceptor = FileFieldsInterceptor;

packages/common/decorators/http/route-params.decorator.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import 'reflect-metadata';
22
import { ROUTE_ARGS_METADATA } from '../../constants';
33
import { RouteParamtypes } from '../../enums/route-paramtypes.enum';
44
import { PipeTransform } from '../../index';
5-
import { isNil, isString } from '../../utils/shared.utils';
65
import { Type } from '../../interfaces';
6+
import { isNil, isString } from '../../utils/shared.utils';
77

88
export type ParamData = object | string | number;
99
export interface RouteParamsMetadata {
@@ -71,7 +71,7 @@ export const Next: () => ParameterDecorator = createRouteParamDecorator(
7171
export const Session: () => ParameterDecorator = createRouteParamDecorator(
7272
RouteParamtypes.SESSION,
7373
);
74-
export const UploadedFile: () => ParameterDecorator = createRouteParamDecorator(
74+
export const UploadedFile: (fileKey?: string) => ParameterDecorator = createRouteParamDecorator(
7575
RouteParamtypes.FILE,
7676
);
7777
export const UploadedFiles: () => ParameterDecorator = createRouteParamDecorator(
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import * as multer from 'multer';
2+
import { Observable } from 'rxjs';
3+
import { ExecutionContext } from '../interfaces';
4+
import { MulterField, MulterOptions } from '../interfaces/external/multer-options.interface';
5+
import { NestInterceptor } from './../interfaces/features/nest-interceptor.interface';
6+
import { transformException } from './multer/multer.utils';
7+
8+
export function FileFieldsInterceptor(
9+
uploadFields: MulterField[],
10+
options?: MulterOptions,
11+
) {
12+
const Interceptor = class implements NestInterceptor {
13+
readonly upload = multer(options);
14+
15+
async intercept(
16+
context: ExecutionContext,
17+
call$: Observable<any>,
18+
): Promise<Observable<any>> {
19+
const ctx = context.switchToHttp();
20+
21+
await new Promise((resolve, reject) =>
22+
this.upload.fields(uploadFields)(
23+
ctx.getRequest(),
24+
ctx.getResponse(),
25+
err => {
26+
if (err) {
27+
const error = transformException(err);
28+
return reject(error);
29+
}
30+
resolve();
31+
},
32+
),
33+
);
34+
return call$;
35+
}
36+
};
37+
return Interceptor;
38+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
export * from './file-fields.interceptor';
12
export * from './file.interceptor';
23
export * from './files.interceptor';

packages/common/interceptors/multer/multer.utils.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
1-
import {
2-
InternalServerErrorException,
3-
HttpException,
4-
PayloadTooLargeException,
5-
BadRequestException,
6-
} from './../../exceptions';
1+
import { BadRequestException, HttpException, PayloadTooLargeException } from './../../exceptions';
72
import { multerExceptions } from './multer.constants';
83

94
export function transformException(error: Error | undefined) {

packages/common/interfaces/external/multer-options.interface.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,10 @@ export interface MulterOptions {
5252
callback: (error: Error | null, acceptFile: boolean) => void,
5353
): void;
5454
}
55+
56+
export interface MulterField {
57+
/** The field name. */
58+
name: string;
59+
/** Optional maximum number of files per field to accept. */
60+
maxCount?: number;
61+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import * as sinon from 'sinon';
2+
import { expect } from 'chai';
3+
import { FileFieldsInterceptor } from './../../interceptors/file-fields.interceptor';
4+
import { Observable, of } from 'rxjs';
5+
import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context.host';
6+
7+
describe('FileFieldsInterceptor', () => {
8+
it('should return metatype with expected structure', async () => {
9+
const targetClass = FileFieldsInterceptor([{name: 'file', maxCount: 1}, {name: 'anotherFile', maxCount: 1}]);
10+
expect(targetClass.prototype.intercept).to.not.be.undefined;
11+
});
12+
describe('intercept', () => {
13+
let stream$;
14+
beforeEach(() => {
15+
stream$ = of('test');
16+
});
17+
it('should call object with expected params', async () => {
18+
const fieldName1 = 'file';
19+
const maxCount1 = 1;
20+
const fieldName2 = 'anotherFile';
21+
const maxCount2 = 2;
22+
const argument = [
23+
{name: fieldName1, maxCount: maxCount1},
24+
{name: fieldName2, maxCount: maxCount2}
25+
]
26+
const target = new (FileFieldsInterceptor(argument))();
27+
28+
const callback = (req, res, next) => next();
29+
const fieldsSpy = sinon
30+
.stub((target as any).upload, 'fields')
31+
.returns(callback);
32+
33+
await target.intercept(new ExecutionContextHost([]), stream$);
34+
35+
expect(fieldsSpy.called).to.be.true;
36+
expect(fieldsSpy.calledWith(argument)).to.be.true;
37+
});
38+
it('should transform exception', async () => {
39+
const fieldName1 = 'file';
40+
const maxCount1 = 1;
41+
const fieldName2 = 'anotherFile';
42+
const maxCount2 = 2;
43+
const argument = [
44+
{name: fieldName1, maxCount: maxCount1},
45+
{name: fieldName2, maxCount: maxCount2}
46+
]
47+
const target = new (FileFieldsInterceptor(argument));
48+
const err = {};
49+
const callback = (req, res, next) => next(err);
50+
51+
(target as any).fields = {
52+
array: () => callback,
53+
};
54+
expect(target.intercept(new ExecutionContextHost([]), stream$)).to.eventually.throw();
55+
});
56+
});
57+
});

packages/core/router/route-params-factory.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { IRouteParamsFactory } from './interfaces/route-params-factory.interface
44
export class RouteParamsFactory implements IRouteParamsFactory {
55
public exchangeKeyForValue(
66
key: RouteParamtypes | string,
7-
data,
7+
data: string | object | any,
88
{ req, res, next },
99
) {
1010
switch (key) {
@@ -25,7 +25,7 @@ export class RouteParamsFactory implements IRouteParamsFactory {
2525
case RouteParamtypes.SESSION:
2626
return req.session;
2727
case RouteParamtypes.FILE:
28-
return req.file;
28+
return req[data || 'file'];
2929
case RouteParamtypes.FILES:
3030
return req.files;
3131
default:

0 commit comments

Comments
 (0)