@globalart/nestjs-fastify 1.0.12 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +144 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +27 -0
- package/dist/index.d.mts +27 -0
- package/dist/index.mjs +139 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +45 -32
- package/dist/decorators/index.d.ts +0 -3
- package/dist/decorators/index.d.ts.map +0 -1
- package/dist/decorators/index.js +0 -21
- package/dist/decorators/index.js.map +0 -1
- package/dist/index.d.ts +0 -6
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -22
- package/dist/index.js.map +0 -1
- package/dist/interceptors/index.d.ts +0 -4
- package/dist/interceptors/index.d.ts.map +0 -1
- package/dist/interceptors/index.js +0 -33
- package/dist/interceptors/index.js.map +0 -1
- package/dist/model/index.d.ts +0 -6
- package/dist/model/index.d.ts.map +0 -1
- package/dist/model/index.js +0 -11
- package/dist/model/index.js.map +0 -1
- package/dist/types/index.d.ts +0 -3
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -3
- package/dist/types/index.js.map +0 -1
- package/dist/utils/index.d.ts +0 -5
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -29
- package/dist/utils/index.js.map +0 -1
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
let _nestjs_common = require("@nestjs/common");
|
|
2
|
+
let _nestjs_swagger = require("@nestjs/swagger");
|
|
3
|
+
let _nestjs_common_constants = require("@nestjs/common/constants");
|
|
4
|
+
let _nestjs_common_enums_route_paramtypes_enum = require("@nestjs/common/enums/route-paramtypes.enum");
|
|
5
|
+
|
|
6
|
+
//#region src/decorators/index.ts
|
|
7
|
+
const API_PARAMETERS = "swagger/apiParameters";
|
|
8
|
+
const IGNORED_TYPES = new Set([
|
|
9
|
+
"Object",
|
|
10
|
+
"String",
|
|
11
|
+
"Number",
|
|
12
|
+
"Boolean",
|
|
13
|
+
"Array",
|
|
14
|
+
"Promise",
|
|
15
|
+
"FastifyRequest",
|
|
16
|
+
"IncomingMessage",
|
|
17
|
+
"FastifyReply",
|
|
18
|
+
"ServerResponse",
|
|
19
|
+
"LoggerService",
|
|
20
|
+
"MultiPartFile"
|
|
21
|
+
]);
|
|
22
|
+
function getBodyType(target, key, idx) {
|
|
23
|
+
const routeArgs = Reflect.getMetadata(_nestjs_common_constants.ROUTE_ARGS_METADATA, target.constructor, key) || {};
|
|
24
|
+
const types = Reflect.getMetadata("design:paramtypes", target, key) || [];
|
|
25
|
+
const bodyKey = Object.keys(routeArgs).find((k) => k.includes(`:${_nestjs_common_enums_route_paramtypes_enum.RouteParamtypes.BODY}`) || routeArgs[k].index === _nestjs_common_enums_route_paramtypes_enum.RouteParamtypes.BODY);
|
|
26
|
+
if (bodyKey) return types[routeArgs[bodyKey].index];
|
|
27
|
+
return types.find((t, i) => i !== idx && t && !IGNORED_TYPES.has(t.name));
|
|
28
|
+
}
|
|
29
|
+
function updateSwagger(target, key, idx, field, isArray) {
|
|
30
|
+
const method = target[key];
|
|
31
|
+
if (!method) return;
|
|
32
|
+
const params = Reflect.getMetadata(API_PARAMETERS, method) || [];
|
|
33
|
+
let bodyParam = params.find((p) => p.in === "body");
|
|
34
|
+
const fileProp = {
|
|
35
|
+
type: isArray ? "array" : "string",
|
|
36
|
+
format: "binary",
|
|
37
|
+
...isArray && { items: {
|
|
38
|
+
type: "string",
|
|
39
|
+
format: "binary"
|
|
40
|
+
} }
|
|
41
|
+
};
|
|
42
|
+
if (bodyParam?.schema) {
|
|
43
|
+
bodyParam.schema.properties = {
|
|
44
|
+
...bodyParam.schema.properties,
|
|
45
|
+
[field]: fileProp
|
|
46
|
+
};
|
|
47
|
+
Reflect.defineMetadata(API_PARAMETERS, params, method);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
let BodyClass = bodyParam?.type || getBodyType(target, key, idx);
|
|
51
|
+
if (!BodyClass?.name?.endsWith("_CombinedDto")) {
|
|
52
|
+
const Parent = BodyClass || class {};
|
|
53
|
+
BodyClass = class extends Parent {};
|
|
54
|
+
Object.defineProperty(BodyClass, "name", { value: `${target.constructor.name}_${key}_CombinedDto` });
|
|
55
|
+
const newParam = {
|
|
56
|
+
in: "body",
|
|
57
|
+
type: BodyClass,
|
|
58
|
+
required: true
|
|
59
|
+
};
|
|
60
|
+
const pIdx = params.findIndex((p) => p.in === "body");
|
|
61
|
+
if (pIdx > -1) params[pIdx] = newParam;
|
|
62
|
+
else params.push(newParam);
|
|
63
|
+
Reflect.defineMetadata(API_PARAMETERS, params, method);
|
|
64
|
+
}
|
|
65
|
+
(0, _nestjs_swagger.ApiProperty)(fileProp)(BodyClass.prototype, field);
|
|
66
|
+
}
|
|
67
|
+
const createUploader = (isArray, extract) => (field = isArray ? "files" : "file") => (target, key, idx) => {
|
|
68
|
+
updateSwagger(target, key, idx, field, isArray);
|
|
69
|
+
(0, _nestjs_common.createParamDecorator)((data, ctx) => extract(ctx.switchToHttp().getRequest(), data))(field)(target, key, idx);
|
|
70
|
+
};
|
|
71
|
+
const UploadedFiles = createUploader(true, (req) => {
|
|
72
|
+
const files = [];
|
|
73
|
+
if (req.storedFiles) Object.values(req.storedFiles).forEach((f) => files.push(...f));
|
|
74
|
+
return files;
|
|
75
|
+
});
|
|
76
|
+
const UploadedFile = createUploader(false, (req, field) => req.storedFiles?.[field]?.[0] || null);
|
|
77
|
+
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region src/utils/index.ts
|
|
80
|
+
const getFileFromPart = async (part) => {
|
|
81
|
+
const buffer = await part.toBuffer();
|
|
82
|
+
return {
|
|
83
|
+
buffer,
|
|
84
|
+
size: buffer.byteLength,
|
|
85
|
+
filename: part.filename,
|
|
86
|
+
mimetype: part.mimetype,
|
|
87
|
+
fieldname: part.fieldname
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
const validateFile = (file, options) => {
|
|
91
|
+
const validators = [];
|
|
92
|
+
if (options.maxFileSize) validators.push(new _nestjs_common.MaxFileSizeValidator({ maxSize: options.maxFileSize }));
|
|
93
|
+
if (options.fileType) validators.push(new _nestjs_common.FileTypeValidator({ fileType: options.fileType }));
|
|
94
|
+
for (const validator of validators) {
|
|
95
|
+
if (validator.isValid(file)) continue;
|
|
96
|
+
return validator.buildErrorMessage(file);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region src/interceptors/index.ts
|
|
102
|
+
function MultipartInterceptor(options = {}) {
|
|
103
|
+
class MixinInterceptor {
|
|
104
|
+
async intercept(context, next) {
|
|
105
|
+
const req = context.switchToHttp().getRequest();
|
|
106
|
+
if (!req.isMultipart()) throw new _nestjs_common.HttpException("The request should be a form-data", _nestjs_common.HttpStatus.BAD_REQUEST);
|
|
107
|
+
const files = {};
|
|
108
|
+
const body = {};
|
|
109
|
+
for await (const part of req.parts()) {
|
|
110
|
+
if (part.type !== "file") {
|
|
111
|
+
body[part.fieldname] = part.value;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const file = await getFileFromPart(part);
|
|
115
|
+
const validationResult = validateFile(file, options);
|
|
116
|
+
if (validationResult) throw new _nestjs_common.HttpException(validationResult, _nestjs_common.HttpStatus.UNPROCESSABLE_ENTITY);
|
|
117
|
+
files[part.fieldname] = files[part.fieldname] || [];
|
|
118
|
+
files[part.fieldname].push(file);
|
|
119
|
+
}
|
|
120
|
+
req.storedFiles = files;
|
|
121
|
+
req.body = body;
|
|
122
|
+
return next.handle();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return (0, _nestjs_common.mixin)(MixinInterceptor);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/model/index.ts
|
|
130
|
+
var MultipartOptions = class {
|
|
131
|
+
constructor(maxFileSize, fileType) {
|
|
132
|
+
this.maxFileSize = maxFileSize;
|
|
133
|
+
this.fileType = fileType;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
//#endregion
|
|
138
|
+
exports.MultipartInterceptor = MultipartInterceptor;
|
|
139
|
+
exports.MultipartOptions = MultipartOptions;
|
|
140
|
+
exports.UploadedFile = UploadedFile;
|
|
141
|
+
exports.UploadedFiles = UploadedFiles;
|
|
142
|
+
exports.getFileFromPart = getFileFromPart;
|
|
143
|
+
exports.validateFile = validateFile;
|
|
144
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["ROUTE_ARGS_METADATA","RouteParamtypes","fileProp: ApiPropertyOptions","files: any[]","validators: FileValidator[]","MaxFileSizeValidator","FileTypeValidator","HttpException","HttpStatus","files: Record<string, any[]>","body: Record<string, any>","maxFileSize?: number","fileType?: string | RegExp"],"sources":["../src/decorators/index.ts","../src/utils/index.ts","../src/interceptors/index.ts","../src/model/index.ts"],"sourcesContent":["import { createParamDecorator, ExecutionContext } from \"@nestjs/common\";\nimport * as fastify from \"fastify\";\nimport { ApiProperty, ApiPropertyOptions } from \"@nestjs/swagger\";\nimport { ROUTE_ARGS_METADATA } from \"@nestjs/common/constants\";\nimport { RouteParamtypes } from \"@nestjs/common/enums/route-paramtypes.enum\";\n\nconst API_PARAMETERS = \"swagger/apiParameters\";\nconst IGNORED_TYPES = new Set([\n \"Object\", \"String\", \"Number\", \"Boolean\", \"Array\", \"Promise\",\n \"FastifyRequest\", \"IncomingMessage\", \"FastifyReply\", \"ServerResponse\", \"LoggerService\", \"MultiPartFile\"\n]);\n\nfunction getBodyType(target: any, key: string, idx: number): any {\n const routeArgs = Reflect.getMetadata(ROUTE_ARGS_METADATA, target.constructor, key) || {};\n const types = Reflect.getMetadata(\"design:paramtypes\", target, key) || [];\n\n const bodyKey = Object.keys(routeArgs).find(k =>\n k.includes(`:${RouteParamtypes.BODY}`) || routeArgs[k].index === RouteParamtypes.BODY\n );\n if (bodyKey) return types[routeArgs[bodyKey].index];\n\n return types.find((t: any, i: number) => i !== idx && t && !IGNORED_TYPES.has(t.name));\n}\n\nfunction updateSwagger(target: any, key: string, idx: number, field: string, isArray: boolean) {\n const method = target[key];\n if (!method) return;\n\n const params = Reflect.getMetadata(API_PARAMETERS, method) || [];\n let bodyParam = params.find((p: any) => p.in === \"body\");\n const fileProp: ApiPropertyOptions = {\n type: isArray ? \"array\" : \"string\",\n format: \"binary\",\n ...(isArray && { items: { type: \"string\", format: \"binary\" } })\n };\n\n if (bodyParam?.schema) {\n bodyParam.schema.properties = { ...bodyParam.schema.properties, [field]: fileProp };\n Reflect.defineMetadata(API_PARAMETERS, params, method);\n return;\n }\n\n let BodyClass = bodyParam?.type || getBodyType(target, key, idx);\n if (!BodyClass?.name?.endsWith(\"_CombinedDto\")) {\n const Parent = BodyClass || class { };\n BodyClass = class extends Parent { };\n Object.defineProperty(BodyClass, \"name\", { value: `${target.constructor.name}_${key}_CombinedDto` });\n\n const newParam = { in: \"body\", type: BodyClass, required: true };\n const pIdx = params.findIndex((p: any) => p.in === \"body\");\n if (pIdx > -1) params[pIdx] = newParam; else params.push(newParam);\n Reflect.defineMetadata(API_PARAMETERS, params, method);\n }\n\n ApiProperty(fileProp)(BodyClass.prototype, field);\n}\n\nconst createUploader = (isArray: boolean, extract: (req: fastify.FastifyRequest, f: string) => any) =>\n (field: string = isArray ? \"files\" : \"file\") =>\n (target: any, key: string, idx: number) => {\n updateSwagger(target, key, idx, field, isArray);\n createParamDecorator((data: string, ctx: ExecutionContext) =>\n extract(ctx.switchToHttp().getRequest(), data)\n )(field)(target, key, idx);\n };\n\nexport const UploadedFiles = createUploader(true, (req) => {\n const files: any[] = [];\n if (req.storedFiles) Object.values(req.storedFiles).forEach(f => files.push(...f));\n return files;\n});\n\nexport const UploadedFile = createUploader(false, (req, field) => req.storedFiles?.[field]?.[0] || null);\n","import { MultipartFile } from \"@fastify/multipart\";\nimport {\n FileTypeValidator,\n FileValidator,\n MaxFileSizeValidator,\n} from \"@nestjs/common\";\nimport { MultipartOptions } from \"../model\";\n\nexport const getFileFromPart = async (\n part: MultipartFile,\n): Promise<Storage.MultipartFile> => {\n const buffer = await part.toBuffer();\n return {\n buffer,\n size: buffer.byteLength,\n filename: part.filename,\n mimetype: part.mimetype,\n fieldname: part.fieldname,\n };\n};\n\nexport const validateFile = (\n file: Storage.MultipartFile,\n options: MultipartOptions,\n): string | void => {\n const validators: FileValidator[] = [];\n\n if (options.maxFileSize)\n validators.push(new MaxFileSizeValidator({ maxSize: options.maxFileSize }));\n if (options.fileType)\n validators.push(new FileTypeValidator({ fileType: options.fileType }));\n\n for (const validator of validators) {\n if (validator.isValid(file)) continue;\n\n return validator.buildErrorMessage(file);\n }\n};\n","import { Observable } from \"rxjs\";\nimport {\n CallHandler,\n ExecutionContext,\n HttpException,\n HttpStatus,\n mixin,\n NestInterceptor,\n Type,\n} from \"@nestjs/common\";\nimport * as fastify from \"fastify\";\nimport { MultipartValue } from \"@fastify/multipart\";\nimport { MultipartOptions } from \"../model\";\nimport { getFileFromPart, validateFile } from \"../utils\";\n\nexport function MultipartInterceptor(\n options: MultipartOptions = {},\n): Type<NestInterceptor> {\n class MixinInterceptor implements NestInterceptor {\n async intercept(\n context: ExecutionContext,\n next: CallHandler,\n ): Promise<Observable<any>> {\n const req = context.switchToHttp().getRequest() as fastify.FastifyRequest;\n\n if (!req.isMultipart())\n throw new HttpException(\n \"The request should be a form-data\",\n HttpStatus.BAD_REQUEST,\n );\n\n const files: Record<string, any[]> = {};\n const body: Record<string, any> = {};\n for await (const part of req.parts()) {\n if (part.type !== \"file\") {\n body[part.fieldname] = (part as MultipartValue).value;\n continue;\n }\n const file = await getFileFromPart(part);\n const validationResult = validateFile(file, options);\n\n if (validationResult)\n throw new HttpException(\n validationResult,\n HttpStatus.UNPROCESSABLE_ENTITY,\n );\n\n files[part.fieldname] = files[part.fieldname] || [];\n files[part.fieldname].push(file);\n }\n req.storedFiles = files;\n req.body = body;\n\n return next.handle();\n }\n }\n\n return mixin(MixinInterceptor);\n}\n","export class MultipartOptions {\n constructor(\n public maxFileSize?: number,\n public fileType?: string | RegExp,\n ) {}\n}\n"],"mappings":"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"}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { NestInterceptor, Type } from "@nestjs/common";
|
|
2
|
+
import { MultipartFile } from "@fastify/multipart";
|
|
3
|
+
|
|
4
|
+
//#region src/decorators/index.d.ts
|
|
5
|
+
declare const UploadedFiles: (field?: string) => (target: any, key: string, idx: number) => void;
|
|
6
|
+
declare const UploadedFile: (field?: string) => (target: any, key: string, idx: number) => void;
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region src/types/index.d.ts
|
|
9
|
+
type MultiPartFile = Storage.MultipartFile;
|
|
10
|
+
type MultiPartFiles = Storage.MultipartFile[];
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/model/index.d.ts
|
|
13
|
+
declare class MultipartOptions {
|
|
14
|
+
maxFileSize?: number | undefined;
|
|
15
|
+
fileType?: (string | RegExp) | undefined;
|
|
16
|
+
constructor(maxFileSize?: number | undefined, fileType?: (string | RegExp) | undefined);
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/interceptors/index.d.ts
|
|
20
|
+
declare function MultipartInterceptor(options?: MultipartOptions): Type<NestInterceptor>;
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/utils/index.d.ts
|
|
23
|
+
declare const getFileFromPart: (part: MultipartFile) => Promise<Storage.MultipartFile>;
|
|
24
|
+
declare const validateFile: (file: Storage.MultipartFile, options: MultipartOptions) => string | void;
|
|
25
|
+
//#endregion
|
|
26
|
+
export { MultiPartFile, MultiPartFiles, MultipartInterceptor, MultipartOptions, UploadedFile, UploadedFiles, getFileFromPart, validateFile };
|
|
27
|
+
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { NestInterceptor, Type } from "@nestjs/common";
|
|
2
|
+
import { MultipartFile } from "@fastify/multipart";
|
|
3
|
+
|
|
4
|
+
//#region src/decorators/index.d.ts
|
|
5
|
+
declare const UploadedFiles: (field?: string) => (target: any, key: string, idx: number) => void;
|
|
6
|
+
declare const UploadedFile: (field?: string) => (target: any, key: string, idx: number) => void;
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region src/types/index.d.ts
|
|
9
|
+
type MultiPartFile = Storage.MultipartFile;
|
|
10
|
+
type MultiPartFiles = Storage.MultipartFile[];
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/model/index.d.ts
|
|
13
|
+
declare class MultipartOptions {
|
|
14
|
+
maxFileSize?: number | undefined;
|
|
15
|
+
fileType?: (string | RegExp) | undefined;
|
|
16
|
+
constructor(maxFileSize?: number | undefined, fileType?: (string | RegExp) | undefined);
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/interceptors/index.d.ts
|
|
20
|
+
declare function MultipartInterceptor(options?: MultipartOptions): Type<NestInterceptor>;
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/utils/index.d.ts
|
|
23
|
+
declare const getFileFromPart: (part: MultipartFile) => Promise<Storage.MultipartFile>;
|
|
24
|
+
declare const validateFile: (file: Storage.MultipartFile, options: MultipartOptions) => string | void;
|
|
25
|
+
//#endregion
|
|
26
|
+
export { MultiPartFile, MultiPartFiles, MultipartInterceptor, MultipartOptions, UploadedFile, UploadedFiles, getFileFromPart, validateFile };
|
|
27
|
+
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { FileTypeValidator, HttpException, HttpStatus, MaxFileSizeValidator, createParamDecorator, mixin } from "@nestjs/common";
|
|
2
|
+
import { ApiProperty } from "@nestjs/swagger";
|
|
3
|
+
import { ROUTE_ARGS_METADATA } from "@nestjs/common/constants";
|
|
4
|
+
import { RouteParamtypes } from "@nestjs/common/enums/route-paramtypes.enum";
|
|
5
|
+
|
|
6
|
+
//#region src/decorators/index.ts
|
|
7
|
+
const API_PARAMETERS = "swagger/apiParameters";
|
|
8
|
+
const IGNORED_TYPES = new Set([
|
|
9
|
+
"Object",
|
|
10
|
+
"String",
|
|
11
|
+
"Number",
|
|
12
|
+
"Boolean",
|
|
13
|
+
"Array",
|
|
14
|
+
"Promise",
|
|
15
|
+
"FastifyRequest",
|
|
16
|
+
"IncomingMessage",
|
|
17
|
+
"FastifyReply",
|
|
18
|
+
"ServerResponse",
|
|
19
|
+
"LoggerService",
|
|
20
|
+
"MultiPartFile"
|
|
21
|
+
]);
|
|
22
|
+
function getBodyType(target, key, idx) {
|
|
23
|
+
const routeArgs = Reflect.getMetadata(ROUTE_ARGS_METADATA, target.constructor, key) || {};
|
|
24
|
+
const types = Reflect.getMetadata("design:paramtypes", target, key) || [];
|
|
25
|
+
const bodyKey = Object.keys(routeArgs).find((k) => k.includes(`:${RouteParamtypes.BODY}`) || routeArgs[k].index === RouteParamtypes.BODY);
|
|
26
|
+
if (bodyKey) return types[routeArgs[bodyKey].index];
|
|
27
|
+
return types.find((t, i) => i !== idx && t && !IGNORED_TYPES.has(t.name));
|
|
28
|
+
}
|
|
29
|
+
function updateSwagger(target, key, idx, field, isArray) {
|
|
30
|
+
const method = target[key];
|
|
31
|
+
if (!method) return;
|
|
32
|
+
const params = Reflect.getMetadata(API_PARAMETERS, method) || [];
|
|
33
|
+
let bodyParam = params.find((p) => p.in === "body");
|
|
34
|
+
const fileProp = {
|
|
35
|
+
type: isArray ? "array" : "string",
|
|
36
|
+
format: "binary",
|
|
37
|
+
...isArray && { items: {
|
|
38
|
+
type: "string",
|
|
39
|
+
format: "binary"
|
|
40
|
+
} }
|
|
41
|
+
};
|
|
42
|
+
if (bodyParam?.schema) {
|
|
43
|
+
bodyParam.schema.properties = {
|
|
44
|
+
...bodyParam.schema.properties,
|
|
45
|
+
[field]: fileProp
|
|
46
|
+
};
|
|
47
|
+
Reflect.defineMetadata(API_PARAMETERS, params, method);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
let BodyClass = bodyParam?.type || getBodyType(target, key, idx);
|
|
51
|
+
if (!BodyClass?.name?.endsWith("_CombinedDto")) {
|
|
52
|
+
const Parent = BodyClass || class {};
|
|
53
|
+
BodyClass = class extends Parent {};
|
|
54
|
+
Object.defineProperty(BodyClass, "name", { value: `${target.constructor.name}_${key}_CombinedDto` });
|
|
55
|
+
const newParam = {
|
|
56
|
+
in: "body",
|
|
57
|
+
type: BodyClass,
|
|
58
|
+
required: true
|
|
59
|
+
};
|
|
60
|
+
const pIdx = params.findIndex((p) => p.in === "body");
|
|
61
|
+
if (pIdx > -1) params[pIdx] = newParam;
|
|
62
|
+
else params.push(newParam);
|
|
63
|
+
Reflect.defineMetadata(API_PARAMETERS, params, method);
|
|
64
|
+
}
|
|
65
|
+
ApiProperty(fileProp)(BodyClass.prototype, field);
|
|
66
|
+
}
|
|
67
|
+
const createUploader = (isArray, extract) => (field = isArray ? "files" : "file") => (target, key, idx) => {
|
|
68
|
+
updateSwagger(target, key, idx, field, isArray);
|
|
69
|
+
createParamDecorator((data, ctx) => extract(ctx.switchToHttp().getRequest(), data))(field)(target, key, idx);
|
|
70
|
+
};
|
|
71
|
+
const UploadedFiles = createUploader(true, (req) => {
|
|
72
|
+
const files = [];
|
|
73
|
+
if (req.storedFiles) Object.values(req.storedFiles).forEach((f) => files.push(...f));
|
|
74
|
+
return files;
|
|
75
|
+
});
|
|
76
|
+
const UploadedFile = createUploader(false, (req, field) => req.storedFiles?.[field]?.[0] || null);
|
|
77
|
+
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region src/utils/index.ts
|
|
80
|
+
const getFileFromPart = async (part) => {
|
|
81
|
+
const buffer = await part.toBuffer();
|
|
82
|
+
return {
|
|
83
|
+
buffer,
|
|
84
|
+
size: buffer.byteLength,
|
|
85
|
+
filename: part.filename,
|
|
86
|
+
mimetype: part.mimetype,
|
|
87
|
+
fieldname: part.fieldname
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
const validateFile = (file, options) => {
|
|
91
|
+
const validators = [];
|
|
92
|
+
if (options.maxFileSize) validators.push(new MaxFileSizeValidator({ maxSize: options.maxFileSize }));
|
|
93
|
+
if (options.fileType) validators.push(new FileTypeValidator({ fileType: options.fileType }));
|
|
94
|
+
for (const validator of validators) {
|
|
95
|
+
if (validator.isValid(file)) continue;
|
|
96
|
+
return validator.buildErrorMessage(file);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region src/interceptors/index.ts
|
|
102
|
+
function MultipartInterceptor(options = {}) {
|
|
103
|
+
class MixinInterceptor {
|
|
104
|
+
async intercept(context, next) {
|
|
105
|
+
const req = context.switchToHttp().getRequest();
|
|
106
|
+
if (!req.isMultipart()) throw new HttpException("The request should be a form-data", HttpStatus.BAD_REQUEST);
|
|
107
|
+
const files = {};
|
|
108
|
+
const body = {};
|
|
109
|
+
for await (const part of req.parts()) {
|
|
110
|
+
if (part.type !== "file") {
|
|
111
|
+
body[part.fieldname] = part.value;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const file = await getFileFromPart(part);
|
|
115
|
+
const validationResult = validateFile(file, options);
|
|
116
|
+
if (validationResult) throw new HttpException(validationResult, HttpStatus.UNPROCESSABLE_ENTITY);
|
|
117
|
+
files[part.fieldname] = files[part.fieldname] || [];
|
|
118
|
+
files[part.fieldname].push(file);
|
|
119
|
+
}
|
|
120
|
+
req.storedFiles = files;
|
|
121
|
+
req.body = body;
|
|
122
|
+
return next.handle();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return mixin(MixinInterceptor);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/model/index.ts
|
|
130
|
+
var MultipartOptions = class {
|
|
131
|
+
constructor(maxFileSize, fileType) {
|
|
132
|
+
this.maxFileSize = maxFileSize;
|
|
133
|
+
this.fileType = fileType;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
//#endregion
|
|
138
|
+
export { MultipartInterceptor, MultipartOptions, UploadedFile, UploadedFiles, getFileFromPart, validateFile };
|
|
139
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["fileProp: ApiPropertyOptions","files: any[]","validators: FileValidator[]","files: Record<string, any[]>","body: Record<string, any>","maxFileSize?: number","fileType?: string | RegExp"],"sources":["../src/decorators/index.ts","../src/utils/index.ts","../src/interceptors/index.ts","../src/model/index.ts"],"sourcesContent":["import { createParamDecorator, ExecutionContext } from \"@nestjs/common\";\nimport * as fastify from \"fastify\";\nimport { ApiProperty, ApiPropertyOptions } from \"@nestjs/swagger\";\nimport { ROUTE_ARGS_METADATA } from \"@nestjs/common/constants\";\nimport { RouteParamtypes } from \"@nestjs/common/enums/route-paramtypes.enum\";\n\nconst API_PARAMETERS = \"swagger/apiParameters\";\nconst IGNORED_TYPES = new Set([\n \"Object\", \"String\", \"Number\", \"Boolean\", \"Array\", \"Promise\",\n \"FastifyRequest\", \"IncomingMessage\", \"FastifyReply\", \"ServerResponse\", \"LoggerService\", \"MultiPartFile\"\n]);\n\nfunction getBodyType(target: any, key: string, idx: number): any {\n const routeArgs = Reflect.getMetadata(ROUTE_ARGS_METADATA, target.constructor, key) || {};\n const types = Reflect.getMetadata(\"design:paramtypes\", target, key) || [];\n\n const bodyKey = Object.keys(routeArgs).find(k =>\n k.includes(`:${RouteParamtypes.BODY}`) || routeArgs[k].index === RouteParamtypes.BODY\n );\n if (bodyKey) return types[routeArgs[bodyKey].index];\n\n return types.find((t: any, i: number) => i !== idx && t && !IGNORED_TYPES.has(t.name));\n}\n\nfunction updateSwagger(target: any, key: string, idx: number, field: string, isArray: boolean) {\n const method = target[key];\n if (!method) return;\n\n const params = Reflect.getMetadata(API_PARAMETERS, method) || [];\n let bodyParam = params.find((p: any) => p.in === \"body\");\n const fileProp: ApiPropertyOptions = {\n type: isArray ? \"array\" : \"string\",\n format: \"binary\",\n ...(isArray && { items: { type: \"string\", format: \"binary\" } })\n };\n\n if (bodyParam?.schema) {\n bodyParam.schema.properties = { ...bodyParam.schema.properties, [field]: fileProp };\n Reflect.defineMetadata(API_PARAMETERS, params, method);\n return;\n }\n\n let BodyClass = bodyParam?.type || getBodyType(target, key, idx);\n if (!BodyClass?.name?.endsWith(\"_CombinedDto\")) {\n const Parent = BodyClass || class { };\n BodyClass = class extends Parent { };\n Object.defineProperty(BodyClass, \"name\", { value: `${target.constructor.name}_${key}_CombinedDto` });\n\n const newParam = { in: \"body\", type: BodyClass, required: true };\n const pIdx = params.findIndex((p: any) => p.in === \"body\");\n if (pIdx > -1) params[pIdx] = newParam; else params.push(newParam);\n Reflect.defineMetadata(API_PARAMETERS, params, method);\n }\n\n ApiProperty(fileProp)(BodyClass.prototype, field);\n}\n\nconst createUploader = (isArray: boolean, extract: (req: fastify.FastifyRequest, f: string) => any) =>\n (field: string = isArray ? \"files\" : \"file\") =>\n (target: any, key: string, idx: number) => {\n updateSwagger(target, key, idx, field, isArray);\n createParamDecorator((data: string, ctx: ExecutionContext) =>\n extract(ctx.switchToHttp().getRequest(), data)\n )(field)(target, key, idx);\n };\n\nexport const UploadedFiles = createUploader(true, (req) => {\n const files: any[] = [];\n if (req.storedFiles) Object.values(req.storedFiles).forEach(f => files.push(...f));\n return files;\n});\n\nexport const UploadedFile = createUploader(false, (req, field) => req.storedFiles?.[field]?.[0] || null);\n","import { MultipartFile } from \"@fastify/multipart\";\nimport {\n FileTypeValidator,\n FileValidator,\n MaxFileSizeValidator,\n} from \"@nestjs/common\";\nimport { MultipartOptions } from \"../model\";\n\nexport const getFileFromPart = async (\n part: MultipartFile,\n): Promise<Storage.MultipartFile> => {\n const buffer = await part.toBuffer();\n return {\n buffer,\n size: buffer.byteLength,\n filename: part.filename,\n mimetype: part.mimetype,\n fieldname: part.fieldname,\n };\n};\n\nexport const validateFile = (\n file: Storage.MultipartFile,\n options: MultipartOptions,\n): string | void => {\n const validators: FileValidator[] = [];\n\n if (options.maxFileSize)\n validators.push(new MaxFileSizeValidator({ maxSize: options.maxFileSize }));\n if (options.fileType)\n validators.push(new FileTypeValidator({ fileType: options.fileType }));\n\n for (const validator of validators) {\n if (validator.isValid(file)) continue;\n\n return validator.buildErrorMessage(file);\n }\n};\n","import { Observable } from \"rxjs\";\nimport {\n CallHandler,\n ExecutionContext,\n HttpException,\n HttpStatus,\n mixin,\n NestInterceptor,\n Type,\n} from \"@nestjs/common\";\nimport * as fastify from \"fastify\";\nimport { MultipartValue } from \"@fastify/multipart\";\nimport { MultipartOptions } from \"../model\";\nimport { getFileFromPart, validateFile } from \"../utils\";\n\nexport function MultipartInterceptor(\n options: MultipartOptions = {},\n): Type<NestInterceptor> {\n class MixinInterceptor implements NestInterceptor {\n async intercept(\n context: ExecutionContext,\n next: CallHandler,\n ): Promise<Observable<any>> {\n const req = context.switchToHttp().getRequest() as fastify.FastifyRequest;\n\n if (!req.isMultipart())\n throw new HttpException(\n \"The request should be a form-data\",\n HttpStatus.BAD_REQUEST,\n );\n\n const files: Record<string, any[]> = {};\n const body: Record<string, any> = {};\n for await (const part of req.parts()) {\n if (part.type !== \"file\") {\n body[part.fieldname] = (part as MultipartValue).value;\n continue;\n }\n const file = await getFileFromPart(part);\n const validationResult = validateFile(file, options);\n\n if (validationResult)\n throw new HttpException(\n validationResult,\n HttpStatus.UNPROCESSABLE_ENTITY,\n );\n\n files[part.fieldname] = files[part.fieldname] || [];\n files[part.fieldname].push(file);\n }\n req.storedFiles = files;\n req.body = body;\n\n return next.handle();\n }\n }\n\n return mixin(MixinInterceptor);\n}\n","export class MultipartOptions {\n constructor(\n public maxFileSize?: number,\n public fileType?: string | RegExp,\n ) {}\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,28 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@globalart/nestjs-fastify",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "A fastify for NestJS",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "GlobalArt, Inc"
|
|
7
7
|
},
|
|
8
8
|
"license": "MIT",
|
|
9
|
-
"
|
|
10
|
-
"types": "./dist/index.d.ts",
|
|
9
|
+
"type": "module",
|
|
11
10
|
"files": [
|
|
12
11
|
"dist",
|
|
13
12
|
"README.md",
|
|
14
13
|
"LICENSE"
|
|
15
14
|
],
|
|
15
|
+
"main": "./dist/index.cjs",
|
|
16
|
+
"module": "./dist/index.mjs",
|
|
17
|
+
"types": "./dist/index.d.cts",
|
|
16
18
|
"exports": {
|
|
17
19
|
".": {
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
"import": {
|
|
21
|
+
"types": "./dist/index.d.mts",
|
|
22
|
+
"default": "./dist/index.mjs"
|
|
23
|
+
},
|
|
24
|
+
"require": {
|
|
25
|
+
"types": "./dist/index.d.cts",
|
|
26
|
+
"default": "./dist/index.cjs"
|
|
27
|
+
}
|
|
21
28
|
}
|
|
22
29
|
},
|
|
23
30
|
"repository": {
|
|
24
31
|
"type": "git",
|
|
25
|
-
"url": "git+https://github.com/GlobalArtInc/
|
|
32
|
+
"url": "git+https://github.com/GlobalArtInc/ecosystem.git#main"
|
|
26
33
|
},
|
|
27
34
|
"keywords": [
|
|
28
35
|
"nestjs",
|
|
@@ -33,38 +40,44 @@
|
|
|
33
40
|
"test": "jest --runInBand --passWithNoTests",
|
|
34
41
|
"test:cov": "jest --coverage --passWithNoTests",
|
|
35
42
|
"coveralls": "yarn run test:cov --coverageReporters=text-lcov | coveralls",
|
|
36
|
-
"build": "rm -rf ./dist &&
|
|
37
|
-
"build:watch": "
|
|
43
|
+
"build": "rm -rf ./dist && tsdown && cp src/globals.d.ts dist/",
|
|
44
|
+
"build:watch": "tsdown --watch",
|
|
38
45
|
"prepublishOnly": "npm run build",
|
|
39
46
|
"publish:dev": "npm publish --access public --tag dev",
|
|
40
|
-
"publish:npm": "release-it"
|
|
47
|
+
"publish:npm": "release-it --config ../../.release-it.json"
|
|
41
48
|
},
|
|
42
49
|
"dependencies": {
|
|
43
|
-
"@fastify/cookie": "
|
|
44
|
-
"@fastify/multipart": "
|
|
45
|
-
"@nestjs/common": "
|
|
46
|
-
"@nestjs/core": "
|
|
47
|
-
"@nestjs/microservices": "
|
|
48
|
-
"@nestjs/platform-fastify": "
|
|
49
|
-
"@nestjs/swagger": "
|
|
50
|
-
"@nestjs/testing": "
|
|
51
|
-
"@nestjs/typeorm": "
|
|
52
|
-
"fastify": "
|
|
50
|
+
"@fastify/cookie": "catalog:prod",
|
|
51
|
+
"@fastify/multipart": "catalog:prod",
|
|
52
|
+
"@nestjs/common": "catalog:prod",
|
|
53
|
+
"@nestjs/core": "catalog:prod",
|
|
54
|
+
"@nestjs/microservices": "catalog:prod",
|
|
55
|
+
"@nestjs/platform-fastify": "catalog:prod",
|
|
56
|
+
"@nestjs/swagger": "catalog:prod",
|
|
57
|
+
"@nestjs/testing": "catalog:prod",
|
|
58
|
+
"@nestjs/typeorm": "catalog:prod",
|
|
59
|
+
"fastify": "catalog:prod"
|
|
53
60
|
},
|
|
54
61
|
"devDependencies": {
|
|
55
|
-
"@types/jest": "
|
|
56
|
-
"@types/node": "
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"ts-
|
|
65
|
-
"
|
|
62
|
+
"@types/jest": "catalog:dev",
|
|
63
|
+
"@types/node": "catalog:dev",
|
|
64
|
+
"class-validator": "catalog:dev",
|
|
65
|
+
"coveralls": "catalog:dev",
|
|
66
|
+
"jest": "catalog:dev",
|
|
67
|
+
"prettier": "catalog:dev",
|
|
68
|
+
"reflect-metadata": "catalog:dev",
|
|
69
|
+
"release-it": "catalog:dev",
|
|
70
|
+
"rxjs": "catalog:dev",
|
|
71
|
+
"ts-jest": "catalog:dev",
|
|
72
|
+
"ts-node": "catalog:dev",
|
|
73
|
+
"tsdown": "catalog:dev",
|
|
74
|
+
"typescript": "catalog:dev"
|
|
66
75
|
},
|
|
67
76
|
"publishConfig": {
|
|
68
77
|
"access": "public"
|
|
69
|
-
}
|
|
78
|
+
},
|
|
79
|
+
"peerDependencies": {
|
|
80
|
+
"class-validator": "^0.14.0"
|
|
81
|
+
},
|
|
82
|
+
"optionalDependencies": {}
|
|
70
83
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/decorators/index.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,aAAa,mDASzB,CAAC;AAEF,eAAO,MAAM,YAAY,mDAWxB,CAAC"}
|
package/dist/decorators/index.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.UploadedFile = exports.UploadedFiles = void 0;
|
|
4
|
-
const common_1 = require("@nestjs/common");
|
|
5
|
-
exports.UploadedFiles = (0, common_1.createParamDecorator)((_data, ctx) => {
|
|
6
|
-
const req = ctx.switchToHttp().getRequest();
|
|
7
|
-
const allFiles = [];
|
|
8
|
-
Object.values(req.storedFiles).forEach((files) => {
|
|
9
|
-
allFiles.push(...files);
|
|
10
|
-
});
|
|
11
|
-
return allFiles;
|
|
12
|
-
});
|
|
13
|
-
exports.UploadedFile = (0, common_1.createParamDecorator)((fieldName, ctx) => {
|
|
14
|
-
const req = ctx.switchToHttp().getRequest();
|
|
15
|
-
if (typeof fieldName === "string") {
|
|
16
|
-
const files = req.storedFiles[fieldName];
|
|
17
|
-
return files && files.length > 0 ? files[0] : null;
|
|
18
|
-
}
|
|
19
|
-
return req.storedFiles;
|
|
20
|
-
});
|
|
21
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/decorators/index.ts"],"names":[],"mappings":";;;AAAA,2CAAwE;AAG3D,QAAA,aAAa,GAAG,IAAA,6BAAoB,EAC/C,CAAC,KAAc,EAAE,GAAqB,EAAE,EAAE;IACxC,MAAM,GAAG,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC,UAAU,EAA4B,CAAC;IACtE,MAAM,QAAQ,GAA4B,EAAE,CAAC;IAC7C,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;QAC/C,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;IACH,OAAO,QAAQ,CAAC;AAClB,CAAC,CACF,CAAC;AAEW,QAAA,YAAY,GAAG,IAAA,6BAAoB,EAC9C,CAAC,SAA2B,EAAE,GAAqB,EAAE,EAAE;IACrD,MAAM,GAAG,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC,UAAU,EAA4B,CAAC;IAEtE,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,GAAG,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;QACzC,OAAO,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACrD,CAAC;IAED,OAAO,GAAG,CAAC,WAAW,CAAC;AACzB,CAAC,CACF,CAAC"}
|
package/dist/index.d.ts
DELETED
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,SAAS,CAAC;AACxB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC"}
|
package/dist/index.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
-
};
|
|
16
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
__exportStar(require("./decorators"), exports);
|
|
18
|
-
__exportStar(require("./types"), exports);
|
|
19
|
-
__exportStar(require("./interceptors"), exports);
|
|
20
|
-
__exportStar(require("./model"), exports);
|
|
21
|
-
__exportStar(require("./utils"), exports);
|
|
22
|
-
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,+CAA6B;AAC7B,0CAAwB;AACxB,iDAA+B;AAC/B,0CAAwB;AACxB,0CAAwB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/interceptors/index.ts"],"names":[],"mappings":"AACA,OAAO,EAML,eAAe,EACf,IAAI,EACL,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAG5C,wBAAgB,oBAAoB,CAClC,OAAO,GAAE,gBAAqB,GAC7B,IAAI,CAAC,eAAe,CAAC,CAyCvB"}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.MultipartInterceptor = MultipartInterceptor;
|
|
4
|
-
const common_1 = require("@nestjs/common");
|
|
5
|
-
const utils_1 = require("../utils");
|
|
6
|
-
function MultipartInterceptor(options = {}) {
|
|
7
|
-
class MixinInterceptor {
|
|
8
|
-
async intercept(context, next) {
|
|
9
|
-
const req = context.switchToHttp().getRequest();
|
|
10
|
-
if (!req.isMultipart())
|
|
11
|
-
throw new common_1.HttpException("The request should be a form-data", common_1.HttpStatus.BAD_REQUEST);
|
|
12
|
-
const files = {};
|
|
13
|
-
const body = {};
|
|
14
|
-
for await (const part of req.parts()) {
|
|
15
|
-
if (part.type !== "file") {
|
|
16
|
-
body[part.fieldname] = part.value;
|
|
17
|
-
continue;
|
|
18
|
-
}
|
|
19
|
-
const file = await (0, utils_1.getFileFromPart)(part);
|
|
20
|
-
const validationResult = (0, utils_1.validateFile)(file, options);
|
|
21
|
-
if (validationResult)
|
|
22
|
-
throw new common_1.HttpException(validationResult, common_1.HttpStatus.UNPROCESSABLE_ENTITY);
|
|
23
|
-
files[part.fieldname] = files[part.fieldname] || [];
|
|
24
|
-
files[part.fieldname].push(file);
|
|
25
|
-
}
|
|
26
|
-
req.storedFiles = files;
|
|
27
|
-
req.body = body;
|
|
28
|
-
return next.handle();
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return (0, common_1.mixin)(MixinInterceptor);
|
|
32
|
-
}
|
|
33
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/interceptors/index.ts"],"names":[],"mappings":";;AAeA,oDA2CC;AAzDD,2CAQwB;AAIxB,oCAAyD;AAEzD,SAAgB,oBAAoB,CAClC,UAA4B,EAAE;IAE9B,MAAM,gBAAgB;QACpB,KAAK,CAAC,SAAS,CACb,OAAyB,EACzB,IAAiB;YAEjB,MAAM,GAAG,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAA4B,CAAC;YAE1E,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE;gBACpB,MAAM,IAAI,sBAAa,CACrB,mCAAmC,EACnC,mBAAU,CAAC,WAAW,CACvB,CAAC;YAEJ,MAAM,KAAK,GAA0B,EAAE,CAAC;YACxC,MAAM,IAAI,GAAwB,EAAE,CAAC;YACrC,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC;gBACrC,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;oBACzB,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAI,IAAuB,CAAC,KAAK,CAAC;oBACtD,SAAS;gBACX,CAAC;gBACD,MAAM,IAAI,GAAG,MAAM,IAAA,uBAAe,EAAC,IAAI,CAAC,CAAC;gBACzC,MAAM,gBAAgB,GAAG,IAAA,oBAAY,EAAC,IAAI,EAAE,OAAO,CAAC,CAAC;gBAErD,IAAI,gBAAgB;oBAClB,MAAM,IAAI,sBAAa,CACrB,gBAAgB,EAChB,mBAAU,CAAC,oBAAoB,CAChC,CAAC;gBAEJ,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;gBACpD,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnC,CAAC;YACD,GAAG,CAAC,WAAW,GAAG,KAAK,CAAC;YACxB,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;YAEhB,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;QACvB,CAAC;KACF;IAED,OAAO,IAAA,cAAK,EAAC,gBAAgB,CAAC,CAAC;AACjC,CAAC"}
|
package/dist/model/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/model/index.ts"],"names":[],"mappings":"AAAA,qBAAa,gBAAgB;IAElB,WAAW,CAAC,EAAE,MAAM;IACpB,QAAQ,CAAC,GAAE,MAAM,GAAG,MAAM;gBAD1B,WAAW,CAAC,EAAE,MAAM,YAAA,EACpB,QAAQ,CAAC,GAAE,MAAM,GAAG,MAAM,aAAA;CAEpC"}
|
package/dist/model/index.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.MultipartOptions = void 0;
|
|
4
|
-
class MultipartOptions {
|
|
5
|
-
constructor(maxFileSize, fileType) {
|
|
6
|
-
this.maxFileSize = maxFileSize;
|
|
7
|
-
this.fileType = fileType;
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
exports.MultipartOptions = MultipartOptions;
|
|
11
|
-
//# sourceMappingURL=index.js.map
|
package/dist/model/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/model/index.ts"],"names":[],"mappings":";;;AAAA,MAAa,gBAAgB;IAC3B,YACS,WAAoB,EACpB,QAA0B;QAD1B,gBAAW,GAAX,WAAW,CAAS;QACpB,aAAQ,GAAR,QAAQ,CAAkB;IAChC,CAAC;CACL;AALD,4CAKC"}
|
package/dist/types/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;AAClD,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC"}
|
package/dist/types/index.js
DELETED
package/dist/types/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":""}
|
package/dist/utils/index.d.ts
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import { MultipartFile } from "@fastify/multipart";
|
|
2
|
-
import { MultipartOptions } from "../model";
|
|
3
|
-
export declare const getFileFromPart: (part: MultipartFile) => Promise<Storage.MultipartFile>;
|
|
4
|
-
export declare const validateFile: (file: Storage.MultipartFile, options: MultipartOptions) => string | void;
|
|
5
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAMnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAE5C,eAAO,MAAM,eAAe,GAC1B,MAAM,aAAa,KAClB,OAAO,CAAC,OAAO,CAAC,aAAa,CAS/B,CAAC;AAEF,eAAO,MAAM,YAAY,GACvB,MAAM,OAAO,CAAC,aAAa,EAC3B,SAAS,gBAAgB,KACxB,MAAM,GAAG,IAaX,CAAC"}
|
package/dist/utils/index.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.validateFile = exports.getFileFromPart = void 0;
|
|
4
|
-
const common_1 = require("@nestjs/common");
|
|
5
|
-
const getFileFromPart = async (part) => {
|
|
6
|
-
const buffer = await part.toBuffer();
|
|
7
|
-
return {
|
|
8
|
-
buffer,
|
|
9
|
-
size: buffer.byteLength,
|
|
10
|
-
filename: part.filename,
|
|
11
|
-
mimetype: part.mimetype,
|
|
12
|
-
fieldname: part.fieldname,
|
|
13
|
-
};
|
|
14
|
-
};
|
|
15
|
-
exports.getFileFromPart = getFileFromPart;
|
|
16
|
-
const validateFile = (file, options) => {
|
|
17
|
-
const validators = [];
|
|
18
|
-
if (options.maxFileSize)
|
|
19
|
-
validators.push(new common_1.MaxFileSizeValidator({ maxSize: options.maxFileSize }));
|
|
20
|
-
if (options.fileType)
|
|
21
|
-
validators.push(new common_1.FileTypeValidator({ fileType: options.fileType }));
|
|
22
|
-
for (const validator of validators) {
|
|
23
|
-
if (validator.isValid(file))
|
|
24
|
-
continue;
|
|
25
|
-
return validator.buildErrorMessage(file);
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
exports.validateFile = validateFile;
|
|
29
|
-
//# sourceMappingURL=index.js.map
|
package/dist/utils/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":";;;AACA,2CAIwB;AAGjB,MAAM,eAAe,GAAG,KAAK,EAClC,IAAmB,EACa,EAAE;IAClC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;IACrC,OAAO;QACL,MAAM;QACN,IAAI,EAAE,MAAM,CAAC,UAAU;QACvB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,SAAS,EAAE,IAAI,CAAC,SAAS;KAC1B,CAAC;AACJ,CAAC,CAAC;AAXW,QAAA,eAAe,mBAW1B;AAEK,MAAM,YAAY,GAAG,CAC1B,IAA2B,EAC3B,OAAyB,EACV,EAAE;IACjB,MAAM,UAAU,GAAoB,EAAE,CAAC;IAEvC,IAAI,OAAO,CAAC,WAAW;QACrB,UAAU,CAAC,IAAI,CAAC,IAAI,6BAAoB,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IAC9E,IAAI,OAAO,CAAC,QAAQ;QAClB,UAAU,CAAC,IAAI,CAAC,IAAI,0BAAiB,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IAEzE,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC;YAAE,SAAS;QAEtC,OAAO,SAAS,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAC3C,CAAC;AACH,CAAC,CAAC;AAhBW,QAAA,YAAY,gBAgBvB"}
|