multermate 2.2.0 → 2.3.0
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/cjs/index.js +378 -131
- package/dist/esm/index.js +382 -135
- package/dist/esm/package.json +3 -1
- package/package.json +2 -2
- package/readme.md +32 -9
- package/types/index.d.ts +22 -1
package/dist/esm/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import multer from
|
|
4
|
-
import path from
|
|
5
|
-
import { v4 as uuidv4 } from
|
|
1
|
+
import { mkdirSync } from "fs";
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import multer from "multer";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { v4 as uuidv4 } from "uuid";
|
|
6
6
|
// Custom error class for MulterMate
|
|
7
7
|
export class MultermateError extends Error {
|
|
8
8
|
constructor(message, code, field) {
|
|
9
9
|
super(message);
|
|
10
|
-
this.name =
|
|
10
|
+
this.name = "MultermateError";
|
|
11
11
|
this.code = code;
|
|
12
12
|
this.field = field;
|
|
13
13
|
// Maintains proper stack trace for where our error was thrown (only available on V8)
|
|
@@ -20,131 +20,310 @@ export class MultermateError extends Error {
|
|
|
20
20
|
const ALLOWED_MIME_TYPES = {
|
|
21
21
|
// Images (all common image formats)
|
|
22
22
|
images: [
|
|
23
|
-
"image/jpeg",
|
|
24
|
-
"image/
|
|
25
|
-
"image/
|
|
23
|
+
"image/jpeg",
|
|
24
|
+
"image/jpg",
|
|
25
|
+
"image/png",
|
|
26
|
+
"image/gif",
|
|
27
|
+
"image/webp",
|
|
28
|
+
"image/svg+xml",
|
|
29
|
+
"image/bmp",
|
|
30
|
+
"image/tiff",
|
|
31
|
+
"image/ico",
|
|
32
|
+
"image/avif",
|
|
33
|
+
"image/heic",
|
|
34
|
+
"image/heif",
|
|
35
|
+
"image/x-icon",
|
|
36
|
+
"image/vnd.microsoft.icon",
|
|
26
37
|
],
|
|
27
38
|
// Videos (all common video formats)
|
|
28
39
|
videos: [
|
|
29
|
-
"video/mp4",
|
|
30
|
-
"video/
|
|
31
|
-
"video/
|
|
40
|
+
"video/mp4",
|
|
41
|
+
"video/mpeg",
|
|
42
|
+
"video/ogg",
|
|
43
|
+
"video/webm",
|
|
44
|
+
"video/avi",
|
|
45
|
+
"video/mov",
|
|
46
|
+
"video/wmv",
|
|
47
|
+
"video/flv",
|
|
48
|
+
"video/mkv",
|
|
49
|
+
"video/m4v",
|
|
50
|
+
"video/3gp",
|
|
51
|
+
"video/quicktime",
|
|
52
|
+
"video/x-msvideo",
|
|
53
|
+
"video/x-ms-wmv",
|
|
54
|
+
"video/x-flv",
|
|
32
55
|
],
|
|
33
56
|
// Audio (all common audio formats)
|
|
34
57
|
audio: [
|
|
35
|
-
"audio/mpeg",
|
|
36
|
-
"audio/
|
|
37
|
-
"audio/
|
|
58
|
+
"audio/mpeg",
|
|
59
|
+
"audio/wav",
|
|
60
|
+
"audio/ogg",
|
|
61
|
+
"audio/aac",
|
|
62
|
+
"audio/flac",
|
|
63
|
+
"audio/m4a",
|
|
64
|
+
"audio/wma",
|
|
65
|
+
"audio/mp3",
|
|
66
|
+
"audio/webm",
|
|
67
|
+
"audio/x-wav",
|
|
68
|
+
"audio/x-m4a",
|
|
69
|
+
"audio/x-aac",
|
|
70
|
+
"audio/opus",
|
|
71
|
+
"audio/amr",
|
|
38
72
|
],
|
|
39
73
|
// Documents (all common document formats)
|
|
40
74
|
documents: [
|
|
41
|
-
"application/pdf",
|
|
75
|
+
"application/pdf",
|
|
76
|
+
"application/msword",
|
|
42
77
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
43
78
|
"application/vnd.ms-excel",
|
|
44
79
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
45
80
|
"application/vnd.ms-powerpoint",
|
|
46
81
|
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
47
|
-
"application/rtf",
|
|
48
|
-
"application/vnd.oasis.opendocument.
|
|
49
|
-
"application/vnd.
|
|
82
|
+
"application/rtf",
|
|
83
|
+
"application/vnd.oasis.opendocument.text",
|
|
84
|
+
"application/vnd.oasis.opendocument.spreadsheet",
|
|
85
|
+
"application/vnd.oasis.opendocument.presentation",
|
|
86
|
+
"application/vnd.apple.pages",
|
|
87
|
+
"application/vnd.apple.numbers",
|
|
88
|
+
"application/vnd.apple.keynote",
|
|
50
89
|
],
|
|
51
90
|
// Text files (all common text formats)
|
|
52
91
|
text: [
|
|
53
|
-
"text/plain",
|
|
54
|
-
"text/
|
|
55
|
-
"text/
|
|
56
|
-
"text/
|
|
57
|
-
"text/
|
|
58
|
-
"text/
|
|
92
|
+
"text/plain",
|
|
93
|
+
"text/csv",
|
|
94
|
+
"text/html",
|
|
95
|
+
"text/css",
|
|
96
|
+
"text/javascript",
|
|
97
|
+
"text/xml",
|
|
98
|
+
"text/markdown",
|
|
99
|
+
"text/x-python",
|
|
100
|
+
"text/x-java-source",
|
|
101
|
+
"text/x-c",
|
|
102
|
+
"text/x-c++",
|
|
103
|
+
"text/x-php",
|
|
104
|
+
"text/x-ruby",
|
|
105
|
+
"text/x-go",
|
|
106
|
+
"text/x-rust",
|
|
107
|
+
"text/x-typescript",
|
|
108
|
+
"text/x-swift",
|
|
109
|
+
"text/x-kotlin",
|
|
110
|
+
"text/x-scala",
|
|
111
|
+
"text/x-perl",
|
|
112
|
+
"text/x-shell",
|
|
113
|
+
"text/x-sh",
|
|
114
|
+
"text/x-bash",
|
|
115
|
+
"text/x-yaml",
|
|
116
|
+
"text/yaml",
|
|
117
|
+
"text/x-toml",
|
|
118
|
+
"text/x-ini",
|
|
119
|
+
"text/x-log",
|
|
59
120
|
],
|
|
60
121
|
// Archives (all common archive formats)
|
|
61
122
|
archives: [
|
|
62
|
-
"application/zip",
|
|
63
|
-
"application/
|
|
64
|
-
"application/x-
|
|
65
|
-
"application/
|
|
123
|
+
"application/zip",
|
|
124
|
+
"application/x-rar-compressed",
|
|
125
|
+
"application/x-tar",
|
|
126
|
+
"application/gzip",
|
|
127
|
+
"application/x-7z-compressed",
|
|
128
|
+
"application/x-bzip2",
|
|
129
|
+
"application/x-xz",
|
|
130
|
+
"application/x-compress",
|
|
131
|
+
"application/x-lz4",
|
|
132
|
+
"application/x-lzma",
|
|
133
|
+
"application/vnd.rar",
|
|
66
134
|
],
|
|
67
135
|
// Code files (programming language files)
|
|
68
136
|
code: [
|
|
69
|
-
"application/json",
|
|
70
|
-
"application/
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"text/x-
|
|
137
|
+
"application/json",
|
|
138
|
+
"application/xml",
|
|
139
|
+
"application/javascript",
|
|
140
|
+
"application/typescript",
|
|
141
|
+
"text/x-python",
|
|
142
|
+
"text/x-java-source",
|
|
143
|
+
"text/x-c",
|
|
144
|
+
"text/x-c++",
|
|
145
|
+
"text/x-php",
|
|
146
|
+
"text/x-ruby",
|
|
147
|
+
"text/x-go",
|
|
148
|
+
"text/x-rust",
|
|
149
|
+
"text/x-swift",
|
|
150
|
+
"text/x-kotlin",
|
|
151
|
+
"text/x-scala",
|
|
152
|
+
"text/x-csharp",
|
|
153
|
+
"text/x-vb",
|
|
154
|
+
"text/x-sql",
|
|
155
|
+
"application/sql",
|
|
74
156
|
],
|
|
75
157
|
// Spreadsheets (separate category)
|
|
76
158
|
spreadsheets: [
|
|
77
159
|
"application/vnd.ms-excel",
|
|
78
160
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
79
|
-
"text/csv",
|
|
161
|
+
"text/csv",
|
|
162
|
+
"application/csv",
|
|
80
163
|
],
|
|
81
164
|
// Presentations (separate category)
|
|
82
165
|
presentations: [
|
|
83
166
|
"application/vnd.ms-powerpoint",
|
|
84
167
|
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
85
|
-
"application/vnd.oasis.opendocument.presentation"
|
|
168
|
+
"application/vnd.oasis.opendocument.presentation",
|
|
86
169
|
],
|
|
87
170
|
// Fonts (font files)
|
|
88
171
|
fonts: [
|
|
89
|
-
"font/woff",
|
|
90
|
-
"
|
|
91
|
-
"
|
|
172
|
+
"font/woff",
|
|
173
|
+
"font/woff2",
|
|
174
|
+
"font/ttf",
|
|
175
|
+
"font/otf",
|
|
176
|
+
"font/eot",
|
|
177
|
+
"application/font-woff",
|
|
178
|
+
"application/font-woff2",
|
|
179
|
+
"application/x-font-ttf",
|
|
180
|
+
"application/x-font-otf",
|
|
181
|
+
"application/vnd.ms-fontobject",
|
|
92
182
|
],
|
|
93
183
|
// CAD files
|
|
94
184
|
cad: [
|
|
95
|
-
"application/dwg",
|
|
96
|
-
"application/
|
|
185
|
+
"application/dwg",
|
|
186
|
+
"application/dxf",
|
|
187
|
+
"model/vnd.dwf",
|
|
188
|
+
"application/acad",
|
|
189
|
+
"image/vnd.dwg",
|
|
97
190
|
],
|
|
98
191
|
// 3D models
|
|
99
192
|
models: [
|
|
100
|
-
"model/obj",
|
|
101
|
-
"model/
|
|
193
|
+
"model/obj",
|
|
194
|
+
"model/gltf+json",
|
|
195
|
+
"model/gltf-binary",
|
|
196
|
+
"model/x3d+xml",
|
|
197
|
+
"model/stl",
|
|
198
|
+
"model/ply",
|
|
199
|
+
"application/x-blender",
|
|
102
200
|
],
|
|
103
201
|
// PDFs (separate category for backward compatibility)
|
|
104
202
|
pdfs: ["application/pdf"],
|
|
105
203
|
// All allowed types - comprehensive list (this is for backward compatibility)
|
|
106
204
|
all: [
|
|
107
205
|
// Images
|
|
108
|
-
"image/jpeg",
|
|
109
|
-
"image/
|
|
110
|
-
"image/
|
|
206
|
+
"image/jpeg",
|
|
207
|
+
"image/jpg",
|
|
208
|
+
"image/png",
|
|
209
|
+
"image/gif",
|
|
210
|
+
"image/webp",
|
|
211
|
+
"image/svg+xml",
|
|
212
|
+
"image/bmp",
|
|
213
|
+
"image/tiff",
|
|
214
|
+
"image/ico",
|
|
215
|
+
"image/avif",
|
|
216
|
+
"image/heic",
|
|
217
|
+
"image/heif",
|
|
218
|
+
"image/x-icon",
|
|
219
|
+
"image/vnd.microsoft.icon",
|
|
111
220
|
// Videos
|
|
112
|
-
"video/mp4",
|
|
113
|
-
"video/
|
|
114
|
-
"video/
|
|
221
|
+
"video/mp4",
|
|
222
|
+
"video/mpeg",
|
|
223
|
+
"video/ogg",
|
|
224
|
+
"video/webm",
|
|
225
|
+
"video/avi",
|
|
226
|
+
"video/mov",
|
|
227
|
+
"video/wmv",
|
|
228
|
+
"video/flv",
|
|
229
|
+
"video/mkv",
|
|
230
|
+
"video/m4v",
|
|
231
|
+
"video/3gp",
|
|
232
|
+
"video/quicktime",
|
|
233
|
+
"video/x-msvideo",
|
|
234
|
+
"video/x-ms-wmv",
|
|
235
|
+
"video/x-flv",
|
|
115
236
|
// Audio
|
|
116
|
-
"audio/mpeg",
|
|
117
|
-
"audio/
|
|
118
|
-
"audio/
|
|
237
|
+
"audio/mpeg",
|
|
238
|
+
"audio/wav",
|
|
239
|
+
"audio/ogg",
|
|
240
|
+
"audio/aac",
|
|
241
|
+
"audio/flac",
|
|
242
|
+
"audio/m4a",
|
|
243
|
+
"audio/wma",
|
|
244
|
+
"audio/mp3",
|
|
245
|
+
"audio/webm",
|
|
246
|
+
"audio/x-wav",
|
|
247
|
+
"audio/x-m4a",
|
|
248
|
+
"audio/x-aac",
|
|
249
|
+
"audio/opus",
|
|
250
|
+
"audio/amr",
|
|
119
251
|
// Documents
|
|
120
|
-
"application/pdf",
|
|
252
|
+
"application/pdf",
|
|
253
|
+
"application/msword",
|
|
121
254
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
122
255
|
"application/vnd.ms-excel",
|
|
123
256
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
124
257
|
"application/vnd.ms-powerpoint",
|
|
125
258
|
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
126
|
-
"application/rtf",
|
|
127
|
-
"application/vnd.oasis.opendocument.
|
|
128
|
-
"application/vnd.
|
|
259
|
+
"application/rtf",
|
|
260
|
+
"application/vnd.oasis.opendocument.text",
|
|
261
|
+
"application/vnd.oasis.opendocument.spreadsheet",
|
|
262
|
+
"application/vnd.oasis.opendocument.presentation",
|
|
263
|
+
"application/vnd.apple.pages",
|
|
264
|
+
"application/vnd.apple.numbers",
|
|
265
|
+
"application/vnd.apple.keynote",
|
|
129
266
|
// Text files
|
|
130
|
-
"text/plain",
|
|
131
|
-
"text/
|
|
132
|
-
"text/
|
|
133
|
-
"text/
|
|
134
|
-
"text/
|
|
135
|
-
"text/
|
|
267
|
+
"text/plain",
|
|
268
|
+
"text/csv",
|
|
269
|
+
"text/html",
|
|
270
|
+
"text/css",
|
|
271
|
+
"text/javascript",
|
|
272
|
+
"text/xml",
|
|
273
|
+
"text/markdown",
|
|
274
|
+
"text/x-python",
|
|
275
|
+
"text/x-java-source",
|
|
276
|
+
"text/x-c",
|
|
277
|
+
"text/x-c++",
|
|
278
|
+
"text/x-php",
|
|
279
|
+
"text/x-ruby",
|
|
280
|
+
"text/x-go",
|
|
281
|
+
"text/x-rust",
|
|
282
|
+
"text/x-typescript",
|
|
283
|
+
"text/x-swift",
|
|
284
|
+
"text/x-kotlin",
|
|
285
|
+
"text/x-scala",
|
|
286
|
+
"text/x-perl",
|
|
287
|
+
"text/x-shell",
|
|
288
|
+
"text/x-sh",
|
|
289
|
+
"text/x-bash",
|
|
290
|
+
"text/x-yaml",
|
|
291
|
+
"text/yaml",
|
|
292
|
+
"text/x-toml",
|
|
293
|
+
"text/x-ini",
|
|
294
|
+
"text/x-log",
|
|
136
295
|
// Archives
|
|
137
|
-
"application/zip",
|
|
138
|
-
"application/
|
|
139
|
-
"application/x-
|
|
140
|
-
"application/
|
|
296
|
+
"application/zip",
|
|
297
|
+
"application/x-rar-compressed",
|
|
298
|
+
"application/x-tar",
|
|
299
|
+
"application/gzip",
|
|
300
|
+
"application/x-7z-compressed",
|
|
301
|
+
"application/x-bzip2",
|
|
302
|
+
"application/x-xz",
|
|
303
|
+
"application/x-compress",
|
|
304
|
+
"application/x-lz4",
|
|
305
|
+
"application/x-lzma",
|
|
306
|
+
"application/vnd.rar",
|
|
141
307
|
// Code/Data
|
|
142
|
-
"application/json",
|
|
143
|
-
"application/
|
|
308
|
+
"application/json",
|
|
309
|
+
"application/xml",
|
|
310
|
+
"application/javascript",
|
|
311
|
+
"application/typescript",
|
|
312
|
+
"text/x-csharp",
|
|
313
|
+
"text/x-vb",
|
|
314
|
+
"text/x-sql",
|
|
315
|
+
"application/sql",
|
|
144
316
|
// Fonts
|
|
145
|
-
"font/woff",
|
|
146
|
-
"
|
|
147
|
-
"
|
|
317
|
+
"font/woff",
|
|
318
|
+
"font/woff2",
|
|
319
|
+
"font/ttf",
|
|
320
|
+
"font/otf",
|
|
321
|
+
"font/eot",
|
|
322
|
+
"application/font-woff",
|
|
323
|
+
"application/font-woff2",
|
|
324
|
+
"application/x-font-ttf",
|
|
325
|
+
"application/x-font-otf",
|
|
326
|
+
"application/vnd.ms-fontobject",
|
|
148
327
|
],
|
|
149
328
|
};
|
|
150
329
|
/**
|
|
@@ -164,11 +343,11 @@ const configureStorage = (destination) => {
|
|
|
164
343
|
}
|
|
165
344
|
catch (error) {
|
|
166
345
|
// Directory might already exist, that's okay
|
|
167
|
-
if (error.code ===
|
|
346
|
+
if (error.code === "EEXIST") {
|
|
168
347
|
cb(null, dir);
|
|
169
348
|
}
|
|
170
349
|
else {
|
|
171
|
-
cb(new MultermateError(`Failed to create destination directory: ${dir}. Error: ${error.message}`,
|
|
350
|
+
cb(new MultermateError(`Failed to create destination directory: ${dir}. Error: ${error.message}`, "DESTINATION_ERROR"), "");
|
|
172
351
|
}
|
|
173
352
|
}
|
|
174
353
|
},
|
|
@@ -184,26 +363,93 @@ const configureStorage = (destination) => {
|
|
|
184
363
|
cb(null, fileName);
|
|
185
364
|
}
|
|
186
365
|
catch (error) {
|
|
187
|
-
cb(new MultermateError(
|
|
366
|
+
cb(new MultermateError("Failed to generate filename", "FILENAME_ERROR"), "");
|
|
188
367
|
}
|
|
189
368
|
},
|
|
190
369
|
});
|
|
191
370
|
};
|
|
192
|
-
const normalizePathForDb = (value) => value.replace(/\\/g,
|
|
371
|
+
const normalizePathForDb = (value) => value.replace(/\\/g, "/");
|
|
372
|
+
const FILE_KIND_TO_CATEGORIES = {
|
|
373
|
+
image: ["images"],
|
|
374
|
+
video: ["videos"],
|
|
375
|
+
audio: ["audio"],
|
|
376
|
+
document: ["documents"],
|
|
377
|
+
text: ["text"],
|
|
378
|
+
archive: ["archives"],
|
|
379
|
+
code: ["code"],
|
|
380
|
+
spreadsheet: ["spreadsheets"],
|
|
381
|
+
presentation: ["presentations"],
|
|
382
|
+
font: ["fonts"],
|
|
383
|
+
cad: ["cad"],
|
|
384
|
+
model: ["models"],
|
|
385
|
+
mix: ["images", "videos", "audio", "documents", "text", "archives"],
|
|
386
|
+
any: ["all"],
|
|
387
|
+
};
|
|
388
|
+
const FILE_TYPE_ALIASES = {
|
|
389
|
+
image: ["images"],
|
|
390
|
+
images: ["images"],
|
|
391
|
+
video: ["videos"],
|
|
392
|
+
videos: ["videos"],
|
|
393
|
+
document: ["documents"],
|
|
394
|
+
documents: ["documents"],
|
|
395
|
+
archive: ["archives"],
|
|
396
|
+
archives: ["archives"],
|
|
397
|
+
spreadsheet: ["spreadsheets"],
|
|
398
|
+
spreadsheets: ["spreadsheets"],
|
|
399
|
+
presentation: ["presentations"],
|
|
400
|
+
presentations: ["presentations"],
|
|
401
|
+
font: ["fonts"],
|
|
402
|
+
fonts: ["fonts"],
|
|
403
|
+
model: ["models"],
|
|
404
|
+
models: ["models"],
|
|
405
|
+
mixed: ["images", "videos", "audio", "documents", "text", "archives"],
|
|
406
|
+
mix: ["images", "videos", "audio", "documents", "text", "archives"],
|
|
407
|
+
any: ["all"],
|
|
408
|
+
};
|
|
409
|
+
const resolveAllowedMimeTypes = (fileTypes = [], fileKinds = []) => {
|
|
410
|
+
const selectedCategories = new Set();
|
|
411
|
+
const invalidSelectors = [];
|
|
412
|
+
fileKinds.forEach((kind) => {
|
|
413
|
+
const categories = FILE_KIND_TO_CATEGORIES[kind];
|
|
414
|
+
categories.forEach((category) => selectedCategories.add(category));
|
|
415
|
+
});
|
|
416
|
+
fileTypes.forEach((rawType) => {
|
|
417
|
+
const normalized = String(rawType).toLowerCase();
|
|
418
|
+
const aliasCategories = FILE_TYPE_ALIASES[normalized];
|
|
419
|
+
if (aliasCategories) {
|
|
420
|
+
aliasCategories.forEach((category) => selectedCategories.add(category));
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
if (ALLOWED_MIME_TYPES[normalized]) {
|
|
424
|
+
selectedCategories.add(normalized);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
invalidSelectors.push(rawType);
|
|
428
|
+
});
|
|
429
|
+
let allowedMimeTypes = [];
|
|
430
|
+
selectedCategories.forEach((category) => {
|
|
431
|
+
allowedMimeTypes = allowedMimeTypes.concat(ALLOWED_MIME_TYPES[category] || []);
|
|
432
|
+
});
|
|
433
|
+
return {
|
|
434
|
+
allowedMimeTypes: [...new Set(allowedMimeTypes)],
|
|
435
|
+
invalidSelectors,
|
|
436
|
+
};
|
|
437
|
+
};
|
|
193
438
|
const getPhysicalDestination = (destination, absoluteDestination) => {
|
|
194
439
|
if (!absoluteDestination) {
|
|
195
|
-
return destination ||
|
|
440
|
+
return destination || "uploads";
|
|
196
441
|
}
|
|
197
442
|
if (!path.isAbsolute(absoluteDestination)) {
|
|
198
|
-
throw new MultermateError(`absoluteDestination must be an absolute path. Received: ${absoluteDestination}`,
|
|
443
|
+
throw new MultermateError(`absoluteDestination must be an absolute path. Received: ${absoluteDestination}`, "INVALID_ABSOLUTE_DESTINATION");
|
|
199
444
|
}
|
|
200
|
-
|
|
445
|
+
const destinationSegment = (destination || "uploads").replace(/^[\\/]+/, "");
|
|
446
|
+
return path.join(absoluteDestination, destinationSegment);
|
|
201
447
|
};
|
|
202
448
|
const sanitizeStoredPathsForDb = (req, destination, absoluteDestination) => {
|
|
203
449
|
if (!absoluteDestination) {
|
|
204
450
|
return;
|
|
205
451
|
}
|
|
206
|
-
const dbDestination = normalizePathForDb(destination ||
|
|
452
|
+
const dbDestination = normalizePathForDb(destination || "uploads");
|
|
207
453
|
if (req.file) {
|
|
208
454
|
req.file.destination = dbDestination;
|
|
209
455
|
req.file.path = normalizePathForDb(path.join(dbDestination, req.file.filename));
|
|
@@ -236,12 +482,12 @@ const configureFileFilter = (allowedMimeTypes) => {
|
|
|
236
482
|
cb(null, true);
|
|
237
483
|
}
|
|
238
484
|
else {
|
|
239
|
-
const error = new MultermateError(`Invalid file type: ${file.mimetype}. Allowed types: ${allowedMimeTypes.join(
|
|
485
|
+
const error = new MultermateError(`Invalid file type: ${file.mimetype}. Allowed types: ${allowedMimeTypes.join(", ")}`, "INVALID_FILE_TYPE", file.fieldname);
|
|
240
486
|
cb(error);
|
|
241
487
|
}
|
|
242
488
|
}
|
|
243
489
|
catch (error) {
|
|
244
|
-
cb(new MultermateError(
|
|
490
|
+
cb(new MultermateError("File filter error", "FILTER_ERROR"));
|
|
245
491
|
}
|
|
246
492
|
};
|
|
247
493
|
};
|
|
@@ -251,7 +497,7 @@ const configureFileFilter = (allowedMimeTypes) => {
|
|
|
251
497
|
* @param options - Configuration options for Multer.
|
|
252
498
|
* @returns Multer instance configured with the provided options.
|
|
253
499
|
*/
|
|
254
|
-
const configureMulter = ({ destination, absoluteDestination, filename, fileTypes = [], customMimeTypes = [], fileSizeLimit, preservePath = false, }) => {
|
|
500
|
+
const configureMulter = ({ destination, absoluteDestination, filename, fileKinds = [], fileTypes = [], customMimeTypes = [], fileSizeLimit, preservePath = false, }) => {
|
|
255
501
|
try {
|
|
256
502
|
const storage = configureStorage(getPhysicalDestination(destination, absoluteDestination));
|
|
257
503
|
// Combine allowed MIME types based on fileTypes array
|
|
@@ -260,13 +506,13 @@ const configureMulter = ({ destination, absoluteDestination, filename, fileTypes
|
|
|
260
506
|
// Use custom MIME types if provided
|
|
261
507
|
allowedMimeTypes = customMimeTypes;
|
|
262
508
|
}
|
|
263
|
-
else if (fileTypes.length > 0) {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
509
|
+
else if (fileTypes.length > 0 || fileKinds.length > 0) {
|
|
510
|
+
const { allowedMimeTypes: resolvedMimeTypes, invalidSelectors } = resolveAllowedMimeTypes(fileTypes, fileKinds);
|
|
511
|
+
if (invalidSelectors.length > 0) {
|
|
512
|
+
throw new MultermateError(`Unknown file type selectors: ${invalidSelectors.join(", ")}. ` +
|
|
513
|
+
`Use supported categories from ALLOWED_FILE_TYPES or fileKinds (image, document, video, mix, etc.).`, "INVALID_FILE_TYPE_SELECTOR");
|
|
514
|
+
}
|
|
515
|
+
allowedMimeTypes = resolvedMimeTypes;
|
|
270
516
|
}
|
|
271
517
|
// If neither customMimeTypes nor fileTypes are provided, allowedMimeTypes remains empty
|
|
272
518
|
// This means ALL file types are allowed (no restrictions)
|
|
@@ -281,7 +527,7 @@ const configureMulter = ({ destination, absoluteDestination, filename, fileTypes
|
|
|
281
527
|
});
|
|
282
528
|
}
|
|
283
529
|
catch (error) {
|
|
284
|
-
throw new MultermateError(
|
|
530
|
+
throw new MultermateError("Failed to configure multer", "CONFIGURATION_ERROR");
|
|
285
531
|
}
|
|
286
532
|
};
|
|
287
533
|
/**
|
|
@@ -305,27 +551,28 @@ export function uploadSingle(options = {}) {
|
|
|
305
551
|
}
|
|
306
552
|
middleware(req, res, (err) => {
|
|
307
553
|
if (err) {
|
|
308
|
-
let errorMessage =
|
|
309
|
-
let errorCode =
|
|
554
|
+
let errorMessage = "Unknown upload error";
|
|
555
|
+
let errorCode = "UPLOAD_ERROR";
|
|
310
556
|
if (err instanceof MultermateError) {
|
|
311
557
|
// Our custom error
|
|
312
558
|
req.fileValidationError = err.message;
|
|
313
559
|
return next(err);
|
|
314
560
|
}
|
|
315
|
-
else if (err.code ===
|
|
316
|
-
errorMessage = `File size limit exceeded. Maximum allowed size: ${options.fileSizeLimit ||
|
|
317
|
-
errorCode =
|
|
561
|
+
else if (err.code === "LIMIT_FILE_SIZE") {
|
|
562
|
+
errorMessage = `File size limit exceeded. Maximum allowed size: ${options.fileSizeLimit || "50MB"}`;
|
|
563
|
+
errorCode = "FILE_SIZE_LIMIT_EXCEEDED";
|
|
318
564
|
}
|
|
319
|
-
else if (err.code ===
|
|
320
|
-
errorMessage =
|
|
321
|
-
|
|
565
|
+
else if (err.code === "INVALID_FILE_TYPE") {
|
|
566
|
+
errorMessage =
|
|
567
|
+
"Invalid file type. Please check allowed file types.";
|
|
568
|
+
errorCode = "INVALID_FILE_TYPE";
|
|
322
569
|
}
|
|
323
|
-
else if (err.code ===
|
|
324
|
-
errorMessage =
|
|
325
|
-
errorCode =
|
|
570
|
+
else if (err.code === "LIMIT_UNEXPECTED_FILE") {
|
|
571
|
+
errorMessage = "Unexpected field";
|
|
572
|
+
errorCode = "UNEXPECTED_FIELD";
|
|
326
573
|
}
|
|
327
574
|
else {
|
|
328
|
-
errorMessage = err.message ||
|
|
575
|
+
errorMessage = err.message || "Upload failed";
|
|
329
576
|
}
|
|
330
577
|
const multermateError = new MultermateError(errorMessage, errorCode);
|
|
331
578
|
req.fileValidationError = errorMessage;
|
|
@@ -337,7 +584,7 @@ export function uploadSingle(options = {}) {
|
|
|
337
584
|
};
|
|
338
585
|
}
|
|
339
586
|
catch (error) {
|
|
340
|
-
throw new MultermateError(
|
|
587
|
+
throw new MultermateError("Failed to create upload middleware", "MIDDLEWARE_CREATION_ERROR");
|
|
341
588
|
}
|
|
342
589
|
}
|
|
343
590
|
/**
|
|
@@ -350,7 +597,7 @@ export function uploadMultiple(options) {
|
|
|
350
597
|
try {
|
|
351
598
|
const storageDestination = getPhysicalDestination(options.destination, options.absoluteDestination);
|
|
352
599
|
// Map fields configuration to multer format
|
|
353
|
-
const fieldConfigs = options.fields.map(field => ({
|
|
600
|
+
const fieldConfigs = options.fields.map((field) => ({
|
|
354
601
|
name: field.name,
|
|
355
602
|
maxCount: field.maxCount || 10, // Default maxCount is 10 if not specified.
|
|
356
603
|
}));
|
|
@@ -363,12 +610,11 @@ export function uploadMultiple(options) {
|
|
|
363
610
|
else {
|
|
364
611
|
// Collect file types from individual fields
|
|
365
612
|
options.fields.forEach((field) => {
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
});
|
|
613
|
+
const { allowedMimeTypes, invalidSelectors } = resolveAllowedMimeTypes(field.fileTypes || [], field.fileKinds || []);
|
|
614
|
+
if (invalidSelectors.length > 0) {
|
|
615
|
+
throw new MultermateError(`Unknown file type selectors in field "${field.name}": ${invalidSelectors.join(", ")}`, "INVALID_FILE_TYPE_SELECTOR", field.name);
|
|
616
|
+
}
|
|
617
|
+
allowedFileTypes = allowedFileTypes.concat(allowedMimeTypes);
|
|
372
618
|
});
|
|
373
619
|
}
|
|
374
620
|
const multerConfig = {
|
|
@@ -377,7 +623,7 @@ export function uploadMultiple(options) {
|
|
|
377
623
|
fileTypes: [],
|
|
378
624
|
customMimeTypes: allowedFileTypes.length > 0 ? allowedFileTypes : [],
|
|
379
625
|
fileSizeLimit: options.fileSizeLimit,
|
|
380
|
-
preservePath: options.preservePath
|
|
626
|
+
preservePath: options.preservePath,
|
|
381
627
|
};
|
|
382
628
|
const multerInstance = configureMulter(multerConfig);
|
|
383
629
|
const middleware = multerInstance.fields(fieldConfigs);
|
|
@@ -391,31 +637,32 @@ export function uploadMultiple(options) {
|
|
|
391
637
|
}
|
|
392
638
|
middleware(req, res, (err) => {
|
|
393
639
|
if (err) {
|
|
394
|
-
let errorMessage =
|
|
395
|
-
let errorCode =
|
|
640
|
+
let errorMessage = "Unknown upload error";
|
|
641
|
+
let errorCode = "UPLOAD_ERROR";
|
|
396
642
|
if (err instanceof MultermateError) {
|
|
397
643
|
// Our custom error
|
|
398
644
|
req.fileValidationError = err.message;
|
|
399
645
|
return next(err);
|
|
400
646
|
}
|
|
401
|
-
else if (err.code ===
|
|
402
|
-
errorMessage = `File size limit exceeded. Maximum allowed size: ${options.fileSizeLimit ||
|
|
403
|
-
errorCode =
|
|
647
|
+
else if (err.code === "LIMIT_FILE_SIZE") {
|
|
648
|
+
errorMessage = `File size limit exceeded. Maximum allowed size: ${options.fileSizeLimit || "50MB"}`;
|
|
649
|
+
errorCode = "FILE_SIZE_LIMIT_EXCEEDED";
|
|
404
650
|
}
|
|
405
|
-
else if (err.code ===
|
|
406
|
-
errorMessage =
|
|
407
|
-
|
|
651
|
+
else if (err.code === "INVALID_FILE_TYPE") {
|
|
652
|
+
errorMessage =
|
|
653
|
+
"Invalid file type. Please check allowed file types.";
|
|
654
|
+
errorCode = "INVALID_FILE_TYPE";
|
|
408
655
|
}
|
|
409
|
-
else if (err.code ===
|
|
410
|
-
errorMessage =
|
|
411
|
-
errorCode =
|
|
656
|
+
else if (err.code === "LIMIT_UNEXPECTED_FILE") {
|
|
657
|
+
errorMessage = "Unexpected field";
|
|
658
|
+
errorCode = "UNEXPECTED_FIELD";
|
|
412
659
|
}
|
|
413
|
-
else if (err.code ===
|
|
414
|
-
errorMessage =
|
|
415
|
-
errorCode =
|
|
660
|
+
else if (err.code === "LIMIT_FILE_COUNT") {
|
|
661
|
+
errorMessage = "Too many files";
|
|
662
|
+
errorCode = "FILE_COUNT_LIMIT_EXCEEDED";
|
|
416
663
|
}
|
|
417
664
|
else {
|
|
418
|
-
errorMessage = err.message ||
|
|
665
|
+
errorMessage = err.message || "Upload failed";
|
|
419
666
|
}
|
|
420
667
|
const multermateError = new MultermateError(errorMessage, errorCode);
|
|
421
668
|
req.fileValidationError = errorMessage;
|
|
@@ -427,7 +674,7 @@ export function uploadMultiple(options) {
|
|
|
427
674
|
};
|
|
428
675
|
}
|
|
429
676
|
catch (error) {
|
|
430
|
-
throw new MultermateError(
|
|
677
|
+
throw new MultermateError("Failed to create multiple upload middleware", "MIDDLEWARE_CREATION_ERROR");
|
|
431
678
|
}
|
|
432
679
|
}
|
|
433
680
|
/**
|
|
@@ -438,8 +685,8 @@ export function uploadMultiple(options) {
|
|
|
438
685
|
*/
|
|
439
686
|
export async function deleteFile(filePath) {
|
|
440
687
|
try {
|
|
441
|
-
if (!filePath || typeof filePath !==
|
|
442
|
-
throw new MultermateError(
|
|
688
|
+
if (!filePath || typeof filePath !== "string") {
|
|
689
|
+
throw new MultermateError("Invalid file path provided", "INVALID_PATH");
|
|
443
690
|
}
|
|
444
691
|
await fs.unlink(filePath);
|
|
445
692
|
return true;
|
|
@@ -448,14 +695,14 @@ export async function deleteFile(filePath) {
|
|
|
448
695
|
if (error instanceof MultermateError) {
|
|
449
696
|
throw error;
|
|
450
697
|
}
|
|
451
|
-
if (error.code ===
|
|
452
|
-
throw new MultermateError(`File not found: ${filePath}`,
|
|
698
|
+
if (error.code === "ENOENT") {
|
|
699
|
+
throw new MultermateError(`File not found: ${filePath}`, "FILE_NOT_FOUND");
|
|
453
700
|
}
|
|
454
|
-
else if (error.code ===
|
|
455
|
-
throw new MultermateError(`Permission denied: ${filePath}`,
|
|
701
|
+
else if (error.code === "EACCES") {
|
|
702
|
+
throw new MultermateError(`Permission denied: ${filePath}`, "PERMISSION_DENIED");
|
|
456
703
|
}
|
|
457
704
|
else {
|
|
458
|
-
throw new MultermateError(`Failed to delete file: ${error.message}`,
|
|
705
|
+
throw new MultermateError(`Failed to delete file: ${error.message}`, "DELETE_ERROR");
|
|
459
706
|
}
|
|
460
707
|
}
|
|
461
708
|
}
|
|
@@ -470,5 +717,5 @@ export default {
|
|
|
470
717
|
deleteFile,
|
|
471
718
|
MultermateError,
|
|
472
719
|
ALLOWED_FILE_TYPES,
|
|
473
|
-
MIME_TYPES
|
|
720
|
+
MIME_TYPES,
|
|
474
721
|
};
|