@feardread/fear 1.0.1

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 (99) hide show
  1. package/FEAR.js +459 -0
  2. package/FEARServer.js +280 -0
  3. package/controllers/agent.js +438 -0
  4. package/controllers/auth/index.js +345 -0
  5. package/controllers/auth/token.js +50 -0
  6. package/controllers/blog.js +105 -0
  7. package/controllers/brand.js +10 -0
  8. package/controllers/cart.js +425 -0
  9. package/controllers/category.js +9 -0
  10. package/controllers/coupon.js +63 -0
  11. package/controllers/crud/crud.js +508 -0
  12. package/controllers/crud/index.js +36 -0
  13. package/controllers/email.js +34 -0
  14. package/controllers/enquiry.js +65 -0
  15. package/controllers/events.js +9 -0
  16. package/controllers/order.js +125 -0
  17. package/controllers/payment.js +31 -0
  18. package/controllers/product.js +147 -0
  19. package/controllers/review.js +247 -0
  20. package/controllers/tag.js +10 -0
  21. package/controllers/task.js +10 -0
  22. package/controllers/upload.js +41 -0
  23. package/controllers/user.js +401 -0
  24. package/index.js +7 -0
  25. package/libs/agent/index.js +561 -0
  26. package/libs/agent/modules/ai/ai.js +285 -0
  27. package/libs/agent/modules/ai/chat.js +518 -0
  28. package/libs/agent/modules/ai/config.js +688 -0
  29. package/libs/agent/modules/ai/operations.js +787 -0
  30. package/libs/agent/modules/analyze/api.js +546 -0
  31. package/libs/agent/modules/analyze/dorks.js +395 -0
  32. package/libs/agent/modules/ccard/README.md +454 -0
  33. package/libs/agent/modules/ccard/audit.js +479 -0
  34. package/libs/agent/modules/ccard/checker.js +674 -0
  35. package/libs/agent/modules/ccard/payment-processors.json +16 -0
  36. package/libs/agent/modules/ccard/validator.js +629 -0
  37. package/libs/agent/modules/code/analyzer.js +303 -0
  38. package/libs/agent/modules/code/jquery.js +1093 -0
  39. package/libs/agent/modules/code/react.js +1536 -0
  40. package/libs/agent/modules/code/refactor.js +499 -0
  41. package/libs/agent/modules/crypto/exchange.js +564 -0
  42. package/libs/agent/modules/net/proxy.js +409 -0
  43. package/libs/agent/modules/security/cve.js +442 -0
  44. package/libs/agent/modules/security/monitor.js +360 -0
  45. package/libs/agent/modules/security/scanner.js +300 -0
  46. package/libs/agent/modules/security/vulnerability.js +506 -0
  47. package/libs/agent/modules/security/web.js +465 -0
  48. package/libs/agent/modules/utils/browser.js +492 -0
  49. package/libs/agent/modules/utils/colorizer.js +285 -0
  50. package/libs/agent/modules/utils/manager.js +478 -0
  51. package/libs/cloud/index.js +228 -0
  52. package/libs/config/db.js +21 -0
  53. package/libs/config/validator.js +82 -0
  54. package/libs/db/index.js +318 -0
  55. package/libs/emailer/imap.js +126 -0
  56. package/libs/emailer/info.js +41 -0
  57. package/libs/emailer/smtp.js +77 -0
  58. package/libs/handler/async.js +3 -0
  59. package/libs/handler/error.js +66 -0
  60. package/libs/handler/index.js +161 -0
  61. package/libs/logger/index.js +49 -0
  62. package/libs/logger/morgan.js +24 -0
  63. package/libs/passport/passport.js +109 -0
  64. package/libs/search/api.js +384 -0
  65. package/libs/search/features.js +219 -0
  66. package/libs/search/service.js +64 -0
  67. package/libs/swagger/config.js +18 -0
  68. package/libs/swagger/index.js +35 -0
  69. package/libs/validator/index.js +254 -0
  70. package/models/blog.js +31 -0
  71. package/models/brand.js +12 -0
  72. package/models/cart.js +14 -0
  73. package/models/category.js +11 -0
  74. package/models/coupon.js +9 -0
  75. package/models/customer.js +0 -0
  76. package/models/enquiry.js +29 -0
  77. package/models/events.js +13 -0
  78. package/models/order.js +94 -0
  79. package/models/product.js +32 -0
  80. package/models/review.js +14 -0
  81. package/models/tag.js +10 -0
  82. package/models/task.js +11 -0
  83. package/models/user.js +68 -0
  84. package/package.json +12 -0
  85. package/routes/agent.js +615 -0
  86. package/routes/auth.js +13 -0
  87. package/routes/blog.js +19 -0
  88. package/routes/brand.js +15 -0
  89. package/routes/cart.js +105 -0
  90. package/routes/category.js +16 -0
  91. package/routes/coupon.js +15 -0
  92. package/routes/enquiry.js +14 -0
  93. package/routes/events.js +16 -0
  94. package/routes/mail.js +170 -0
  95. package/routes/order.js +19 -0
  96. package/routes/product.js +22 -0
  97. package/routes/review.js +11 -0
  98. package/routes/task.js +12 -0
  99. package/routes/user.js +17 -0
