@duvdu-v1/duvdu 1.1.204 → 1.1.206
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/build/utils/bucket.js +113 -98
- package/package.json +1 -1
package/build/utils/bucket.js
CHANGED
|
@@ -39,17 +39,16 @@ class Bucket {
|
|
|
39
39
|
}
|
|
40
40
|
saveBucketFiles(folder, ...files) {
|
|
41
41
|
return __awaiter(this, void 0, void 0, function* () {
|
|
42
|
-
const CHUNK_SIZE =
|
|
42
|
+
const CHUNK_SIZE = 20 * 1024 * 1024; // 20MB chunks for better performance
|
|
43
|
+
const MAX_PARALLEL_CHUNKS = 4; // Control parallel uploads
|
|
43
44
|
yield Promise.all(files.map((file) => __awaiter(this, void 0, void 0, function* () {
|
|
44
45
|
var _a, e_1, _b, _c;
|
|
45
46
|
const filePath = path_1.default.resolve(`media/${folder}/${file.filename}`);
|
|
46
47
|
const fileSize = fs_1.default.statSync(filePath).size;
|
|
47
48
|
const contentType = this.getContentType(file.filename);
|
|
48
|
-
|
|
49
|
-
if (fileSize < CHUNK_SIZE * 3) {
|
|
49
|
+
if (fileSize < CHUNK_SIZE) {
|
|
50
50
|
return this.uploadSmallFile(folder, file, contentType);
|
|
51
51
|
}
|
|
52
|
-
// Use multipart upload for large files
|
|
53
52
|
let multipartUpload;
|
|
54
53
|
try {
|
|
55
54
|
// Initiate multipart upload
|
|
@@ -69,37 +68,19 @@ class Bucket {
|
|
|
69
68
|
});
|
|
70
69
|
const uploadId = multipartUpload.UploadId;
|
|
71
70
|
const parts = [];
|
|
72
|
-
const fileStream = fs_1.default.createReadStream(filePath
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
const fileStream = fs_1.default.createReadStream(filePath);
|
|
72
|
+
// Prepare chunks for parallel upload
|
|
73
|
+
const chunks = [];
|
|
74
|
+
let currentChunk = Buffer.alloc(0);
|
|
75
75
|
try {
|
|
76
76
|
for (var _d = true, fileStream_1 = __asyncValues(fileStream), fileStream_1_1; fileStream_1_1 = yield fileStream_1.next(), _a = fileStream_1_1.done, !_a; _d = true) {
|
|
77
77
|
_c = fileStream_1_1.value;
|
|
78
78
|
_d = false;
|
|
79
79
|
const chunk = _c;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
this.s3.uploadPart({
|
|
85
|
-
Bucket: this.bucketName,
|
|
86
|
-
Key: `${folder}/${file.filename}`,
|
|
87
|
-
PartNumber: partNumber,
|
|
88
|
-
UploadId: uploadId,
|
|
89
|
-
Body: buffer,
|
|
90
|
-
}, (err, data) => {
|
|
91
|
-
if (err)
|
|
92
|
-
reject(err);
|
|
93
|
-
else
|
|
94
|
-
resolve(data);
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
parts.push({
|
|
98
|
-
PartNumber: partNumber,
|
|
99
|
-
ETag: partData.ETag
|
|
100
|
-
});
|
|
101
|
-
buffer = Buffer.alloc(0);
|
|
102
|
-
partNumber++;
|
|
80
|
+
currentChunk = Buffer.concat([currentChunk, chunk]);
|
|
81
|
+
if (currentChunk.length >= CHUNK_SIZE) {
|
|
82
|
+
chunks.push(Buffer.from(currentChunk));
|
|
83
|
+
currentChunk = Buffer.alloc(0);
|
|
103
84
|
}
|
|
104
85
|
}
|
|
105
86
|
}
|
|
@@ -110,6 +91,35 @@ class Bucket {
|
|
|
110
91
|
}
|
|
111
92
|
finally { if (e_1) throw e_1.error; }
|
|
112
93
|
}
|
|
94
|
+
if (currentChunk.length > 0) {
|
|
95
|
+
chunks.push(Buffer.from(currentChunk));
|
|
96
|
+
}
|
|
97
|
+
// Upload chunks in parallel batches
|
|
98
|
+
for (let i = 0; i < chunks.length; i += MAX_PARALLEL_CHUNKS) {
|
|
99
|
+
const batch = chunks.slice(i, i + MAX_PARALLEL_CHUNKS);
|
|
100
|
+
const partUploads = batch.map((chunkBuffer, index) => {
|
|
101
|
+
const partNumber = i + index + 1;
|
|
102
|
+
return new Promise((resolve, reject) => {
|
|
103
|
+
this.s3.uploadPart({
|
|
104
|
+
Bucket: this.bucketName,
|
|
105
|
+
Key: `${folder}/${file.filename}`,
|
|
106
|
+
PartNumber: partNumber,
|
|
107
|
+
UploadId: uploadId,
|
|
108
|
+
Body: chunkBuffer,
|
|
109
|
+
}, (err, data) => {
|
|
110
|
+
if (err)
|
|
111
|
+
reject(err);
|
|
112
|
+
else
|
|
113
|
+
resolve({
|
|
114
|
+
PartNumber: partNumber,
|
|
115
|
+
ETag: data.ETag
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
const completedParts = yield Promise.all(partUploads);
|
|
121
|
+
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); }));
|
|
122
|
+
}
|
|
113
123
|
// Complete multipart upload
|
|
114
124
|
yield new Promise((resolve, reject) => {
|
|
115
125
|
this.s3.completeMultipartUpload({
|
|
@@ -132,7 +142,7 @@ class Bucket {
|
|
|
132
142
|
this.s3.abortMultipartUpload({
|
|
133
143
|
Bucket: this.bucketName,
|
|
134
144
|
Key: `${folder}/${file.filename}`,
|
|
135
|
-
UploadId: multipartUpload.UploadId
|
|
145
|
+
UploadId: multipartUpload.UploadId
|
|
136
146
|
}, () => resolve(null));
|
|
137
147
|
});
|
|
138
148
|
}
|
|
@@ -170,7 +180,7 @@ class Bucket {
|
|
|
170
180
|
}
|
|
171
181
|
removeBucketFiles(...filePaths) {
|
|
172
182
|
return __awaiter(this, void 0, void 0, function* () {
|
|
173
|
-
const BATCH_SIZE = 1000;
|
|
183
|
+
const BATCH_SIZE = 1000;
|
|
174
184
|
for (let i = 0; i < filePaths.length; i += BATCH_SIZE) {
|
|
175
185
|
const batch = filePaths.slice(i, i + BATCH_SIZE);
|
|
176
186
|
yield new Promise((resolve, reject) => {
|
|
@@ -189,64 +199,15 @@ class Bucket {
|
|
|
189
199
|
}
|
|
190
200
|
getContentType(filename) {
|
|
191
201
|
const ext = path_1.default.extname(filename).toLowerCase();
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
case '.webm':
|
|
197
|
-
return 'video/webm';
|
|
198
|
-
case '.ogg':
|
|
199
|
-
return 'video/ogg';
|
|
200
|
-
case '.avi':
|
|
201
|
-
return 'video/x-msvideo';
|
|
202
|
-
case '.mov':
|
|
203
|
-
return 'video/quicktime';
|
|
204
|
-
case '.wmv':
|
|
205
|
-
return 'video/x-ms-wmv';
|
|
206
|
-
case '.mkv':
|
|
207
|
-
return 'video/x-matroska';
|
|
208
|
-
case '.flv':
|
|
209
|
-
return 'video/x-flv';
|
|
210
|
-
// Audio Types
|
|
211
|
-
case '.mp3':
|
|
212
|
-
return 'audio/mpeg';
|
|
213
|
-
case '.wav':
|
|
214
|
-
return 'audio/wav';
|
|
215
|
-
case '.aac':
|
|
216
|
-
return 'audio/aac';
|
|
217
|
-
case '.flac':
|
|
218
|
-
return 'audio/flac';
|
|
219
|
-
case '.m4a':
|
|
220
|
-
return 'audio/x-m4a';
|
|
221
|
-
case '.wma':
|
|
222
|
-
return 'audio/x-ms-wma';
|
|
223
|
-
// Image Types
|
|
224
|
-
case '.jpg':
|
|
225
|
-
case '.jpeg':
|
|
226
|
-
return 'image/jpeg';
|
|
227
|
-
case '.png':
|
|
228
|
-
return 'image/png';
|
|
229
|
-
case '.gif':
|
|
230
|
-
return 'image/gif';
|
|
231
|
-
case '.bmp':
|
|
232
|
-
return 'image/bmp';
|
|
233
|
-
case '.webp':
|
|
234
|
-
return 'image/webp';
|
|
235
|
-
case '.svg':
|
|
236
|
-
return 'image/svg+xml';
|
|
237
|
-
case '.tiff':
|
|
238
|
-
case '.tif':
|
|
239
|
-
return 'image/tiff';
|
|
240
|
-
// PDF and other document types
|
|
241
|
-
case '.pdf':
|
|
242
|
-
return 'application/pdf';
|
|
243
|
-
// Default fallback
|
|
244
|
-
default:
|
|
245
|
-
return 'application/octet-stream'; // Fallback for unknown types
|
|
202
|
+
for (const category of Object.values(MIME_TYPES)) {
|
|
203
|
+
if (ext in category) {
|
|
204
|
+
return category[ext];
|
|
205
|
+
}
|
|
246
206
|
}
|
|
207
|
+
return 'application/octet-stream';
|
|
247
208
|
}
|
|
248
209
|
validateFace(imageKey) {
|
|
249
|
-
var _a, _b, _c, _d, _e, _f;
|
|
210
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
250
211
|
return __awaiter(this, void 0, void 0, function* () {
|
|
251
212
|
try {
|
|
252
213
|
const params = {
|
|
@@ -259,7 +220,6 @@ class Bucket {
|
|
|
259
220
|
Attributes: ['ALL']
|
|
260
221
|
};
|
|
261
222
|
const result = yield this.rekognition.detectFaces(params).promise();
|
|
262
|
-
// Check if exactly one face is detected
|
|
263
223
|
if (!result.FaceDetails || result.FaceDetails.length === 0) {
|
|
264
224
|
return {
|
|
265
225
|
isValid: false,
|
|
@@ -273,31 +233,52 @@ class Bucket {
|
|
|
273
233
|
};
|
|
274
234
|
}
|
|
275
235
|
const face = result.FaceDetails[0];
|
|
276
|
-
|
|
277
|
-
if (!face.Confidence || face.Confidence < 90) {
|
|
236
|
+
if (!face.Confidence || face.Confidence < 95) {
|
|
278
237
|
return {
|
|
279
238
|
isValid: false,
|
|
280
|
-
error: { en: '
|
|
239
|
+
error: { en: 'Image quality is not sufficient', ar: 'جودة الصورة غير كافية' }
|
|
281
240
|
};
|
|
282
241
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
242
|
+
if (((_a = face.Sunglasses) === null || _a === void 0 ? void 0 : _a.Value) || ((_b = face.Eyeglasses) === null || _b === void 0 ? void 0 : _b.Value)) {
|
|
243
|
+
return {
|
|
244
|
+
isValid: false,
|
|
245
|
+
error: { en: 'Please remove glasses or sunglasses', ar: 'يرجى إزالة النظارات أو النظارات الشمسية' }
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
const leftEyeOpen = ((_c = face.EyesOpen) === null || _c === void 0 ? void 0 : _c.Value) && ((_e = (_d = face.EyesOpen) === null || _d === void 0 ? void 0 : _d.Confidence) !== null && _e !== void 0 ? _e : 0) > 95;
|
|
249
|
+
const rightEyeOpen = ((_f = face.EyesOpen) === null || _f === void 0 ? void 0 : _f.Value) && ((_h = (_g = face.EyesOpen) === null || _g === void 0 ? void 0 : _g.Confidence) !== null && _h !== void 0 ? _h : 0) > 95;
|
|
286
250
|
if (!leftEyeOpen || !rightEyeOpen) {
|
|
287
251
|
return {
|
|
288
252
|
isValid: false,
|
|
289
|
-
error: { en: 'Eyes must be open and clearly visible', ar: 'يجب أن تكون العينين
|
|
253
|
+
error: { en: 'Eyes must be fully open and clearly visible', ar: 'يجب أن تكون العينين مفتوحتين بالكامل وواضحتين' }
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
if ((_j = face.MouthOpen) === null || _j === void 0 ? void 0 : _j.Value) {
|
|
257
|
+
return {
|
|
258
|
+
isValid: false,
|
|
259
|
+
error: { en: 'Mouth should be closed', ar: 'يجب أن يكون الفم مغلقاً' }
|
|
290
260
|
};
|
|
291
261
|
}
|
|
292
|
-
// Check face orientation (looking straight ahead)
|
|
293
262
|
const pose = face.Pose;
|
|
294
|
-
const poseThreshold =
|
|
263
|
+
const poseThreshold = 15;
|
|
295
264
|
if (Math.abs((pose === null || pose === void 0 ? void 0 : pose.Pitch) || 0) > poseThreshold ||
|
|
296
265
|
Math.abs((pose === null || pose === void 0 ? void 0 : pose.Roll) || 0) > poseThreshold ||
|
|
297
266
|
Math.abs((pose === null || pose === void 0 ? void 0 : pose.Yaw) || 0) > poseThreshold) {
|
|
298
267
|
return {
|
|
299
268
|
isValid: false,
|
|
300
|
-
error: { en: 'Face must be
|
|
269
|
+
error: { en: 'Face must be directly facing the camera', ar: 'يجب أن يكون الوجه مواجهاً للكاميرا مباشرة' }
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
if ((_k = face.FaceOccluded) === null || _k === void 0 ? void 0 : _k.Value) {
|
|
273
|
+
return {
|
|
274
|
+
isValid: false,
|
|
275
|
+
error: { en: 'Face must be fully visible without any coverings', ar: 'يجب أن يكون الوجه مرئياً بالكامل بدون أي أغطية' }
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
if ((_l = face.Smile) === null || _l === void 0 ? void 0 : _l.Value) {
|
|
279
|
+
return {
|
|
280
|
+
isValid: false,
|
|
281
|
+
error: { en: 'Please maintain a neutral expression (no smiling)', ar: 'يرجى الحفاظ على تعبير محايد (بدون ابتسامة)' }
|
|
301
282
|
};
|
|
302
283
|
}
|
|
303
284
|
return { isValid: true };
|
|
@@ -313,3 +294,37 @@ class Bucket {
|
|
|
313
294
|
}
|
|
314
295
|
}
|
|
315
296
|
exports.Bucket = Bucket;
|
|
297
|
+
const MIME_TYPES = {
|
|
298
|
+
video: {
|
|
299
|
+
'.mp4': 'video/mp4',
|
|
300
|
+
'.webm': 'video/webm',
|
|
301
|
+
'.ogg': 'video/ogg',
|
|
302
|
+
'.avi': 'video/x-msvideo',
|
|
303
|
+
'.mov': 'video/quicktime',
|
|
304
|
+
'.wmv': 'video/x-ms-wmv',
|
|
305
|
+
'.mkv': 'video/x-matroska',
|
|
306
|
+
'.flv': 'video/x-flv'
|
|
307
|
+
},
|
|
308
|
+
audio: {
|
|
309
|
+
'.mp3': 'audio/mpeg',
|
|
310
|
+
'.wav': 'audio/wav',
|
|
311
|
+
'.aac': 'audio/aac',
|
|
312
|
+
'.flac': 'audio/flac',
|
|
313
|
+
'.m4a': 'audio/x-m4a',
|
|
314
|
+
'.wma': 'audio/x-ms-wma'
|
|
315
|
+
},
|
|
316
|
+
image: {
|
|
317
|
+
'.jpg': 'image/jpeg',
|
|
318
|
+
'.jpeg': 'image/jpeg',
|
|
319
|
+
'.png': 'image/png',
|
|
320
|
+
'.gif': 'image/gif',
|
|
321
|
+
'.bmp': 'image/bmp',
|
|
322
|
+
'.webp': 'image/webp',
|
|
323
|
+
'.svg': 'image/svg+xml',
|
|
324
|
+
'.tiff': 'image/tiff',
|
|
325
|
+
'.tif': 'image/tiff'
|
|
326
|
+
},
|
|
327
|
+
document: {
|
|
328
|
+
'.pdf': 'application/pdf'
|
|
329
|
+
}
|
|
330
|
+
};
|