@duvdu-v1/duvdu 1.1.203 → 1.1.204
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.d.ts +9 -0
- package/build/utils/bucket.js +203 -10
- package/package.json +1 -1
package/build/utils/bucket.d.ts
CHANGED
|
@@ -2,8 +2,17 @@
|
|
|
2
2
|
export declare class Bucket {
|
|
3
3
|
private s3;
|
|
4
4
|
private bucketName;
|
|
5
|
+
private rekognition;
|
|
5
6
|
constructor();
|
|
6
7
|
saveBucketFiles(folder: string, ...files: Express.Multer.File[]): Promise<void>;
|
|
8
|
+
private uploadSmallFile;
|
|
7
9
|
removeBucketFiles(...filePaths: string[]): Promise<void>;
|
|
8
10
|
private getContentType;
|
|
11
|
+
validateFace(imageKey: string): Promise<{
|
|
12
|
+
isValid: boolean;
|
|
13
|
+
error?: {
|
|
14
|
+
en: string;
|
|
15
|
+
ar: string;
|
|
16
|
+
};
|
|
17
|
+
}>;
|
|
9
18
|
}
|
package/build/utils/bucket.js
CHANGED
|
@@ -8,6 +8,13 @@ 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
|
+
};
|
|
11
18
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
19
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
20
|
};
|
|
@@ -24,12 +31,120 @@ class Bucket {
|
|
|
24
31
|
region: process.env.BUCKET_REGION,
|
|
25
32
|
});
|
|
26
33
|
this.bucketName = process.env.BUCKET_NAME;
|
|
34
|
+
this.rekognition = new aws_sdk_1.default.Rekognition({
|
|
35
|
+
accessKeyId: process.env.BUCKET_ACESS_KEY,
|
|
36
|
+
secretAccessKey: process.env.BUCKET_SECRET_KEY,
|
|
37
|
+
region: process.env.BUCKET_REGION,
|
|
38
|
+
});
|
|
27
39
|
}
|
|
28
40
|
saveBucketFiles(folder, ...files) {
|
|
29
41
|
return __awaiter(this, void 0, void 0, function* () {
|
|
30
|
-
|
|
31
|
-
|
|
42
|
+
const CHUNK_SIZE = 5 * 1024 * 1024; // 5MB chunks (AWS minimum)
|
|
43
|
+
yield Promise.all(files.map((file) => __awaiter(this, void 0, void 0, function* () {
|
|
44
|
+
var _a, e_1, _b, _c;
|
|
45
|
+
const filePath = path_1.default.resolve(`media/${folder}/${file.filename}`);
|
|
46
|
+
const fileSize = fs_1.default.statSync(filePath).size;
|
|
32
47
|
const contentType = this.getContentType(file.filename);
|
|
48
|
+
// Use regular upload for small files (less than 15MB)
|
|
49
|
+
if (fileSize < CHUNK_SIZE * 3) {
|
|
50
|
+
return this.uploadSmallFile(folder, file, contentType);
|
|
51
|
+
}
|
|
52
|
+
// Use multipart upload for large files
|
|
53
|
+
let multipartUpload;
|
|
54
|
+
try {
|
|
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
|
+
const fileStream = fs_1.default.createReadStream(filePath, { highWaterMark: CHUNK_SIZE });
|
|
73
|
+
let partNumber = 1;
|
|
74
|
+
let buffer = Buffer.alloc(0);
|
|
75
|
+
try {
|
|
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
|
+
_c = fileStream_1_1.value;
|
|
78
|
+
_d = false;
|
|
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++;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
107
|
+
finally {
|
|
108
|
+
try {
|
|
109
|
+
if (!_d && !_a && (_b = fileStream_1.return)) yield _b.call(fileStream_1);
|
|
110
|
+
}
|
|
111
|
+
finally { if (e_1) throw e_1.error; }
|
|
112
|
+
}
|
|
113
|
+
// Complete multipart upload
|
|
114
|
+
yield new Promise((resolve, reject) => {
|
|
115
|
+
this.s3.completeMultipartUpload({
|
|
116
|
+
Bucket: this.bucketName,
|
|
117
|
+
Key: `${folder}/${file.filename}`,
|
|
118
|
+
UploadId: uploadId,
|
|
119
|
+
MultipartUpload: { Parts: parts }
|
|
120
|
+
}, (err, data) => {
|
|
121
|
+
if (err)
|
|
122
|
+
reject(err);
|
|
123
|
+
else
|
|
124
|
+
resolve(data);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
// Attempt to abort multipart upload if it fails
|
|
130
|
+
if (multipartUpload === null || multipartUpload === void 0 ? void 0 : multipartUpload.UploadId) {
|
|
131
|
+
yield new Promise((resolve) => {
|
|
132
|
+
this.s3.abortMultipartUpload({
|
|
133
|
+
Bucket: this.bucketName,
|
|
134
|
+
Key: `${folder}/${file.filename}`,
|
|
135
|
+
UploadId: multipartUpload.UploadId // Add non-null assertion here
|
|
136
|
+
}, () => resolve(null));
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
})));
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
uploadSmallFile(folder, file, contentType) {
|
|
145
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
146
|
+
const fileStream = fs_1.default.createReadStream(path_1.default.resolve(`media/${folder}/${file.filename}`));
|
|
147
|
+
try {
|
|
33
148
|
yield new Promise((resolve, reject) => {
|
|
34
149
|
this.s3.putObject({
|
|
35
150
|
Bucket: this.bucketName,
|
|
@@ -37,27 +152,39 @@ class Bucket {
|
|
|
37
152
|
Body: fileStream,
|
|
38
153
|
ContentDisposition: 'inline',
|
|
39
154
|
ContentType: contentType,
|
|
155
|
+
ServerSideEncryption: 'AES256',
|
|
40
156
|
}, (err, data) => {
|
|
157
|
+
fileStream.destroy();
|
|
41
158
|
if (err)
|
|
42
159
|
reject(err);
|
|
43
160
|
else
|
|
44
161
|
resolve(data);
|
|
45
162
|
});
|
|
46
163
|
});
|
|
47
|
-
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
fileStream.destroy();
|
|
167
|
+
throw error;
|
|
48
168
|
}
|
|
49
169
|
});
|
|
50
170
|
}
|
|
51
171
|
removeBucketFiles(...filePaths) {
|
|
52
172
|
return __awaiter(this, void 0, void 0, function* () {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
173
|
+
const BATCH_SIZE = 1000; // AWS limit is 1000 objects per delete operation
|
|
174
|
+
for (let i = 0; i < filePaths.length; i += BATCH_SIZE) {
|
|
175
|
+
const batch = filePaths.slice(i, i + BATCH_SIZE);
|
|
176
|
+
yield new Promise((resolve, reject) => {
|
|
177
|
+
this.s3.deleteObjects({
|
|
178
|
+
Bucket: this.bucketName,
|
|
179
|
+
Delete: { Objects: batch.map((el) => ({ Key: el })) }
|
|
180
|
+
}, (err, data) => {
|
|
181
|
+
if (err)
|
|
182
|
+
reject(err);
|
|
183
|
+
else
|
|
184
|
+
resolve(data);
|
|
185
|
+
});
|
|
59
186
|
});
|
|
60
|
-
}
|
|
187
|
+
}
|
|
61
188
|
});
|
|
62
189
|
}
|
|
63
190
|
getContentType(filename) {
|
|
@@ -118,5 +245,71 @@ class Bucket {
|
|
|
118
245
|
return 'application/octet-stream'; // Fallback for unknown types
|
|
119
246
|
}
|
|
120
247
|
}
|
|
248
|
+
validateFace(imageKey) {
|
|
249
|
+
var _a, _b, _c, _d, _e, _f;
|
|
250
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
251
|
+
try {
|
|
252
|
+
const params = {
|
|
253
|
+
Image: {
|
|
254
|
+
S3Object: {
|
|
255
|
+
Bucket: this.bucketName,
|
|
256
|
+
Name: imageKey
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
Attributes: ['ALL']
|
|
260
|
+
};
|
|
261
|
+
const result = yield this.rekognition.detectFaces(params).promise();
|
|
262
|
+
// Check if exactly one face is detected
|
|
263
|
+
if (!result.FaceDetails || result.FaceDetails.length === 0) {
|
|
264
|
+
return {
|
|
265
|
+
isValid: false,
|
|
266
|
+
error: { en: 'No face detected in the image', ar: 'لا يوجد وجه مكتشف في الصورة' }
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
if (result.FaceDetails.length > 1) {
|
|
270
|
+
return {
|
|
271
|
+
isValid: false,
|
|
272
|
+
error: { en: 'Multiple faces detected in the image', ar: 'تم الكشف عن أكثر من وجه في الصورة' }
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
const face = result.FaceDetails[0];
|
|
276
|
+
// Validate if it's a real human face (confidence check)
|
|
277
|
+
if (!face.Confidence || face.Confidence < 90) {
|
|
278
|
+
return {
|
|
279
|
+
isValid: false,
|
|
280
|
+
error: { en: 'Low confidence in face detection', ar: 'الثقة في الكشف عن الوجه منخفضة' }
|
|
281
|
+
};
|
|
282
|
+
}
|
|
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;
|
|
286
|
+
if (!leftEyeOpen || !rightEyeOpen) {
|
|
287
|
+
return {
|
|
288
|
+
isValid: false,
|
|
289
|
+
error: { en: 'Eyes must be open and clearly visible', ar: 'يجب أن تكون العينين مفتوحة وواضحة بشكل مباشر' }
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
// Check face orientation (looking straight ahead)
|
|
293
|
+
const pose = face.Pose;
|
|
294
|
+
const poseThreshold = 20; // degrees
|
|
295
|
+
if (Math.abs((pose === null || pose === void 0 ? void 0 : pose.Pitch) || 0) > poseThreshold ||
|
|
296
|
+
Math.abs((pose === null || pose === void 0 ? void 0 : pose.Roll) || 0) > poseThreshold ||
|
|
297
|
+
Math.abs((pose === null || pose === void 0 ? void 0 : pose.Yaw) || 0) > poseThreshold) {
|
|
298
|
+
return {
|
|
299
|
+
isValid: false,
|
|
300
|
+
error: { en: 'Face must be looking straight ahead', ar: 'يجب أن يكون الوجه منظراً مباشراً' }
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
return { isValid: true };
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
console.error('Error validating face:', error);
|
|
307
|
+
return {
|
|
308
|
+
isValid: false,
|
|
309
|
+
error: { en: 'Error processing image', ar: 'خطأ في معالجة الصورة' }
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
}
|
|
121
314
|
}
|
|
122
315
|
exports.Bucket = Bucket;
|