@duvdu-v1/duvdu 1.1.213 → 1.1.214
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,8 @@
|
|
|
1
1
|
import multer from 'multer';
|
|
2
|
-
interface
|
|
2
|
+
interface UploadOptions {
|
|
3
3
|
fileTypes?: string[];
|
|
4
4
|
maxSize?: number;
|
|
5
5
|
fileFilter?(req: Request, file: Express.Multer.File, callback: multer.FileFilterCallback): void;
|
|
6
6
|
}
|
|
7
|
-
export declare const globalUploadMiddleware: (folder: string, options?:
|
|
7
|
+
export declare const globalUploadMiddleware: (folder: string, options?: UploadOptions) => multer.Multer;
|
|
8
8
|
export {};
|
|
@@ -1,21 +1,45 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
// /* eslint-disable indent */
|
|
3
|
+
// import path from 'path';
|
|
2
4
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
5
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
6
|
};
|
|
5
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
8
|
exports.globalUploadMiddleware = void 0;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
// import multer from 'multer';
|
|
10
|
+
// import { v4 } from 'uuid';
|
|
11
|
+
// import { BadRequestError } from '../errors/bad-request-error';
|
|
12
|
+
// interface uploadOptions {
|
|
13
|
+
// fileTypes?: string[];
|
|
14
|
+
// maxSize?: number;
|
|
15
|
+
// fileFilter?(req: Request, file: Express.Multer.File, callback: multer.FileFilterCallback): void;
|
|
16
|
+
// }
|
|
17
|
+
// export const globalUploadMiddleware = (folder: string, options?: uploadOptions) =>
|
|
18
|
+
// multer({
|
|
19
|
+
// storage: multer.diskStorage({
|
|
20
|
+
// destination: path.resolve(`media/${folder}`),
|
|
21
|
+
// filename(req, file, callback) {
|
|
22
|
+
// callback(null, `${v4()}.${file.originalname.split('.').at(-1)}`);
|
|
23
|
+
// },
|
|
24
|
+
// }),
|
|
25
|
+
// limits: { fileSize: options?.maxSize || 3 * 1024 * 1024 }, // 3MB
|
|
26
|
+
// fileFilter: options?.fileFilter
|
|
27
|
+
// ? (options.fileFilter as any)
|
|
28
|
+
// : function fileFilter(req, file, callback) {
|
|
29
|
+
// if (!options?.fileTypes) {
|
|
30
|
+
// if (!file.mimetype.startsWith('image'))
|
|
31
|
+
// return callback(new BadRequestError('invalid file format'));
|
|
32
|
+
// return callback(null, true);
|
|
33
|
+
// }
|
|
34
|
+
// if (options?.fileTypes?.some((type) => file.mimetype.startsWith(type)))
|
|
35
|
+
// return callback(null, true);
|
|
36
|
+
// else return callback(new BadRequestError('invalid file format'));
|
|
37
|
+
// },
|
|
38
|
+
// });
|
|
9
39
|
const multer_1 = __importDefault(require("multer"));
|
|
10
|
-
const uuid_1 = require("uuid");
|
|
11
40
|
const bad_request_error_1 = require("../errors/bad-request-error");
|
|
12
41
|
const globalUploadMiddleware = (folder, options) => (0, multer_1.default)({
|
|
13
|
-
storage: multer_1.default.
|
|
14
|
-
destination: path_1.default.resolve(`media/${folder}`),
|
|
15
|
-
filename(req, file, callback) {
|
|
16
|
-
callback(null, `${(0, uuid_1.v4)()}.${file.originalname.split('.').at(-1)}`);
|
|
17
|
-
},
|
|
18
|
-
}),
|
|
42
|
+
storage: multer_1.default.memoryStorage(),
|
|
19
43
|
limits: { fileSize: (options === null || options === void 0 ? void 0 : options.maxSize) || 3 * 1024 * 1024 }, // 3MB
|
|
20
44
|
fileFilter: (options === null || options === void 0 ? void 0 : options.fileFilter)
|
|
21
45
|
? options.fileFilter
|
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) {
|