@@ -0,0 +1,478 @@
1
+ // modules/services/manager.js - Background Service Manager
2
+ const fs = require('fs').promises;
3
+ const path = require('path');
4
+ const colorizer = require('../utils/colorizer');
5
+ const EventEmitter = require('events');
6
+
7
+ const ServiceManager = function(agent) {
8
+ this.agent = agent;
9
+ this.services = new Map();
10
+ this.logs = new Map();
11
+ this.maxLogEntries = 1000;
12
+
13
+ // Register built-in services
14
+ this.registerBuiltInServices();
15
+ };
16
+
17
+ ServiceManager.prototype = {
18
+
19
+ registerBuiltInServices() {
20
+ // Auto-scan service - monitors for security issues
21
+ this.registerService('auto-scan', {
22
+ name: 'Auto Security Scanner',
23
+ description: 'Periodic security scanning of project files',
24
+ interval: 300000, // 5 minutes
25
+ action: () => this.runAutoScan()
26
+ });
27
+
28
+ // Log monitor service - watches for suspicious patterns
29
+ this.registerService('log-monitor', {
30
+ name: 'Log Monitor',
31
+ description: 'Monitors application logs for security events',
32
+ interval: 60000, // 1 minute
33
+ action: () => this.runLogMonitor()
34
+ });
35
+
36
+ // CVE checker service - checks for new vulnerabilities
37
+ this.registerService('cve-check', {
38
+ name: 'CVE Vulnerability Checker',
39
+ description: 'Checks dependencies for new CVEs',
40
+ interval: 3600000, // 1 hour
41
+ action: () => this.runCVECheck()
42
+ });
43
+
44
+ // Network monitor service - monitors network activity
45
+ this.registerService('net-monitor', {
46
+ name: 'Network Monitor',
47
+ description: 'Monitors network connections and traffic',
48
+ interval: 120000, // 2 minutes
49
+ action: () => this.runNetworkMonitor()
50
+ });
51
+
52
+ // File watcher service - watches for file changes
53
+ this.registerService('file-watch', {
54
+ name: 'File Watcher',
55
+ description: 'Watches project files for suspicious changes',
56
+ interval: 30000, // 30 seconds
57
+ action: () => this.runFileWatcher()
58
+ });
59
+ },
60
+
61
+ registerService(id, config) {
62
+ const service = {
63
+ id,
64
+ name: config.name,
65
+ description: config.description,
66
+ interval: config.interval,
67
+ action: config.action,
68
+ status: 'stopped',
69
+ timer: null,
70
+ lastRun: null,
71
+ runCount: 0,
72
+ errors: 0,
73
+ emitter: new EventEmitter()
74
+ };
75
+
76
+ this.services.set(id, service);
77
+ this.logs.set(id, []);
78
+
79
+ // Register with agent if available
80
+ if (this.agent) {
81
+ this.agent.registerService(id, {
82
+ start: () => this.startService([id]),
83
+ stop: () => this.stopService([id])
84
+ });
85
+ }
86
+ },
87
+
88
+ startService(args) {
89
+ const serviceId = args[0];
90
+
91
+ if (!serviceId) {
92
+ console.log(colorizer.error('Usage: service-start <service-id>'));
93
+ console.log(colorizer.info('Run "service-list" to see available services\n'));
94
+ return Promise.resolve();
95
+ }
96
+
97
+ const service = this.services.get(serviceId);
98
+
99
+ if (!service) {
100
+ console.log(colorizer.error(`Service "${serviceId}" not found\n`));
101
+ return Promise.resolve();
102
+ }
103
+
104
+ if (service.status === 'running') {
105
+ console.log(colorizer.warning(`Service "${service.name}" is already running\n`));
106
+ return Promise.resolve();
107
+ }
108
+
109
+ service.status = 'running';
110
+
111
+ // Run immediately
112
+ this.executeService(service);
113
+
114
+ // Schedule periodic execution
115
+ service.timer = setInterval(() => {
116
+ this.executeService(service);
117
+ }, service.interval);
118
+
119
+ this.log(serviceId, 'info', 'Service started');
120
+ console.log(colorizer.success(`✓ Started: ${service.name}`));
121
+ console.log(colorizer.dim(` Interval: ${this.formatInterval(service.interval)}\n`));
122
+
123
+ return Promise.resolve();
124
+ },
125
+
126
+ stopService(args) {
127
+ const serviceId = args[0];
128
+
129
+ if (!serviceId) {
130
+ console.log(colorizer.error('Usage: service-stop <service-id>'));
131
+ console.log(colorizer.info('Run "service-list" to see running services\n'));
132
+ return Promise.resolve();
133
+ }
134
+
135
+ const service = this.services.get(serviceId);
136
+
137
+ if (!service) {
138
+ console.log(colorizer.error(`Service "${serviceId}" not found\n`));
139
+ return Promise.resolve();
140
+ }
141
+
142
+ if (service.status === 'stopped') {
143
+ console.log(colorizer.warning(`Service "${service.name}" is not running\n`));
144
+ return Promise.resolve();
145
+ }
146
+
147
+ if (service.timer) {
148
+ clearInterval(service.timer);
149
+ service.timer = null;
150
+ }
151
+
152
+ service.status = 'stopped';
153
+ this.log(serviceId, 'info', 'Service stopped');
154
+
155
+ console.log(colorizer.success(`✓ Stopped: ${service.name}\n`));
156
+ return Promise.resolve();
157
+ },
158
+
159
+ restartService(args) {
160
+ const serviceId = args[0];
161
+
162
+ if (!serviceId) {
163
+ console.log(colorizer.error('Usage: service-restart <service-id>\n'));
164
+ return Promise.resolve();
165
+ }
166
+
167
+ console.log(colorizer.info(`Restarting ${serviceId}...`));
168
+
169
+ return this.stopService([serviceId])
170
+ .then(() => new Promise(resolve => setTimeout(resolve, 1000)))
171
+ .then(() => this.startService([serviceId]));
172
+ },
173
+
174
+ executeService(service) {
175
+ try {
176
+ service.lastRun = new Date();
177
+ service.runCount++;
178
+
179
+ Promise.resolve(service.action())
180
+ .then(result => {
181
+ if (result && result.alert) {
182
+ this.log(service.id, 'alert', result.message);
183
+ console.log(colorizer.warning(`\n⚠️ ${service.name}: ${result.message}`));
184
+ } else if (result && result.info) {
185
+ this.log(service.id, 'info', result.message);
186
+ }
187
+ })
188
+ .catch(err => {
189
+ service.errors++;
190
+ this.log(service.id, 'error', err.message);
191
+
192
+ if (service.errors > 5) {
193
+ console.log(colorizer.error(`\n❌ ${service.name} encountered multiple errors. Stopping...`));
194
+ this.stopService([service.id]);
195
+ }
196
+ });
197
+ } catch (err) {
198
+ service.errors++;
199
+ this.log(service.id, 'error', err.message);
200
+ }
201
+ },
202
+
203
+ serviceStatus(args) {
204
+ const serviceId = args[0];
205
+
206
+ if (serviceId) {
207
+ return this.showServiceDetail(serviceId);
208
+ }
209
+
210
+ console.log(colorizer.header('Background Services Status'));
211
+ console.log(colorizer.separator());
212
+
213
+ if (this.services.size === 0) {
214
+ console.log(colorizer.dim('No services registered\n'));
215
+ return Promise.resolve();
216
+ }
217
+
218
+ this.services.forEach(service => {
219
+ const status = service.status === 'running' ?
220
+ colorizer.green('● RUNNING') :
221
+ colorizer.dim('○ STOPPED');
222
+
223
+ console.log(`${status} ${colorizer.cyan(service.name)}`);
224
+ console.log(colorizer.dim(` ID: ${service.id}`));
225
+ console.log(colorizer.dim(` ${service.description}`));
226
+
227
+ if (service.status === 'running') {
228
+ console.log(colorizer.dim(` Interval: ${this.formatInterval(service.interval)}`));
229
+ console.log(colorizer.dim(` Runs: ${service.runCount}`));
230
+ if (service.lastRun) {
231
+ console.log(colorizer.dim(` Last run: ${service.lastRun.toLocaleString()}`));
232
+ }
233
+ }
234
+
235
+ if (service.errors > 0) {
236
+ console.log(colorizer.warning(` Errors: ${service.errors}`));
237
+ }
238
+
239
+ console.log();
240
+ });
241
+
242
+ return Promise.resolve();
243
+ },
244
+
245
+ showServiceDetail(serviceId) {
246
+ const service = this.services.get(serviceId);
247
+
248
+ if (!service) {
249
+ console.log(colorizer.error(`Service "${serviceId}" not found\n`));
250
+ return Promise.resolve();
251
+ }
252
+
253
+ console.log(colorizer.header(`Service: ${service.name}`));
254
+ console.log(colorizer.separator());
255
+
256
+ console.log(colorizer.cyan('ID: ') + service.id);
257
+ console.log(colorizer.cyan('Description: ') + service.description);
258
+ console.log(colorizer.cyan('Status: ') +
259
+ (service.status === 'running' ? colorizer.green('Running') : colorizer.dim('Stopped')));
260
+ console.log(colorizer.cyan('Interval: ') + this.formatInterval(service.interval));
261
+ console.log(colorizer.cyan('Total Runs: ') + service.runCount);
262
+ console.log(colorizer.cyan('Errors: ') +
263
+ (service.errors > 0 ? colorizer.warning(service.errors) : colorizer.green('0')));
264
+
265
+ if (service.lastRun) {
266
+ console.log(colorizer.cyan('Last Run: ') + service.lastRun.toLocaleString());
267
+ const nextRun = new Date(service.lastRun.getTime() + service.interval);
268
+ console.log(colorizer.cyan('Next Run: ') + nextRun.toLocaleString());
269
+ }
270
+
271
+ console.log();
272
+
273
+ // Show recent logs
274
+ const logs = this.logs.get(serviceId) || [];
275
+ if (logs.length > 0) {
276
+ console.log(colorizer.section('Recent Activity'));
277
+ logs.slice(-10).forEach(log => {
278
+ const icon = log.level === 'error' ? '❌' :
279
+ log.level === 'alert' ? '⚠️' :
280
+ 'ℹ️';
281
+ const color = log.level === 'error' ? colorizer.error :
282
+ log.level === 'alert' ? colorizer.warning :
283
+ colorizer.dim;
284
+ console.log(`${icon} ${color(`[${log.time}] ${log.message}`)}`);
285
+ });
286
+ }
287
+
288
+ console.log();
289
+ return Promise.resolve();
290
+ },
291
+
292
+ listServices(args) {
293
+ console.log(colorizer.header('Available Background Services'));
294
+ console.log(colorizer.separator());
295
+
296
+ if (this.services.size === 0) {
297
+ console.log(colorizer.dim('No services available\n'));
298
+ return Promise.resolve();
299
+ }
300
+
301
+ console.log(colorizer.section('Service List'));
302
+
303
+ this.services.forEach(service => {
304
+ const status = service.status === 'running' ?
305
+ colorizer.green('[RUNNING]') :
306
+ colorizer.dim('[STOPPED]');
307
+
308
+ console.log(`${status} ${colorizer.cyan(service.id)}`);
309
+ console.log(colorizer.dim(` ${service.name} - ${service.description}`));
310
+ });
311
+
312
+ console.log();
313
+ console.log(colorizer.info('Commands:'));
314
+ console.log(colorizer.dim(' service-start <id> - Start a service'));
315
+ console.log(colorizer.dim(' service-stop <id> - Stop a service'));
316
+ console.log(colorizer.dim(' service-status <id> - View service details'));
317
+ console.log(colorizer.dim(' service-logs <id> - View service logs'));
318
+ console.log(colorizer.dim(' service-restart <id> - Restart a service'));
319
+ console.log();
320
+
321
+ return Promise.resolve();
322
+ },
323
+
324
+ showLogs(args) {
325
+ const serviceId = args[0];
326
+ const limit = parseInt(args[1]) || 50;
327
+
328
+ if (!serviceId) {
329
+ console.log(colorizer.error('Usage: service-logs <service-id> [limit]'));
330
+ console.log(colorizer.info('Example: service-logs auto-scan 100\n'));
331
+ return Promise.resolve();
332
+ }
333
+
334
+ const service = this.services.get(serviceId);
335
+ const logs = this.logs.get(serviceId);
336
+
337
+ if (!service) {
338
+ console.log(colorizer.error(`Service "${serviceId}" not found\n`));
339
+ return Promise.resolve();
340
+ }
341
+
342
+ console.log(colorizer.header(`Logs: ${service.name}`));
343
+ console.log(colorizer.separator());
344
+
345
+ if (!logs || logs.length === 0) {
346
+ console.log(colorizer.dim('No logs available\n'));
347
+ return Promise.resolve();
348
+ }
349
+
350
+ const recentLogs = logs.slice(-limit);
351
+
352
+ console.log(colorizer.cyan(`Showing last ${recentLogs.length} entries:\n`));
353
+
354
+ recentLogs.forEach(log => {
355
+ const icon = log.level === 'error' ? '❌' :
356
+ log.level === 'alert' ? '⚠️' :
357
+ log.level === 'info' ? 'ℹ️' :
358
+ '📝';
359
+
360
+ const color = log.level === 'error' ? colorizer.error :
361
+ log.level === 'alert' ? colorizer.warning :
362
+ colorizer.dim;
363
+
364
+ console.log(`${icon} ${color(`[${log.timestamp}] ${log.message}`)}`);
365
+ });
366
+
367
+ console.log();
368
+ return Promise.resolve();
369
+ },
370
+
371
+ log(serviceId, level, message) {
372
+ const logs = this.logs.get(serviceId);
373
+ if (!logs) return;
374
+
375
+ const entry = {
376
+ timestamp: new Date().toISOString(),
377
+ time: new Date().toLocaleTimeString(),
378
+ level,
379
+ message
380
+ };
381
+
382
+ logs.push(entry);
383
+
384
+ // Trim logs if too many
385
+ if (logs.length > this.maxLogEntries) {
386
+ logs.splice(0, logs.length - this.maxLogEntries);
387
+ }
388
+ },
389
+
390
+ formatInterval(ms) {
391
+ const seconds = Math.floor(ms / 1000);
392
+ const minutes = Math.floor(seconds / 60);
393
+ const hours = Math.floor(minutes / 60);
394
+
395
+ if (hours > 0) {
396
+ return `${hours}h ${minutes % 60}m`;
397
+ } else if (minutes > 0) {
398
+ return `${minutes}m ${seconds % 60}s`;
399
+ } else {
400
+ return `${seconds}s`;
401
+ }
402
+ },
403
+
404
+ // Built-in service actions
405
+
406
+ runAutoScan() {
407
+ // Scan project files for security issues
408
+ const scanResults = {
409
+ filesScanned: 0,
410
+ issues: []
411
+ };
412
+
413
+ // This would integrate with the scanner module
414
+ if (this.agent && this.agent.modules.scanner) {
415
+ // Perform quick security audit
416
+ return Promise.resolve({
417
+ info: true,
418
+ message: `Scanned ${scanResults.filesScanned} files`
419
+ });
420
+ }
421
+
422
+ return Promise.resolve();
423
+ },
424
+
425
+ runLogMonitor() {
426
+ // Monitor logs for suspicious patterns
427
+ const logsDir = './logs';
428
+
429
+ return fs.readdir(logsDir)
430
+ .then(files => {
431
+ const logFiles = files.filter(f => f.endsWith('.log'));
432
+
433
+ if (logFiles.length > 0) {
434
+ return {
435
+ info: true,
436
+ message: `Monitored ${logFiles.length} log files`
437
+ };
438
+ }
439
+ })
440
+ .catch(() => null);
441
+ },
442
+
443
+ runCVECheck() {
444
+ // Check for CVE updates
445
+ if (this.agent && this.agent.modules.cveDatabase) {
446
+ return Promise.resolve({
447
+ info: true,
448
+ message: 'CVE database check completed'
449
+ });
450
+ }
451
+ return Promise.resolve();
452
+ },
453
+
454
+ runNetworkMonitor() {
455
+ // Monitor network connections
456
+ return Promise.resolve({
457
+ info: true,
458
+ message: 'Network monitoring active'
459
+ });
460
+ },
461
+
462
+ runFileWatcher() {
463
+ // Watch for file changes
464
+ const watchDir = process.cwd();
465
+
466
+ return fs.readdir(watchDir)
467
+ .then(files => {
468
+ // This would track file modifications
469
+ return {
470
+ info: true,
471
+ message: `Watching ${files.length} files`
472
+ };
473
+ })
474
+ .catch(() => null);
475
+ }
476
+ };
477
+
478
+ module.exports = ServiceManager;
@@ -0,0 +1,228 @@
1
+ const cloudinary = require("cloudinary").v2;
2
+ const multer = require("multer");
3
+ const sharp = require("sharp");
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+ require("dotenv").config();
7
+
8
+ // Initialize Cloudinary configuration
9
+ cloudinary.config({
10
+ cloud_name: process.env.CLOUDINARY_NAME,
11
+ api_key: process.env.API_KEY,
12
+ api_secret: process.env.API_SECRET,
13
+ });
14
+
15
+ /**
16
+ * Converts file buffer to base64 data URL
17
+ * @param {Object} file - File object with mimetype and buffer
18
+ * @returns {string} Base64 data URL
19
+ */
20
+ const convertToBase64 = (file) => {
21
+ return `data:${file.mimetype};base64,${file.buffer.toString("base64")}`;
22
+ };
23
+
24
+ /**
25
+ * Uploads avatar to Cloudinary
26
+ * @param {string} file - Base64 encoded file or file path
27
+ * @returns {Promise<Object>} Avatar object with public_id and url
28
+ */
29
+ const uploadAvatar = async (file) => {
30
+ try {
31
+ const result = await cloudinary.uploader.upload(file, {
32
+ folder: "avatar",
33
+ width: 150,
34
+ crop: "scale",
35
+ });
36
+
37
+ return {
38
+ public_id: result.public_id,
39
+ url: result.secure_url,
40
+ };
41
+ } catch (error) {
42
+ throw new Error(`Avatar upload failed: ${error.message}`);
43
+ }
44
+ };
45
+
46
+ /**
47
+ * Uploads multiple images to Cloudinary in chunks to avoid rate limiting
48
+ * @param {string|string[]} files - Single file or array of files
49
+ * @param {number} chunkSize - Number of files to upload concurrently
50
+ * @returns {Promise<Object[]>} Array of image objects with public_id and url
51
+ */
52
+ const uploadImages = async (files, chunkSize = 3) => {
53
+ try {
54
+ // Normalize input to array
55
+ const imageArray = Array.isArray(files) ? [...files] : [files];
56
+
57
+ if (imageArray.length === 0) {
58
+ return [];
59
+ }
60
+
61
+ const imageLinks = [];
62
+
63
+ // Process images in chunks to avoid overwhelming the API
64
+ for (let i = 0; i < imageArray.length; i += chunkSize) {
65
+ const chunk = imageArray.slice(i, i + chunkSize);
66
+
67
+ const uploadPromises = chunk.map((image) =>
68
+ cloudinary.uploader.upload(image, {
69
+ folder: "products",
70
+ })
71
+ );
72
+
73
+ const results = await Promise.all(uploadPromises);
74
+
75
+ const chunkResults = results.map((result) => ({
76
+ product_id: result.public_id,
77
+ url: result.secure_url,
78
+ }));
79
+
80
+ imageLinks.push(...chunkResults);
81
+ }
82
+
83
+ return imageLinks;
84
+ } catch (error) {
85
+ throw new Error(`Image upload failed: ${error.message}`);
86
+ }
87
+ };
88
+
89
+ /**
90
+ * Multer disk storage configuration
91
+ */
92
+ const storage = multer.diskStorage({
93
+ destination: (req, file, cb) => {
94
+ const uploadPath = path.join(__dirname, "../public/images/");
95
+ cb(null, uploadPath);
96
+ },
97
+ filename: (req, file, cb) => {
98
+ const uniqueSuffix = `${Date.now()}-${Math.round(Math.random() * 1e9)}`;
99
+ const extension = path.extname(file.originalname) || '.jpeg';
100
+ cb(null, `${file.fieldname}-${uniqueSuffix}${extension}`);
101
+ },
102
+ });
103
+
104
+ /**
105
+ * Multer file filter for images only
106
+ */
107
+ const multerFilter = (req, file, cb) => {
108
+ if (file.mimetype.startsWith("image/")) {
109
+ cb(null, true);
110
+ } else {
111
+ cb(new Error("Unsupported file format. Only images are allowed."), false);
112
+ }
113
+ };
114
+
115
+ /**
116
+ * Multer upload configuration
117
+ */
118
+ const uploadPhoto = multer({
119
+ storage,
120
+ fileFilter: multerFilter,
121
+ limits: {
122
+ fileSize: 1000000, // 1MB
123
+ files: 10 // Maximum 10 files
124
+ },
125
+ });
126
+
127
+ /**
128
+ * Middleware to resize uploaded images
129
+ * @param {Object} req - Express request object
130
+ * @param {Object} res - Express response object
131
+ * @param {Function} next - Express next function
132
+ */
133
+ const resizeImages = async (req, res, next) => {
134
+ try {
135
+ // Skip if no files uploaded or no directory specified
136
+ if (!req.files || !req.directory) {
137
+ return next();
138
+ }
139
+
140
+ // Ensure directory exists
141
+ const targetDir = path.join("public/images", req.directory);
142
+ if (!fs.existsSync(targetDir)) {
143
+ fs.mkdirSync(targetDir, { recursive: true });
144
+ }
145
+
146
+ // Process all files concurrently
147
+ await Promise.all(
148
+ req.files.map(async (file) => {
149
+ const originalPath = file.path;
150
+ const targetPath = path.join(targetDir, file.filename);
151
+
152
+ try {
153
+ // Resize and convert image
154
+ await sharp(originalPath)
155
+ .resize(300, 300, {
156
+ fit: 'cover',
157
+ position: 'center'
158
+ })
159
+ .jpeg({ quality: 90 })
160
+ .toFile(targetPath);
161
+
162
+ // Clean up original file
163
+ if (fs.existsSync(originalPath)) {
164
+ fs.unlinkSync(originalPath);
165
+ }
166
+ } catch (imageError) {
167
+ console.error(`Failed to process image ${file.filename}:`, imageError);
168
+ // Clean up files on error
169
+ [originalPath, targetPath].forEach(filePath => {
170
+ if (fs.existsSync(filePath)) {
171
+ fs.unlinkSync(filePath);
172
+ }
173
+ });
174
+ throw imageError;
175
+ }
176
+ })
177
+ );
178
+
179
+ next();
180
+ } catch (error) {
181
+ next(new Error(`Image resize failed: ${error.message}`));
182
+ }
183
+ };
184
+
185
+ /**
186
+ * Delete image from Cloudinary
187
+ * @param {string} publicId - Public ID of the image to delete
188
+ * @returns {Promise<Object>} Deletion result
189
+ */
190
+ const deleteImage = async (publicId) => {
191
+ try {
192
+ const result = await cloudinary.uploader.destroy(publicId);
193
+ return result;
194
+ } catch (error) {
195
+ throw new Error(`Image deletion failed: ${error.message}`);
196
+ }
197
+ };
198
+
199
+ /**
200
+ * Get Cloudinary image transformation URL
201
+ * @param {string} publicId - Public ID of the image
202
+ * @param {Object} transformations - Transformation options
203
+ * @returns {string} Transformed image URL
204
+ */
205
+ const getTransformedImageUrl = (publicId, transformations = {}) => {
206
+ return cloudinary.url(publicId, {
207
+ secure: true,
208
+ ...transformations,
209
+ });
210
+ };
211
+
212
+ module.exports = {
213
+ // Core functions
214
+ convertToBase64,
215
+ uploadAvatar,
216
+ uploadImages,
217
+ deleteImage,
218
+ getTransformedImageUrl,
219
+
220
+ // Multer configuration
221
+ storage,
222
+ multerFilter,
223
+ uploadPhoto,
224
+ resizeImages,
225
+
226
+ // Cloudinary instance (if needed for direct access)
227
+ cloudinary,
228
+ };