node-server-engine 1.2.5 → 1.2.7
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/README.md +43 -18
- package/dist/middleware/fileUploader/fileUploader.d.ts +4 -5
- package/dist/middleware/fileUploader/fileUploader.js +68 -255
- package/dist/middleware/fileUploader/fileUploader.js.map +1 -1
- package/dist/middleware/fileUploader/fileUploader.types.d.ts +13 -32
- package/dist/middleware/fileUploader/fileUploader.types.js.map +1 -1
- package/package.json +2 -2
- package/src/middleware/fileUploader/fileUploader.ts +70 -270
- package/src/middleware/fileUploader/fileUploader.types.ts +16 -33
- package/test/env.ts +1 -0
- package/dist/middleware/fileUploader/compressAvatar/compressAvatar.d.ts +0 -6
- package/dist/middleware/fileUploader/compressAvatar/compressAvatar.js +0 -28
- package/dist/middleware/fileUploader/compressAvatar/compressAvatar.js.map +0 -1
- package/dist/middleware/fileUploader/compressAvatar/compressAvatar.types.d.ts +0 -12
- package/dist/middleware/fileUploader/compressAvatar/compressAvatar.types.js +0 -2
- package/dist/middleware/fileUploader/compressAvatar/compressAvatar.types.js.map +0 -1
- package/dist/middleware/fileUploader/compressAvatar/index.d.ts +0 -2
- package/dist/middleware/fileUploader/compressAvatar/index.js +0 -28
- package/dist/middleware/fileUploader/compressAvatar/index.js.map +0 -1
- package/src/middleware/fileUploader/compressAvatar/compressAvatar.ts +0 -25
- package/src/middleware/fileUploader/compressAvatar/compressAvatar.types.ts +0 -12
- package/src/middleware/fileUploader/compressAvatar/index.ts +0 -2
- package/src/middleware/fileUploader/fileUploader.test.ts +0 -310
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Framework used to develop Node backend services. This package ships with a lot o
|
|
|
11
11
|
- [Endpoint Configuration](#endpoint-configuration)
|
|
12
12
|
- [Methods](#methods)
|
|
13
13
|
- [Authentication](#authentication)
|
|
14
|
-
- [File Upload](#file-upload)
|
|
14
|
+
- [File Upload Middleware](#file-upload-middleware)
|
|
15
15
|
- [Socket Client](#socket-client)
|
|
16
16
|
- [Socket Client Options](#socket-client-options)
|
|
17
17
|
- [Properties](#properties)
|
|
@@ -203,29 +203,54 @@ The server engine exposes an enumeration for auth types.
|
|
|
203
203
|
|
|
204
204
|
<!-- markdownlint-enable MD033 -->
|
|
205
205
|
|
|
206
|
-
|
|
206
|
+
### File Upload Middleware
|
|
207
207
|
|
|
208
|
-
|
|
208
|
+
This middleware handles multipart file uploads in an Express application. It processes files in memory, validates them based on configuration options, and ensures that required files are uploaded.
|
|
209
209
|
|
|
210
|
-
|
|
210
|
+
## Usage
|
|
211
211
|
|
|
212
|
-
The
|
|
212
|
+
The request must be made using `multipart/form-data`. The uploaded files will be available in `req.files`.
|
|
213
213
|
|
|
214
214
|
The following settings can be used on each object the Endpoint's `options.files`.
|
|
215
215
|
|
|
216
|
-
| Property
|
|
217
|
-
|
|
|
218
|
-
| key
|
|
219
|
-
|
|
|
220
|
-
|
|
|
221
|
-
|
|
|
222
|
-
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
216
|
+
| Property | Type | Description | Default |
|
|
217
|
+
| ----------- | -------------- | -------------------------------------------------------------------- | ------------ |
|
|
218
|
+
| key | string | Form key as which the file should be fetched | **required** |
|
|
219
|
+
| maxSize | string | Maximum file size in a human readable format (ex: 5MB) | **required** |
|
|
220
|
+
| mimeTypes | Array\<string> | A list of accepted MIME Types | [] |
|
|
221
|
+
| required | boolean | Will fail the request and not store the files if this one is missing | false |
|
|
222
|
+
| noExtension | boolean | Store the file with no extension | false |
|
|
223
|
+
|
|
224
|
+
## Example
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
import { body } from 'express-validator';
|
|
228
|
+
import { Endpoint, middleware, AuthType, Method } from 'node-server-engine';
|
|
229
|
+
|
|
230
|
+
const filesConfig = [
|
|
231
|
+
{ key: 'avatar', mimeTypes: ['image/png', 'image/jpeg'], required: true },
|
|
232
|
+
{ key: 'document', mimeTypes: ['application/pdf'], maxSize: '5MB' }
|
|
233
|
+
];
|
|
234
|
+
|
|
235
|
+
new Endpoint({
|
|
236
|
+
path: '/upload',
|
|
237
|
+
method: Method.POST,
|
|
238
|
+
authType: AuthType.JWT,
|
|
239
|
+
files: filesConfig,
|
|
240
|
+
handler: (req, res) => {
|
|
241
|
+
res.json({ message: 'Files uploaded successfully', files: req.files });
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Features
|
|
247
|
+
|
|
248
|
+
- Supports multiple file uploads.
|
|
249
|
+
- Validates file types and sizes.
|
|
250
|
+
- Ensures required files are uploaded.
|
|
251
|
+
- Uses memory storage (files are not saved to disk).
|
|
252
|
+
|
|
253
|
+
This middleware simplifies file handling in Express, making it easy to manage uploads while enforcing validation rules.
|
|
229
254
|
|
|
230
255
|
---
|
|
231
256
|
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import Busboy from 'busboy';
|
|
2
1
|
import { Request, Response, NextFunction } from 'express';
|
|
3
2
|
import { Schema } from 'express-validator';
|
|
4
3
|
import { FileUploaderConfig } from './fileUploader.types';
|
|
5
4
|
/**
|
|
6
|
-
*
|
|
5
|
+
* Middleware for handling file uploads.
|
|
6
|
+
* @param filesOptions Configuration options for expected files.
|
|
7
|
+
* @param validator Validation schema for request fields.
|
|
7
8
|
*/
|
|
8
|
-
export declare function fileUploader(
|
|
9
|
-
/** Handle file upload and generic multi-part parsing */
|
|
10
|
-
export declare function fileUploaderMiddleware(request: Request, response: Response, next: NextFunction, filesOptions: Array<FileUploaderConfig>, validator: Schema): Busboy.Busboy;
|
|
9
|
+
export declare function fileUploader(filesOptions: Array<FileUploaderConfig>, validator: Schema): (request: Request, response: Response, next: NextFunction) => Promise<void>;
|
|
@@ -5,292 +5,105 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
value: true
|
|
6
6
|
});
|
|
7
7
|
exports.fileUploader = fileUploader;
|
|
8
|
-
exports.fileUploaderMiddleware = fileUploaderMiddleware;
|
|
9
|
-
var _busboy = _interopRequireDefault(require("busboy"));
|
|
10
|
-
var _compressAvatar = require("./compressAvatar");
|
|
11
|
-
var _isMultiPart = require("./isMultiPart");
|
|
12
|
-
var _validate = require("./validate");
|
|
13
|
-
var _Storage = require("../../entities/Storage");
|
|
14
8
|
var _WebError = require("../../entities/WebError");
|
|
15
9
|
var _report = require("../../utils/report");
|
|
10
|
+
var _multer = _interopRequireDefault(require("multer"));
|
|
11
|
+
var _validate = require("./validate");
|
|
16
12
|
const namespace = 'engine:middleware:fileUploader';
|
|
17
13
|
|
|
18
14
|
/**
|
|
19
|
-
*
|
|
15
|
+
* Middleware for handling file uploads.
|
|
16
|
+
* @param filesOptions Configuration options for expected files.
|
|
17
|
+
* @param validator Validation schema for request fields.
|
|
20
18
|
*/
|
|
21
|
-
function fileUploader(
|
|
22
|
-
return (request, response, next) =>
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** Handle file upload and generic multi-part parsing */
|
|
26
|
-
function fileUploaderMiddleware(request, response, next, filesOptions, validator) {
|
|
27
|
-
(0, _report.reportDebug)({
|
|
28
|
-
namespace,
|
|
29
|
-
message: 'Received request to upload file',
|
|
30
|
-
data: {
|
|
31
|
-
filesOptions,
|
|
32
|
-
validator
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
// Check that the request is a proper multipart request
|
|
36
|
-
if (!(0, _isMultiPart.isMultipart)(request)) throw new _WebError.WebError({
|
|
37
|
-
statusCode: 415,
|
|
38
|
-
errorCode: 'invalid-content-type',
|
|
39
|
-
message: 'Request is not of type multipart/form-data',
|
|
40
|
-
data: {
|
|
41
|
-
contentType: request.get('Content-Type')
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
// If no files to handle ignore
|
|
45
|
-
if (!filesOptions.length) next();
|
|
46
|
-
// Parse multipart data with Busboy
|
|
47
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
48
|
-
const busboy = new _busboy.default({
|
|
49
|
-
headers: {
|
|
50
|
-
...request.headers,
|
|
51
|
-
'content-type': request.headers['content-type'] ?? ''
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
let hasError = false;
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Function called when an error happened in the process
|
|
58
|
-
* It immediately stops and drains all connection and deletes all files that have already been uploaded
|
|
59
|
-
*/
|
|
60
|
-
function triggerError(error) {
|
|
61
|
-
pendingWrites--;
|
|
62
|
-
if (hasError) return;
|
|
63
|
-
hasError = true;
|
|
64
|
-
request.unpipe(busboy);
|
|
65
|
-
request.on('readable', request.read.bind(request));
|
|
66
|
-
busboy.removeAllListeners();
|
|
67
|
-
next(error);
|
|
68
|
-
if (request.files) {
|
|
69
|
-
Object.values(request.files).forEach(files => {
|
|
70
|
-
files.forEach(file => {
|
|
71
|
-
_Storage.Storage.delete(file.bucket, file.name).catch(error => {
|
|
72
|
-
(0, _report.reportError)(error);
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
let readFinished = false;
|
|
79
|
-
let fieldsValidated = false;
|
|
80
|
-
let pendingWrites = 0;
|
|
81
|
-
|
|
82
|
-
/** Function called when all the body has been parsed */
|
|
83
|
-
function done() {
|
|
84
|
-
if (readFinished && pendingWrites === 0 && fieldsValidated && !hasError) {
|
|
85
|
-
const uploadedFiles = Object.keys(request.files ?? {});
|
|
86
|
-
if (filesOptions.some(options => options.required && !uploadedFiles.includes(options.key))) {
|
|
87
|
-
triggerError(new _WebError.WebError({
|
|
88
|
-
statusCode: 400,
|
|
89
|
-
errorCode: 'missing-field',
|
|
90
|
-
message: 'Some required files have not been uploaded',
|
|
91
|
-
data: {
|
|
92
|
-
filesOptions,
|
|
93
|
-
validator,
|
|
94
|
-
uploadedFiles
|
|
95
|
-
}
|
|
96
|
-
}));
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
next();
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/*
|
|
104
|
-
* On cancel request, while processing
|
|
105
|
-
*/
|
|
106
|
-
request.connection.on('close', function () {
|
|
107
|
-
if (!response.writableEnded) triggerError(new Error('Request cancelled'));
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Once all the fields have been read they are checked against the validation schema
|
|
112
|
-
*/
|
|
113
|
-
async function validateFields() {
|
|
114
|
-
try {
|
|
115
|
-
await (0, _validate.validate)(request, validator);
|
|
116
|
-
} catch (error) {
|
|
117
|
-
triggerError(error);
|
|
118
|
-
}
|
|
19
|
+
function fileUploader(filesOptions, validator) {
|
|
20
|
+
return async (request, response, next) => {
|
|
119
21
|
(0, _report.reportDebug)({
|
|
120
22
|
namespace,
|
|
121
|
-
message: '
|
|
23
|
+
message: 'Received request to upload file',
|
|
122
24
|
data: {
|
|
123
|
-
hasError,
|
|
124
25
|
filesOptions,
|
|
125
26
|
validator
|
|
126
27
|
}
|
|
127
28
|
});
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
pendingWrites
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
// Do not handle the stream if there has been an error previously
|
|
146
|
-
if (hasError) file.destroy();
|
|
147
|
-
pendingWrites++;
|
|
148
|
-
// Find related file options based on the field name
|
|
149
|
-
const options = filesOptions.find(fileOption => fileOption.key === field);
|
|
150
|
-
// Make sure that the field is in the config
|
|
151
|
-
if (!options) {
|
|
152
|
-
triggerError(new _WebError.WebError({
|
|
153
|
-
statusCode: 400,
|
|
154
|
-
errorCode: 'invalid-file-field',
|
|
155
|
-
message: 'Request contains a file at an invalid field',
|
|
156
|
-
hint: {
|
|
157
|
-
field
|
|
158
|
-
},
|
|
29
|
+
|
|
30
|
+
// Setup multer with memory storage
|
|
31
|
+
const storage = _multer.default.memoryStorage();
|
|
32
|
+
const upload = (0, _multer.default)({
|
|
33
|
+
storage,
|
|
34
|
+
limits: {
|
|
35
|
+
fileSize: 10 * 1024 * 1024
|
|
36
|
+
} // Limit file size to 10MB
|
|
37
|
+
}).any();
|
|
38
|
+
upload(request, response, async err => {
|
|
39
|
+
if (err) {
|
|
40
|
+
(0, _report.reportDebug)({
|
|
41
|
+
namespace,
|
|
42
|
+
message: 'Multer upload error',
|
|
159
43
|
data: {
|
|
160
|
-
|
|
44
|
+
error: err.message
|
|
161
45
|
}
|
|
162
|
-
})
|
|
163
|
-
return;
|
|
46
|
+
});
|
|
47
|
+
return next(err);
|
|
164
48
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
encoding,
|
|
173
|
-
mimeType,
|
|
174
|
-
pendingWrites,
|
|
175
|
-
options
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
// If a specific MIME type is required check it
|
|
179
|
-
if (options.mimeTypes && !options.mimeTypes.includes(mimeType)) {
|
|
180
|
-
triggerError(new _WebError.WebError({
|
|
181
|
-
statusCode: 415,
|
|
182
|
-
errorCode: 'invalid-mime-type',
|
|
183
|
-
message: 'Request contains a file with an invalid MIME type',
|
|
184
|
-
hint: {
|
|
185
|
-
field
|
|
186
|
-
},
|
|
187
|
-
data: {
|
|
188
|
-
field,
|
|
189
|
-
mimeType,
|
|
190
|
-
expected: options.mimeTypes
|
|
191
|
-
}
|
|
192
|
-
}));
|
|
193
|
-
return;
|
|
49
|
+
|
|
50
|
+
// Ensure `request.files` is properly typed
|
|
51
|
+
let uploadedFiles = [];
|
|
52
|
+
if (Array.isArray(request.files)) {
|
|
53
|
+
uploadedFiles = request.files;
|
|
54
|
+
} else if (typeof request.files === 'object') {
|
|
55
|
+
uploadedFiles = Object.values(request.files).flat();
|
|
194
56
|
}
|
|
195
|
-
|
|
57
|
+
|
|
58
|
+
// Log uploaded files
|
|
196
59
|
(0, _report.reportDebug)({
|
|
197
60
|
namespace,
|
|
198
|
-
message: '
|
|
61
|
+
message: 'Files Received',
|
|
199
62
|
data: {
|
|
200
|
-
|
|
201
|
-
field,
|
|
202
|
-
fileName,
|
|
203
|
-
encoding,
|
|
204
|
-
mimeType,
|
|
205
|
-
pendingWrites,
|
|
206
|
-
options
|
|
63
|
+
uploadedFiles: uploadedFiles.map(file => file.originalname)
|
|
207
64
|
}
|
|
208
65
|
});
|
|
66
|
+
|
|
67
|
+
// Validate request body fields
|
|
209
68
|
try {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
contentType: mimeType
|
|
69
|
+
await (0, _validate.validate)(request, validator);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
(0, _report.reportDebug)({
|
|
72
|
+
namespace,
|
|
73
|
+
message: 'Validation failed',
|
|
74
|
+
data: {
|
|
75
|
+
error
|
|
218
76
|
}
|
|
219
|
-
}, {
|
|
220
|
-
maxSize: options.maxSize
|
|
221
77
|
});
|
|
78
|
+
return next(error);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Extract uploaded field names
|
|
82
|
+
const uploadedFileKeys = uploadedFiles.map(file => file.fieldname);
|
|
83
|
+
|
|
84
|
+
// Check for missing required files
|
|
85
|
+
const missingFiles = filesOptions.filter(option => option.required && !uploadedFileKeys.includes(option.key));
|
|
86
|
+
if (missingFiles.length > 0) {
|
|
222
87
|
(0, _report.reportDebug)({
|
|
223
88
|
namespace,
|
|
224
|
-
message: '
|
|
89
|
+
message: 'Missing required files',
|
|
225
90
|
data: {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
fileName,
|
|
229
|
-
encoding,
|
|
230
|
-
mimeType,
|
|
231
|
-
pendingWrites,
|
|
232
|
-
options,
|
|
233
|
-
uploadedFile
|
|
91
|
+
missingFiles,
|
|
92
|
+
uploadedFileKeys
|
|
234
93
|
}
|
|
235
94
|
});
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
if (!request.files[field]) request.files[field] = [];
|
|
246
|
-
request.files[field].push({
|
|
247
|
-
...uploadedFile,
|
|
248
|
-
originalFileName: fileName
|
|
249
|
-
});
|
|
250
|
-
done();
|
|
251
|
-
}
|
|
252
|
-
} catch (error) {
|
|
253
|
-
triggerError(error);
|
|
95
|
+
return next(new _WebError.WebError({
|
|
96
|
+
statusCode: 400,
|
|
97
|
+
errorCode: 'missing-field',
|
|
98
|
+
message: 'Some required files have not been uploaded',
|
|
99
|
+
data: {
|
|
100
|
+
missingFiles,
|
|
101
|
+
uploadedFileKeys
|
|
102
|
+
}
|
|
103
|
+
}));
|
|
254
104
|
}
|
|
255
|
-
|
|
256
|
-
triggerError(error);
|
|
257
|
-
});
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Handle non file fields
|
|
262
|
-
* These fields will be pushed to the body as array of strings
|
|
263
|
-
*/
|
|
264
|
-
busboy.on('field', (fieldName, value) => {
|
|
265
|
-
if (!request.body) request.body = {};
|
|
266
|
-
if (request.body[fieldName]) {
|
|
267
|
-
request.body[fieldName].push(value);
|
|
268
|
-
} else {
|
|
269
|
-
request.body[fieldName] = [value];
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
busboy.on('error', function (error) {
|
|
273
|
-
triggerError(error);
|
|
274
|
-
});
|
|
275
|
-
busboy.on('finish', () => {
|
|
276
|
-
(async () => {
|
|
277
|
-
(0, _report.reportDebug)({
|
|
278
|
-
namespace,
|
|
279
|
-
message: 'Busboy finished reading',
|
|
280
|
-
data: {
|
|
281
|
-
hasError,
|
|
282
|
-
filesOptions,
|
|
283
|
-
validator,
|
|
284
|
-
pendingWrites
|
|
285
|
-
}
|
|
286
|
-
});
|
|
287
|
-
readFinished = true;
|
|
288
|
-
await validateFields();
|
|
289
|
-
done();
|
|
290
|
-
})().catch(error => {
|
|
291
|
-
triggerError(error);
|
|
105
|
+
next();
|
|
292
106
|
});
|
|
293
|
-
}
|
|
294
|
-
return request.pipe(busboy);
|
|
107
|
+
};
|
|
295
108
|
}
|
|
296
109
|
//# sourceMappingURL=fileUploader.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fileUploader.js","names":["_busboy","_interopRequireDefault","require","_compressAvatar","_isMultiPart","_validate","_Storage","_WebError","_report","namespace","fileUploader","options","validator","request","response","next","fileUploaderMiddleware","filesOptions","reportDebug","message","data","isMultipart","WebError","statusCode","errorCode","contentType","get","length","busboy","Busboy","headers","hasError","triggerError","error","pendingWrites","unpipe","on","read","bind","removeAllListeners","files","Object","values","forEach","file","Storage","delete","bucket","name","catch","reportError","readFinished","fieldsValidated","done","uploadedFiles","keys","some","required","includes","key","connection","writableEnded","Error","validateFields","validate","field","fileName","encoding","mimeType","destroy","find","fileOption","hint","mimeTypes","expected","fileToUpload","image","compressAvatar","uploadedFile","upload","directory","path","mime","noExtension","metadata","maxSize","push","originalFileName","fieldName","value","body","pipe"],"sources":["../../../src/middleware/fileUploader/fileUploader.ts"],"sourcesContent":["import Busboy from 'busboy';\nimport { Request, Response, NextFunction } from 'express';\nimport { Schema } from 'express-validator';\nimport { compressAvatar } from './compressAvatar';\nimport { FileUploaderConfig } from './fileUploader.types';\nimport { isMultipart } from './isMultiPart';\nimport { validate } from './validate';\nimport { Storage } from 'entities/Storage';\nimport { WebError } from 'entities/WebError';\nimport { reportError, reportDebug } from 'utils/report';\nimport { Readable } from 'stream';\n\nconst namespace = 'engine:middleware:fileUploader';\n\n/**\n * Generate a custom fileUploader middleware\n */\nexport function fileUploader(\n options: Array<FileUploaderConfig>,\n validator: Schema\n) {\n return (\n request: Request,\n response: Response,\n next: NextFunction\n ): Busboy.Busboy =>\n fileUploaderMiddleware(request, response, next, options, validator);\n}\n\n/** Handle file upload and generic multi-part parsing */\nexport function fileUploaderMiddleware(\n request: Request,\n response: Response,\n next: NextFunction,\n filesOptions: Array<FileUploaderConfig>,\n validator: Schema\n): Busboy.Busboy {\n reportDebug({\n namespace,\n message: 'Received request to upload file',\n data: { filesOptions, validator }\n });\n // Check that the request is a proper multipart request\n if (!isMultipart(request))\n throw new WebError({\n statusCode: 415,\n errorCode: 'invalid-content-type',\n message: 'Request is not of type multipart/form-data',\n data: { contentType: request.get('Content-Type') }\n });\n // If no files to handle ignore\n if (!filesOptions.length) next();\n // Parse multipart data with Busboy\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const busboy = new (Busboy as any)({\n headers: {\n ...request.headers,\n 'content-type': request.headers['content-type'] ?? ''\n }\n });\n\n let hasError = false;\n\n /**\n * Function called when an error happened in the process\n * It immediately stops and drains all connection and deletes all files that have already been uploaded\n */\n function triggerError(error: unknown): void {\n pendingWrites--;\n if (hasError) return;\n hasError = true;\n request.unpipe(busboy);\n request.on('readable', request.read.bind(request));\n busboy.removeAllListeners();\n next(error);\n if (request.files) {\n Object.values(request.files).forEach((files) => {\n files.forEach((file) => {\n Storage.delete(file.bucket, file.name).catch((error) => {\n reportError(error);\n });\n });\n });\n }\n }\n\n let readFinished = false;\n let fieldsValidated = false;\n let pendingWrites = 0;\n\n /** Function called when all the body has been parsed */\n function done(): void {\n if (readFinished && pendingWrites === 0 && fieldsValidated && !hasError) {\n const uploadedFiles = Object.keys(request.files ?? {});\n if (\n filesOptions.some(\n (options) => options.required && !uploadedFiles.includes(options.key)\n )\n ) {\n triggerError(\n new WebError({\n statusCode: 400,\n errorCode: 'missing-field',\n message: 'Some required files have not been uploaded',\n data: { filesOptions, validator, uploadedFiles }\n })\n );\n return;\n }\n next();\n }\n }\n\n /*\n * On cancel request, while processing\n */\n request.connection.on('close', function () {\n if (!response.writableEnded) triggerError(new Error('Request cancelled'));\n });\n\n /**\n * Once all the fields have been read they are checked against the validation schema\n */\n async function validateFields(): Promise<void> {\n try {\n await validate(request, validator);\n } catch (error) {\n triggerError(error);\n }\n reportDebug({\n namespace,\n message: 'Validation processed',\n data: { hasError, filesOptions, validator }\n });\n fieldsValidated = true;\n done();\n }\n\n busboy.on(\n 'file',\n (\n field: string,\n file: Readable,\n fileName: string,\n encoding: unknown,\n mimeType: string\n ) => {\n (async (): Promise<void> => {\n reportDebug({\n namespace,\n message: 'Found file in request',\n data: { hasError, field, fileName, encoding, mimeType, pendingWrites }\n });\n // Do not handle the stream if there has been an error previously\n if (hasError) file.destroy();\n pendingWrites++;\n // Find related file options based on the field name\n const options = filesOptions.find(\n (fileOption) => fileOption.key === field\n );\n // Make sure that the field is in the config\n if (!options) {\n triggerError(\n new WebError({\n statusCode: 400,\n errorCode: 'invalid-file-field',\n message: 'Request contains a file at an invalid field',\n hint: { field },\n data: { field }\n })\n );\n return;\n }\n reportDebug({\n namespace,\n message: 'Found options for file',\n data: {\n hasError,\n field,\n fileName,\n encoding,\n mimeType,\n pendingWrites,\n options\n }\n });\n // If a specific MIME type is required check it\n if (options.mimeTypes && !options.mimeTypes.includes(mimeType)) {\n triggerError(\n new WebError({\n statusCode: 415,\n errorCode: 'invalid-mime-type',\n message: 'Request contains a file with an invalid MIME type',\n hint: { field },\n data: { field, mimeType, expected: options.mimeTypes }\n })\n );\n return;\n }\n // Generate the destination path of the file and upload\n reportDebug({\n namespace,\n message: 'Starting file upload',\n data: {\n hasError,\n field,\n fileName,\n encoding,\n mimeType,\n pendingWrites,\n options\n }\n });\n try {\n const fileToUpload = options.image\n ? compressAvatar(file, options.image)\n : file;\n\n const uploadedFile = await Storage.upload(\n fileToUpload,\n options.bucket,\n {\n directory: options.path,\n mime: mimeType,\n noExtension: options.noExtension\n },\n {\n metadata: { contentType: mimeType }\n },\n { maxSize: options.maxSize }\n );\n\n reportDebug({\n namespace,\n message: 'File uploaded',\n data: {\n hasError,\n field,\n fileName,\n encoding,\n mimeType,\n pendingWrites,\n options,\n uploadedFile\n }\n });\n if (hasError) {\n // If some other failed before this one terminated\n // Delete file\n await Storage.delete(uploadedFile.bucket, uploadedFile.name);\n } else {\n // Handle successful upload\n // Add the file's data to the request\n pendingWrites--;\n if (!request.files) request.files = {};\n if (!request.files[field]) request.files[field] = [];\n request.files[field].push({\n ...uploadedFile,\n originalFileName: fileName\n });\n done();\n }\n } catch (error) {\n triggerError(error);\n }\n })().catch((error) => {\n triggerError(error);\n });\n }\n );\n\n /**\n * Handle non file fields\n * These fields will be pushed to the body as array of strings\n */\n busboy.on('field', (fieldName: string, value: unknown) => {\n if (!request.body) request.body = {};\n\n if (request.body[fieldName]) {\n request.body[fieldName].push(value);\n } else {\n request.body[fieldName] = [value];\n }\n });\n busboy.on('error', function (error: unknown) {\n triggerError(error);\n });\n busboy.on('finish', () => {\n (async (): Promise<void> => {\n reportDebug({\n namespace,\n message: 'Busboy finished reading',\n data: { hasError, filesOptions, validator, pendingWrites }\n });\n readFinished = true;\n await validateFields();\n done();\n })().catch((error) => {\n triggerError(error);\n });\n });\n return request.pipe(busboy);\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"fileUploader.js","names":["_WebError","require","_report","_multer","_interopRequireDefault","_validate","namespace","fileUploader","filesOptions","validator","request","response","next","reportDebug","message","data","storage","multer","memoryStorage","upload","limits","fileSize","any","err","error","uploadedFiles","Array","isArray","files","Object","values","flat","map","file","originalname","validate","uploadedFileKeys","fieldname","missingFiles","filter","option","required","includes","key","length","WebError","statusCode","errorCode"],"sources":["../../../src/middleware/fileUploader/fileUploader.ts"],"sourcesContent":["import { Request, Response, NextFunction } from 'express';\nimport { Schema } from 'express-validator';\nimport { WebError } from 'entities/WebError';\nimport { reportDebug } from 'utils/report';\nimport multer from 'multer';\nimport { FileUploaderConfig } from './fileUploader.types';\nimport { validate } from './validate';\n\nconst namespace = 'engine:middleware:fileUploader';\n\n/**\n * Middleware for handling file uploads.\n * @param filesOptions Configuration options for expected files.\n * @param validator Validation schema for request fields.\n */\nexport function fileUploader(\n filesOptions: Array<FileUploaderConfig>,\n validator: Schema\n) {\n return async (\n request: Request,\n response: Response,\n next: NextFunction\n ): Promise<void> => {\n reportDebug({\n namespace,\n message: 'Received request to upload file',\n data: { filesOptions, validator }\n });\n\n // Setup multer with memory storage\n const storage = multer.memoryStorage();\n const upload = multer({\n storage,\n limits: { fileSize: 10 * 1024 * 1024 } // Limit file size to 10MB\n }).any();\n\n upload(request, response, async (err) => {\n if (err) {\n reportDebug({\n namespace,\n message: 'Multer upload error',\n data: { error: err.message }\n });\n return next(err);\n }\n\n // Ensure `request.files` is properly typed\n let uploadedFiles: Express.Multer.File[] = [];\n if (Array.isArray(request.files)) {\n uploadedFiles = request.files;\n } else if (typeof request.files === 'object') {\n uploadedFiles = Object.values(request.files).flat();\n }\n\n // Log uploaded files\n reportDebug({\n namespace,\n message: 'Files Received',\n data: { uploadedFiles: uploadedFiles.map((file) => file.originalname) }\n });\n\n // Validate request body fields\n try {\n await validate(request, validator);\n } catch (error) {\n reportDebug({\n namespace,\n message: 'Validation failed',\n data: { error }\n });\n return next(error);\n }\n\n // Extract uploaded field names\n const uploadedFileKeys = uploadedFiles.map((file) => file.fieldname);\n\n // Check for missing required files\n const missingFiles = filesOptions.filter(\n (option) => option.required && !uploadedFileKeys.includes(option.key)\n );\n\n if (missingFiles.length > 0) {\n reportDebug({\n namespace,\n message: 'Missing required files',\n data: { missingFiles, uploadedFileKeys }\n });\n\n return next(\n new WebError({\n statusCode: 400,\n errorCode: 'missing-field',\n message: 'Some required files have not been uploaded',\n data: { missingFiles, uploadedFileKeys }\n })\n );\n }\n\n next();\n });\n };\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,49 +1,30 @@
|
|
|
1
|
-
import { CompressionOptions } from './compressAvatar';
|
|
2
|
-
import { StorageUploadedFile } from '../../entities/Storage';
|
|
3
1
|
declare global {
|
|
4
2
|
namespace Express {
|
|
5
|
-
/** Express request object */
|
|
6
3
|
interface Request {
|
|
7
|
-
/**
|
|
4
|
+
/** Uploaded files mapped by field name */
|
|
8
5
|
files?: {
|
|
9
|
-
[key: string]:
|
|
10
|
-
};
|
|
6
|
+
[key: string]: Express.Multer.File[];
|
|
7
|
+
} | Express.Multer.File[] | undefined;
|
|
11
8
|
}
|
|
12
9
|
}
|
|
13
10
|
}
|
|
14
11
|
/** Custom uploaded file metadata */
|
|
15
|
-
export interface UploadedFile extends
|
|
16
|
-
/**
|
|
12
|
+
export interface UploadedFile extends Express.Multer.File {
|
|
13
|
+
/** Original file name (use `originalname` from `Express.Multer.File`) */
|
|
17
14
|
originalFileName: string;
|
|
15
|
+
/** File data buffer (use `buffer` from `Express.Multer.File`) */
|
|
16
|
+
file: Buffer;
|
|
18
17
|
}
|
|
19
|
-
/**
|
|
20
|
-
interface FileUploaderOutputs {
|
|
21
|
-
/** Name of the output */
|
|
22
|
-
name: string;
|
|
23
|
-
/** Path to which this specific output is stored (defaults to default path if not set) */
|
|
24
|
-
path?: string;
|
|
25
|
-
/** Bucket to which this specific output is stored (defaults to default bucket if not set) */
|
|
26
|
-
bucket?: string;
|
|
27
|
-
}
|
|
28
|
-
/** Configuration for a file upload */
|
|
18
|
+
/** Configuration for file upload */
|
|
29
19
|
export interface FileUploaderConfig {
|
|
30
|
-
/** Field
|
|
20
|
+
/** Field name expected in the request */
|
|
31
21
|
key: string;
|
|
32
|
-
/**
|
|
22
|
+
/** Allowed MIME types */
|
|
33
23
|
mimeTypes?: Array<string>;
|
|
34
|
-
/**
|
|
35
|
-
path?: string;
|
|
36
|
-
/** Bucket in which the file should be stored (will be used as temporary storage when outputs are set) */
|
|
37
|
-
bucket: string;
|
|
38
|
-
/** Maximum size of the file (ex: 2MB) */
|
|
24
|
+
/** Maximum file size (e.g., "2MB") */
|
|
39
25
|
maxSize?: string;
|
|
40
|
-
/**
|
|
26
|
+
/** Whether the file is required */
|
|
41
27
|
required?: boolean;
|
|
42
|
-
/**
|
|
28
|
+
/** Whether to store without an extension */
|
|
43
29
|
noExtension?: boolean;
|
|
44
|
-
/** Specify multiple outputs with processing */
|
|
45
|
-
outputs?: Array<FileUploaderOutputs>;
|
|
46
|
-
/** @deprecated compression of images */
|
|
47
|
-
image?: CompressionOptions;
|
|
48
30
|
}
|
|
49
|
-
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fileUploader.types.js","names":[],"sources":["../../../src/middleware/fileUploader/fileUploader.types.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"fileUploader.types.js","names":[],"sources":["../../../src/middleware/fileUploader/fileUploader.types.ts"],"sourcesContent":["// Extend Express request object to include `files`\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-namespace\n namespace Express {\n interface Request {\n /** Uploaded files mapped by field name */\n files?:\n | { [key: string]: Express.Multer.File[] }\n | Express.Multer.File[]\n | undefined;\n }\n }\n}\n\n/** Custom uploaded file metadata */\nexport interface UploadedFile extends Express.Multer.File {\n /** Original file name (use `originalname` from `Express.Multer.File`) */\n originalFileName: string;\n /** File data buffer (use `buffer` from `Express.Multer.File`) */\n file: Buffer;\n}\n\n/** Configuration for file upload */\nexport interface FileUploaderConfig {\n /** Field name expected in the request */\n key: string;\n /** Allowed MIME types */\n mimeTypes?: Array<string>;\n /** Maximum file size (e.g., \"2MB\") */\n maxSize?: string;\n /** Whether the file is required */\n required?: boolean;\n /** Whether to store without an extension */\n noExtension?: boolean;\n}\n"],"mappings":"","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-server-engine",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.7",
|
|
4
4
|
"description": "Framework used to develop backend services. This package ships with a lot of features to standardize the creation of services, letting you focus on the business logic.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"@google/generative-ai": "^0.21.0",
|
|
46
46
|
"ajv": "^8.17.1",
|
|
47
47
|
"axios": "^1.7.9",
|
|
48
|
-
"backend-test-tools": "^1.0.
|
|
48
|
+
"backend-test-tools": "^1.0.7",
|
|
49
49
|
"body-parser": "^1.20.3",
|
|
50
50
|
"busboy": "^1.6.0",
|
|
51
51
|
"callsite": "^1.0.0",
|