@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.
Files changed (2) hide show
  1. package/build/utils/bucket.js +113 -98
  2. package/package.json +1 -1
@@ -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 = 5 * 1024 * 1024; // 5MB chunks (AWS minimum)
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
- // Use regular upload for small files (less than 15MB)
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, { highWaterMark: CHUNK_SIZE });
73
- let partNumber = 1;
74
- let buffer = Buffer.alloc(0);
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
- buffer = Buffer.concat([buffer, chunk]);
81
- // Upload when buffer reaches CHUNK_SIZE or it's the last chunk
82
- if (buffer.length >= CHUNK_SIZE || partNumber * CHUNK_SIZE >= fileSize) {
83
- const partData = yield new Promise((resolve, reject) => {
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 // Add non-null assertion here
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; // AWS limit is 1000 objects per delete operation
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
- switch (ext) {
193
- // Video Types
194
- case '.mp4':
195
- return 'video/mp4';
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
- // Validate if it's a real human face (confidence check)
277
- if (!face.Confidence || face.Confidence < 90) {
236
+ if (!face.Confidence || face.Confidence < 95) {
278
237
  return {
279
238
  isValid: false,
280
- error: { en: 'Low confidence in face detection', ar: 'الثقة في الكشف عن الوجه منخفضة' }
239
+ error: { en: 'Image quality is not sufficient', ar: 'جودة الصورة غير كافية' }
281
240
  };
282
241
  }
283
- // Check if eyes are open
284
- const leftEyeOpen = ((_a = face.EyesOpen) === null || _a === void 0 ? void 0 : _a.Value) && ((_c = (_b = face.EyesOpen) === null || _b === void 0 ? void 0 : _b.Confidence) !== null && _c !== void 0 ? _c : 0) > 90;
285
- const rightEyeOpen = ((_d = face.EyesOpen) === null || _d === void 0 ? void 0 : _d.Value) && ((_f = (_e = face.EyesOpen) === null || _e === void 0 ? void 0 : _e.Confidence) !== null && _f !== void 0 ? _f : 0) > 90;
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 = 20; // degrees
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 looking straight ahead', ar: 'يجب أن يكون الوجه منظراً مباشراً' }
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
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duvdu-v1/duvdu",
3
- "version": "1.1.204",
3
+ "version": "1.1.206",
4
4
  "main": "./build/index.js",
5
5
  "types": "./build/index.d.ts",
6
6
  "files": [