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