@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.
@@ -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
  }
@@ -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
- for (const file of files) {
31
- const fileStream = fs_1.default.createReadStream(path_1.default.resolve(`media/${folder}/${file.filename}`));
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
- fileStream.close();
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
- yield new Promise((resolve, reject) => {
54
- this.s3.deleteObjects({ Bucket: this.bucketName, Delete: { Objects: filePaths.map((el) => ({ Key: el })) } }, (err, data) => {
55
- if (err)
56
- reject(err);
57
- else
58
- resolve(data);
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duvdu-v1/duvdu",
3
- "version": "1.1.203",
3
+ "version": "1.1.204",
4
4
  "main": "./build/index.js",
5
5
  "types": "./build/index.d.ts",
6
6
  "files": [