@duvdu-v1/duvdu 1.1.213 → 1.1.215
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.
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
/// <reference types="qs" />
|
|
2
|
+
/// <reference types="express" />
|
|
1
3
|
import multer from 'multer';
|
|
2
|
-
interface
|
|
4
|
+
interface UploadOptions {
|
|
3
5
|
fileTypes?: string[];
|
|
4
6
|
maxSize?: number;
|
|
5
7
|
fileFilter?(req: Request, file: Express.Multer.File, callback: multer.FileFilterCallback): void;
|
|
6
8
|
}
|
|
7
|
-
export declare const globalUploadMiddleware: (folder: string, options?:
|
|
9
|
+
export declare const globalUploadMiddleware: (folder: string, options?: UploadOptions) => import("express").RequestHandler<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
|
|
8
10
|
export {};
|
|
@@ -4,32 +4,53 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.globalUploadMiddleware = void 0;
|
|
7
|
-
/* eslint-disable indent */
|
|
8
|
-
const path_1 = __importDefault(require("path"));
|
|
9
7
|
const multer_1 = __importDefault(require("multer"));
|
|
10
8
|
const uuid_1 = require("uuid");
|
|
11
9
|
const bad_request_error_1 = require("../errors/bad-request-error");
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return callback(new bad_request_error_1.BadRequestError('invalid file format'));
|
|
10
|
+
const generateUniqueFileName = (originalname) => {
|
|
11
|
+
const timestamp = Date.now();
|
|
12
|
+
const uuid = (0, uuid_1.v4)().slice(0, 8);
|
|
13
|
+
const extension = originalname.split('.').pop();
|
|
14
|
+
return `${timestamp}-${uuid}.${extension}`;
|
|
15
|
+
};
|
|
16
|
+
const globalUploadMiddleware = (folder, options) => {
|
|
17
|
+
const storage = multer_1.default.memoryStorage();
|
|
18
|
+
// Extend memory storage to include filename generation
|
|
19
|
+
const customStorage = Object.create(storage);
|
|
20
|
+
customStorage._handleFile = function (req, file, cb) {
|
|
21
|
+
// Generate unique filename before storing
|
|
22
|
+
file.filename = generateUniqueFileName(file.originalname);
|
|
23
|
+
// Call original memory storage handler
|
|
24
|
+
storage._handleFile(req, file, cb);
|
|
25
|
+
};
|
|
26
|
+
return (0, multer_1.default)({
|
|
27
|
+
storage: customStorage,
|
|
28
|
+
limits: {
|
|
29
|
+
fileSize: (options === null || options === void 0 ? void 0 : options.maxSize) || 3 * 1024 * 1024 // 3MB default
|
|
33
30
|
},
|
|
34
|
-
|
|
31
|
+
fileFilter: (options === null || options === void 0 ? void 0 : options.fileFilter)
|
|
32
|
+
? options.fileFilter
|
|
33
|
+
: function fileFilter(req, file, callback) {
|
|
34
|
+
var _a;
|
|
35
|
+
if (!(options === null || options === void 0 ? void 0 : options.fileTypes)) {
|
|
36
|
+
if (!file.mimetype.startsWith('image')) {
|
|
37
|
+
return callback(new bad_request_error_1.BadRequestError({
|
|
38
|
+
en: 'Invalid file format',
|
|
39
|
+
ar: 'صيغة الملف غير صالحة'
|
|
40
|
+
}, 'en'));
|
|
41
|
+
}
|
|
42
|
+
return callback(null, true);
|
|
43
|
+
}
|
|
44
|
+
if ((_a = options === null || options === void 0 ? void 0 : options.fileTypes) === null || _a === void 0 ? void 0 : _a.some((type) => file.mimetype.startsWith(type))) {
|
|
45
|
+
return callback(null, true);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
return callback(new bad_request_error_1.BadRequestError({
|
|
49
|
+
en: 'Invalid file format',
|
|
50
|
+
ar: 'صيغة الملف غير صالحة'
|
|
51
|
+
}, 'en'));
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
}).any();
|
|
55
|
+
};
|
|
35
56
|
exports.globalUploadMiddleware = globalUploadMiddleware;
|
package/build/utils/bucket.js
CHANGED
|
@@ -8,19 +8,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
-
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
12
|
-
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
13
|
-
var m = o[Symbol.asyncIterator], i;
|
|
14
|
-
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
15
|
-
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
16
|
-
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
17
|
-
};
|
|
18
11
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
19
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
20
13
|
};
|
|
21
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
15
|
exports.Bucket = void 0;
|
|
23
|
-
const fs_1 = __importDefault(require("fs"));
|
|
24
16
|
const path_1 = __importDefault(require("path"));
|
|
25
17
|
const aws_sdk_1 = __importDefault(require("aws-sdk"));
|
|
26
18
|
class Bucket {
|
|
@@ -39,74 +31,55 @@ class Bucket {
|
|
|
39
31
|
}
|
|
40
32
|
saveBucketFiles(folder, ...files) {
|
|
41
33
|
return __awaiter(this, void 0, void 0, function* () {
|
|
42
|
-
const CHUNK_SIZE =
|
|
43
|
-
const
|
|
34
|
+
const CHUNK_SIZE = 20 * 1024 * 1024;
|
|
35
|
+
const MAX_PARALLEL_FILES = 30;
|
|
44
36
|
const MAX_RETRIES = 3;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
// Initiate multipart upload
|
|
56
|
-
multipartUpload = yield new Promise((resolve, reject) => {
|
|
57
|
-
this.s3.createMultipartUpload({
|
|
58
|
-
Bucket: this.bucketName,
|
|
59
|
-
Key: `${folder}/${file.filename}`,
|
|
60
|
-
ContentType: contentType,
|
|
61
|
-
ContentDisposition: 'inline',
|
|
62
|
-
ServerSideEncryption: 'AES256',
|
|
63
|
-
}, (err, data) => {
|
|
64
|
-
if (err)
|
|
65
|
-
reject(err);
|
|
66
|
-
else
|
|
67
|
-
resolve(data);
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
const uploadId = multipartUpload.UploadId;
|
|
71
|
-
const parts = [];
|
|
72
|
-
// Prepare chunks for upload
|
|
73
|
-
const chunks = [];
|
|
74
|
-
let chunkIndex = 0;
|
|
75
|
-
const fileStream = fs_1.default.createReadStream(filePath, {
|
|
76
|
-
highWaterMark: CHUNK_SIZE // Optimize read buffer size
|
|
77
|
-
});
|
|
37
|
+
// Process files in parallel batches
|
|
38
|
+
for (let i = 0; i < files.length; i += MAX_PARALLEL_FILES) {
|
|
39
|
+
const fileBatch = files.slice(i, i + MAX_PARALLEL_FILES);
|
|
40
|
+
yield Promise.all(fileBatch.map((file) => __awaiter(this, void 0, void 0, function* () {
|
|
41
|
+
const contentType = this.getContentType(file.filename);
|
|
42
|
+
const fileSize = file.size;
|
|
43
|
+
if (fileSize < CHUNK_SIZE) {
|
|
44
|
+
return this.uploadSmallFile(folder, file, contentType);
|
|
45
|
+
}
|
|
46
|
+
let multipartUpload;
|
|
78
47
|
try {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
48
|
+
// Initiate multipart upload
|
|
49
|
+
multipartUpload = yield new Promise((resolve, reject) => {
|
|
50
|
+
this.s3.createMultipartUpload({
|
|
51
|
+
Bucket: this.bucketName,
|
|
52
|
+
Key: `${folder}/${file.filename}`,
|
|
53
|
+
ContentType: contentType,
|
|
54
|
+
ContentDisposition: 'inline',
|
|
55
|
+
ServerSideEncryption: 'AES256',
|
|
56
|
+
}, (err, data) => {
|
|
57
|
+
if (err)
|
|
58
|
+
reject(err);
|
|
59
|
+
else
|
|
60
|
+
resolve(data);
|
|
86
61
|
});
|
|
62
|
+
});
|
|
63
|
+
const uploadId = multipartUpload.UploadId;
|
|
64
|
+
const parts = [];
|
|
65
|
+
// Split buffer into chunks
|
|
66
|
+
const buffer = file.buffer;
|
|
67
|
+
const chunks = [];
|
|
68
|
+
for (let i = 0; i < buffer.length; i += CHUNK_SIZE) {
|
|
69
|
+
chunks.push(buffer.slice(i, i + CHUNK_SIZE));
|
|
87
70
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
finally {
|
|
91
|
-
try {
|
|
92
|
-
if (!_d && !_a && (_b = fileStream_1.return)) yield _b.call(fileStream_1);
|
|
93
|
-
}
|
|
94
|
-
finally { if (e_1) throw e_1.error; }
|
|
95
|
-
}
|
|
96
|
-
// Upload chunks in parallel batches with retry logic
|
|
97
|
-
for (let i = 0; i < chunks.length; i += MAX_PARALLEL_CHUNKS) {
|
|
98
|
-
const batch = chunks.slice(i, i + MAX_PARALLEL_CHUNKS);
|
|
99
|
-
const partUploads = batch.map(({ buffer, index }) => {
|
|
71
|
+
// Upload chunks
|
|
72
|
+
const partUploads = chunks.map((chunk, index) => {
|
|
100
73
|
const partNumber = index + 1;
|
|
101
74
|
const uploadChunkWithRetry = (retryCount = 0) => __awaiter(this, void 0, void 0, function* () {
|
|
102
75
|
try {
|
|
103
|
-
|
|
76
|
+
const uploadPromise = new Promise((resolve, reject) => {
|
|
104
77
|
this.s3.uploadPart({
|
|
105
78
|
Bucket: this.bucketName,
|
|
106
79
|
Key: `${folder}/${file.filename}`,
|
|
107
80
|
PartNumber: partNumber,
|
|
108
81
|
UploadId: uploadId,
|
|
109
|
-
Body:
|
|
82
|
+
Body: chunk,
|
|
110
83
|
}, (err, data) => {
|
|
111
84
|
if (err)
|
|
112
85
|
reject(err);
|
|
@@ -117,10 +90,13 @@ class Bucket {
|
|
|
117
90
|
});
|
|
118
91
|
});
|
|
119
92
|
});
|
|
93
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
94
|
+
setTimeout(() => reject(new Error('Upload timeout')), 30000);
|
|
95
|
+
});
|
|
96
|
+
return yield Promise.race([uploadPromise, timeoutPromise]);
|
|
120
97
|
}
|
|
121
98
|
catch (error) {
|
|
122
99
|
if (retryCount < MAX_RETRIES) {
|
|
123
|
-
// Exponential backoff
|
|
124
100
|
yield new Promise(resolve => setTimeout(resolve, Math.pow(2, retryCount) * 1000));
|
|
125
101
|
return uploadChunkWithRetry(retryCount + 1);
|
|
126
102
|
}
|
|
@@ -131,63 +107,54 @@ class Bucket {
|
|
|
131
107
|
});
|
|
132
108
|
const completedParts = yield Promise.all(partUploads);
|
|
133
109
|
parts.push(...completedParts.sort((a, b) => { var _a, _b; return ((_a = a.PartNumber) !== null && _a !== void 0 ? _a : 0) - ((_b = b.PartNumber) !== null && _b !== void 0 ? _b : 0); }));
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
this.s3.completeMultipartUpload({
|
|
138
|
-
Bucket: this.bucketName,
|
|
139
|
-
Key: `${folder}/${file.filename}`,
|
|
140
|
-
UploadId: uploadId,
|
|
141
|
-
MultipartUpload: { Parts: parts }
|
|
142
|
-
}, (err, data) => {
|
|
143
|
-
if (err)
|
|
144
|
-
reject(err);
|
|
145
|
-
else
|
|
146
|
-
resolve(data);
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
catch (error) {
|
|
151
|
-
// Attempt to abort multipart upload if it fails
|
|
152
|
-
if (multipartUpload === null || multipartUpload === void 0 ? void 0 : multipartUpload.UploadId) {
|
|
153
|
-
yield new Promise((resolve) => {
|
|
154
|
-
this.s3.abortMultipartUpload({
|
|
110
|
+
// Complete multipart upload
|
|
111
|
+
yield new Promise((resolve, reject) => {
|
|
112
|
+
this.s3.completeMultipartUpload({
|
|
155
113
|
Bucket: this.bucketName,
|
|
156
114
|
Key: `${folder}/${file.filename}`,
|
|
157
|
-
UploadId:
|
|
158
|
-
|
|
115
|
+
UploadId: uploadId,
|
|
116
|
+
MultipartUpload: { Parts: parts }
|
|
117
|
+
}, (err, data) => {
|
|
118
|
+
if (err)
|
|
119
|
+
reject(err);
|
|
120
|
+
else
|
|
121
|
+
resolve(data);
|
|
122
|
+
});
|
|
159
123
|
});
|
|
160
124
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
125
|
+
catch (error) {
|
|
126
|
+
if (multipartUpload === null || multipartUpload === void 0 ? void 0 : multipartUpload.UploadId) {
|
|
127
|
+
yield new Promise((resolve) => {
|
|
128
|
+
this.s3.abortMultipartUpload({
|
|
129
|
+
Bucket: this.bucketName,
|
|
130
|
+
Key: `${folder}/${file.filename}`,
|
|
131
|
+
UploadId: multipartUpload.UploadId
|
|
132
|
+
}, () => resolve(null));
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
})));
|
|
138
|
+
}
|
|
164
139
|
});
|
|
165
140
|
}
|
|
166
141
|
uploadSmallFile(folder, file, contentType) {
|
|
167
142
|
return __awaiter(this, void 0, void 0, function* () {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
reject(err);
|
|
182
|
-
else
|
|
183
|
-
resolve(data);
|
|
184
|
-
});
|
|
143
|
+
yield new Promise((resolve, reject) => {
|
|
144
|
+
this.s3.putObject({
|
|
145
|
+
Bucket: this.bucketName,
|
|
146
|
+
Key: `${folder}/${file.filename}`,
|
|
147
|
+
Body: file.buffer,
|
|
148
|
+
ContentDisposition: 'inline',
|
|
149
|
+
ContentType: contentType,
|
|
150
|
+
ServerSideEncryption: 'AES256',
|
|
151
|
+
}, (err, data) => {
|
|
152
|
+
if (err)
|
|
153
|
+
reject(err);
|
|
154
|
+
else
|
|
155
|
+
resolve(data);
|
|
185
156
|
});
|
|
186
|
-
}
|
|
187
|
-
catch (error) {
|
|
188
|
-
fileStream.destroy();
|
|
189
|
-
throw error;
|
|
190
|
-
}
|
|
157
|
+
});
|
|
191
158
|
});
|
|
192
159
|
}
|
|
193
160
|
removeBucketFiles(...filePaths) {
|