@duvdu-v1/duvdu 1.1.205 → 1.1.207
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/models/User.model.js +4 -1
- package/build/types/User.d.ts +1 -0
- package/build/utils/bucket.js +83 -96
- package/package.json +1 -1
|
@@ -70,7 +70,8 @@ const userSchema = new mongoose_1.Schema({
|
|
|
70
70
|
color: { type: String, default: null },
|
|
71
71
|
},
|
|
72
72
|
projectsView: { type: Number, default: 0 },
|
|
73
|
-
haveInvitation: { type: Boolean, default: false }
|
|
73
|
+
haveInvitation: { type: Boolean, default: false },
|
|
74
|
+
faceRecognition: { type: String, default: null },
|
|
74
75
|
}, {
|
|
75
76
|
timestamps: true,
|
|
76
77
|
collection: model_names_1.MODELS.user,
|
|
@@ -80,6 +81,8 @@ const userSchema = new mongoose_1.Schema({
|
|
|
80
81
|
ret.coverImage = process.env.BUCKET_HOST + '/' + ret.coverImage;
|
|
81
82
|
if (ret.profileImage)
|
|
82
83
|
ret.profileImage = process.env.BUCKET_HOST + '/' + ret.profileImage;
|
|
84
|
+
if (ret.faceRecognition)
|
|
85
|
+
ret.faceRecognition = process.env.BUCKET_HOST + '/' + ret.faceRecognition;
|
|
83
86
|
},
|
|
84
87
|
},
|
|
85
88
|
})
|
package/build/types/User.d.ts
CHANGED
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,61 +199,12 @@ 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
210
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
@@ -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,
|
|
@@ -269,25 +229,22 @@ class Bucket {
|
|
|
269
229
|
if (result.FaceDetails.length > 1) {
|
|
270
230
|
return {
|
|
271
231
|
isValid: false,
|
|
272
|
-
error: { en: 'Multiple faces detected in the image', ar: 'تم الكشف عن أكثر من وجه
|
|
232
|
+
error: { en: 'Multiple faces detected in the image', ar: 'تم الكشف عن أكثر من وجه في الصورة' }
|
|
273
233
|
};
|
|
274
234
|
}
|
|
275
235
|
const face = result.FaceDetails[0];
|
|
276
|
-
// Stricter confidence threshold (95%)
|
|
277
236
|
if (!face.Confidence || face.Confidence < 95) {
|
|
278
237
|
return {
|
|
279
238
|
isValid: false,
|
|
280
239
|
error: { en: 'Image quality is not sufficient', ar: 'جودة الصورة غير كافية' }
|
|
281
240
|
};
|
|
282
241
|
}
|
|
283
|
-
// Check for sunglasses or eyeglasses
|
|
284
242
|
if (((_a = face.Sunglasses) === null || _a === void 0 ? void 0 : _a.Value) || ((_b = face.Eyeglasses) === null || _b === void 0 ? void 0 : _b.Value)) {
|
|
285
243
|
return {
|
|
286
244
|
isValid: false,
|
|
287
245
|
error: { en: 'Please remove glasses or sunglasses', ar: 'يرجى إزالة النظارات أو النظارات الشمسية' }
|
|
288
246
|
};
|
|
289
247
|
}
|
|
290
|
-
// Check if eyes are open with higher confidence
|
|
291
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;
|
|
292
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;
|
|
293
250
|
if (!leftEyeOpen || !rightEyeOpen) {
|
|
@@ -296,16 +253,14 @@ class Bucket {
|
|
|
296
253
|
error: { en: 'Eyes must be fully open and clearly visible', ar: 'يجب أن تكون العينين مفتوحتين بالكامل وواضحتين' }
|
|
297
254
|
};
|
|
298
255
|
}
|
|
299
|
-
// Check for mouth open
|
|
300
256
|
if ((_j = face.MouthOpen) === null || _j === void 0 ? void 0 : _j.Value) {
|
|
301
257
|
return {
|
|
302
258
|
isValid: false,
|
|
303
259
|
error: { en: 'Mouth should be closed', ar: 'يجب أن يكون الفم مغلقاً' }
|
|
304
260
|
};
|
|
305
261
|
}
|
|
306
|
-
// Stricter face orientation check
|
|
307
262
|
const pose = face.Pose;
|
|
308
|
-
const poseThreshold = 15;
|
|
263
|
+
const poseThreshold = 15;
|
|
309
264
|
if (Math.abs((pose === null || pose === void 0 ? void 0 : pose.Pitch) || 0) > poseThreshold ||
|
|
310
265
|
Math.abs((pose === null || pose === void 0 ? void 0 : pose.Roll) || 0) > poseThreshold ||
|
|
311
266
|
Math.abs((pose === null || pose === void 0 ? void 0 : pose.Yaw) || 0) > poseThreshold) {
|
|
@@ -314,14 +269,12 @@ class Bucket {
|
|
|
314
269
|
error: { en: 'Face must be directly facing the camera', ar: 'يجب أن يكون الوجه مواجهاً للكاميرا مباشرة' }
|
|
315
270
|
};
|
|
316
271
|
}
|
|
317
|
-
// Check for facial occlusions
|
|
318
272
|
if ((_k = face.FaceOccluded) === null || _k === void 0 ? void 0 : _k.Value) {
|
|
319
273
|
return {
|
|
320
274
|
isValid: false,
|
|
321
275
|
error: { en: 'Face must be fully visible without any coverings', ar: 'يجب أن يكون الوجه مرئياً بالكامل بدون أي أغطية' }
|
|
322
276
|
};
|
|
323
277
|
}
|
|
324
|
-
// Check for neutral expression
|
|
325
278
|
if ((_l = face.Smile) === null || _l === void 0 ? void 0 : _l.Value) {
|
|
326
279
|
return {
|
|
327
280
|
isValid: false,
|
|
@@ -341,3 +294,37 @@ class Bucket {
|
|
|
341
294
|
}
|
|
342
295
|
}
|
|
343
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
|
+
};
|