@arela/uploader 1.0.24 → 1.1.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.
- package/docs/AUTO_PROCESSING_PIPELINE.md +258 -0
- package/docs/COMPLETE_USAGE_GUIDE.md +1363 -0
- package/docs/DATABASESERVICE_IMPROVEMENTS.md +546 -0
- package/docs/PASO_2_TEST_RESULTS.md +298 -0
- package/docs/PASO_3_PLAN.md +385 -0
- package/docs/PHASE_1_FILE_DETECTION.md +366 -0
- package/docs/PHASE_2_API_INTEGRATION.md +426 -0
- package/docs/PHASE_3_DATABASE_MANAGEMENT.md +480 -0
- package/docs/PHASE_4_FILE_OPERATIONS.md +448 -0
- package/docs/PHASE_5_WATCH_MODE.md +450 -0
- package/docs/PHASE_6_SIGNAL_HANDLING.md +472 -0
- package/docs/PHASE_7_ADVANCED_FEATURES.md +560 -0
- package/docs/PLAN_WATCH_FEATURE.md +417 -0
- package/docs/README.md +480 -0
- package/docs/SCHEMA_ALIGNMENT_SUMMARY.md +301 -0
- package/docs/SMARTWATCH_DATABASE_REFACTORING.md +181 -0
- package/docs/SMART_WATCH_DATABASE_CHANGES.md +502 -0
- package/docs/TESTING_WATCH_MODE.md +212 -0
- package/docs/WATCHER_API_IMPLEMENTATION.md +520 -0
- package/docs/WATCHER_API_INTEGRATION.md +562 -0
- package/docs/WATCHER_SETUP_GUIDE.md +614 -0
- package/docs/WATCH_ARCHITECTURE.md +395 -0
- package/docs/WATCH_AUTO_PIPELINE.md +334 -0
- package/docs/WATCH_CONFIGURATION.md +267 -0
- package/docs/WATCH_USAGE_GUIDE.md +567 -0
- package/docs/commands.md +14 -0
- package/package.json +1 -1
- package/src/commands/IdentifyCommand.js +11 -0
- package/src/config/config.js +2 -2
- package/src/file-detection.js +42 -1
- package/src/scoring/scoring-engine.js +40 -7
- package/src/services/LoggingService.js +5 -3
- package/.vscode/settings.json +0 -1
- package/coverage/IdentifyCommand.js.html +0 -1462
- package/coverage/PropagateCommand.js.html +0 -1507
- package/coverage/PushCommand.js.html +0 -1504
- package/coverage/ScanCommand.js.html +0 -1654
- package/coverage/UploadCommand.js.html +0 -1846
- package/coverage/WatchCommand.js.html +0 -4111
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -191
- package/coverage/lcov-report/IdentifyCommand.js.html +0 -1462
- package/coverage/lcov-report/PropagateCommand.js.html +0 -1507
- package/coverage/lcov-report/PushCommand.js.html +0 -1504
- package/coverage/lcov-report/ScanCommand.js.html +0 -1654
- package/coverage/lcov-report/UploadCommand.js.html +0 -1846
- package/coverage/lcov-report/WatchCommand.js.html +0 -4111
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -87
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +0 -191
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -210
- package/coverage/lcov.info +0 -1937
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -210
- package/docs/API_ENDPOINTS_FOR_DETECTION.md +0 -647
- package/docs/API_RETRY_MECHANISM.md +0 -338
- package/docs/ARELA_IDENTIFY_IMPLEMENTATION.md +0 -489
- package/docs/ARELA_IDENTIFY_QUICKREF.md +0 -186
- package/docs/ARELA_PROPAGATE_IMPLEMENTATION.md +0 -581
- package/docs/ARELA_PROPAGATE_QUICKREF.md +0 -272
- package/docs/ARELA_PUSH_IMPLEMENTATION.md +0 -577
- package/docs/ARELA_PUSH_QUICKREF.md +0 -322
- package/docs/ARELA_SCAN_IMPLEMENTATION.md +0 -373
- package/docs/ARELA_SCAN_QUICKREF.md +0 -139
- package/docs/CROSS_PLATFORM_PATH_HANDLING.md +0 -597
- package/docs/DETECTION_ATTEMPT_TRACKING.md +0 -414
- package/docs/MIGRATION_UPLOADER_TO_FILE_STATS.md +0 -1020
- package/docs/MULTI_LEVEL_DIRECTORY_SCANNING.md +0 -494
- package/docs/QUICK_REFERENCE_API_DETECTION.md +0 -264
- package/docs/REFACTORING_SUMMARY_DETECT_PEDIMENTOS.md +0 -200
- package/docs/STATS_COMMAND_SEQUENCE_DIAGRAM.md +0 -287
- package/docs/STATS_COMMAND_SIMPLE.md +0 -93
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
# Integración del Watcher con arela-api
|
|
2
|
+
|
|
3
|
+
## 📋 Resumen
|
|
4
|
+
Este documento describe cómo exponer la información del WatchService a través de endpoints REST en la arela-api para que el frontend pueda monitorear en tiempo real el estado del uploader en modo watch.
|
|
5
|
+
|
|
6
|
+
## 🎯 Objetivos
|
|
7
|
+
- Permitir que el frontend vea el estado en tiempo real del watcher
|
|
8
|
+
- Exponer estadísticas de archivos pendientes, listos para subir, procesando, etc.
|
|
9
|
+
- Permitir control remoto del watcher (start/stop/enable-disable auto-processing)
|
|
10
|
+
- Proporcionar información de progreso del pipeline automático
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 📡 Endpoints Necesarios
|
|
15
|
+
|
|
16
|
+
### 1. GET `/api/watcher/status`
|
|
17
|
+
**Descripción:** Obtener estado actual del watcher
|
|
18
|
+
|
|
19
|
+
**Respuesta:**
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"isRunning": true,
|
|
23
|
+
"autoProcessingEnabled": true,
|
|
24
|
+
"watchedDirectories": 3,
|
|
25
|
+
"activeWatchers": 3,
|
|
26
|
+
"lastUpdated": "2025-11-17T22:30:00.000Z",
|
|
27
|
+
"stats": {
|
|
28
|
+
"filesAdded": 145,
|
|
29
|
+
"filesModified": 23,
|
|
30
|
+
"filesRemoved": 5,
|
|
31
|
+
"uploadsTriggered": 12,
|
|
32
|
+
"pipelinesTriggered": 12,
|
|
33
|
+
"errorsEncountered": 0
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
### 2. GET `/api/watcher/queue-stats`
|
|
41
|
+
**Descripción:** Obtener estadísticas de la cola de procesamiento
|
|
42
|
+
|
|
43
|
+
**Respuesta:**
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"queue": {
|
|
47
|
+
"pending": 25,
|
|
48
|
+
"readyToUpload": 8,
|
|
49
|
+
"processing": 2,
|
|
50
|
+
"uploaded": 156,
|
|
51
|
+
"failed": 0,
|
|
52
|
+
"total": 191
|
|
53
|
+
},
|
|
54
|
+
"progress": {
|
|
55
|
+
"percentComplete": 82.2,
|
|
56
|
+
"estimatedTimeRemaining": "5 minutes",
|
|
57
|
+
"avgProcessingTimeMs": 3400
|
|
58
|
+
},
|
|
59
|
+
"timestamp": "2025-11-17T22:30:00.000Z"
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
### 3. GET `/api/watcher/directories`
|
|
66
|
+
**Descripción:** Obtener lista de directorios siendo monitoreados
|
|
67
|
+
|
|
68
|
+
**Respuesta:**
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"directories": [
|
|
72
|
+
{
|
|
73
|
+
"path": "/Users/jjfigueroa/Documents/2023",
|
|
74
|
+
"isReady": true,
|
|
75
|
+
"fileCount": 65,
|
|
76
|
+
"folderStructure": "arela"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"path": "/Users/jjfigueroa/Documents/2024",
|
|
80
|
+
"isReady": true,
|
|
81
|
+
"fileCount": 89,
|
|
82
|
+
"folderStructure": "arela"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"path": "/Users/jjfigueroa/Downloads",
|
|
86
|
+
"isReady": false,
|
|
87
|
+
"fileCount": 37,
|
|
88
|
+
"folderStructure": "arela"
|
|
89
|
+
}
|
|
90
|
+
],
|
|
91
|
+
"totalDirectories": 3
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
### 4. POST `/api/watcher/start`
|
|
98
|
+
**Descripción:** Iniciar el watcher
|
|
99
|
+
|
|
100
|
+
**Body (opcional):**
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"directories": ["/path/to/dir1", "/path/to/dir2"],
|
|
104
|
+
"autoProcessing": true,
|
|
105
|
+
"folderStructure": "arela"
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Respuesta:**
|
|
110
|
+
```json
|
|
111
|
+
{
|
|
112
|
+
"success": true,
|
|
113
|
+
"message": "Watcher started successfully",
|
|
114
|
+
"stats": { /* ver GET /api/watcher/status */ }
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
### 5. POST `/api/watcher/stop`
|
|
121
|
+
**Descripción:** Detener el watcher
|
|
122
|
+
|
|
123
|
+
**Respuesta:**
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"success": true,
|
|
127
|
+
"message": "Watcher stopped successfully",
|
|
128
|
+
"finalStats": { /* estadísticas finales */ }
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
### 6. POST `/api/watcher/enable-auto-processing`
|
|
135
|
+
**Descripción:** Habilitar procesamiento automático
|
|
136
|
+
|
|
137
|
+
**Body:**
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"batchSize": 10,
|
|
141
|
+
"timeout": 30000
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Respuesta:**
|
|
146
|
+
```json
|
|
147
|
+
{
|
|
148
|
+
"success": true,
|
|
149
|
+
"autoProcessingEnabled": true
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
### 7. POST `/api/watcher/disable-auto-processing`
|
|
156
|
+
**Descripción:** Deshabilitar procesamiento automático
|
|
157
|
+
|
|
158
|
+
**Respuesta:**
|
|
159
|
+
```json
|
|
160
|
+
{
|
|
161
|
+
"success": true,
|
|
162
|
+
"autoProcessingEnabled": false
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
### 8. GET `/api/watcher/pipeline-status`
|
|
169
|
+
**Descripción:** Obtener estado de los pipelines en ejecución
|
|
170
|
+
|
|
171
|
+
**Respuesta:**
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"isProcessing": false,
|
|
175
|
+
"activePipelines": 0,
|
|
176
|
+
"recentPipelines": [
|
|
177
|
+
{
|
|
178
|
+
"id": "pipeline-1763418030418-ml4dcsgw3",
|
|
179
|
+
"file": "3429-07-23009203-Simplif.pdf",
|
|
180
|
+
"directory": "/Users/jjfigueroa/Documents/2023/230734293009203",
|
|
181
|
+
"startTime": "2025-11-17T22:20:30.000Z",
|
|
182
|
+
"duration": 653,
|
|
183
|
+
"status": "success",
|
|
184
|
+
"steps": {
|
|
185
|
+
"statsOnly": "success",
|
|
186
|
+
"detectPdfs": "success",
|
|
187
|
+
"propagateArelaPath": "success",
|
|
188
|
+
"uploadByRfc": "success"
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
]
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## 🔧 Implementación en arela-api
|
|
198
|
+
|
|
199
|
+
### Archivo: `routes/watcher.routes.js`
|
|
200
|
+
|
|
201
|
+
```javascript
|
|
202
|
+
import express from 'express';
|
|
203
|
+
import watchService from '../services/WatchService.js'; // Import desde arela-uploader
|
|
204
|
+
import logger from '../services/LoggingService.js';
|
|
205
|
+
|
|
206
|
+
const router = express.Router();
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* GET /api/watcher/status
|
|
210
|
+
* Get watcher status and stats
|
|
211
|
+
*/
|
|
212
|
+
router.get('/status', (req, res) => {
|
|
213
|
+
try {
|
|
214
|
+
const stats = watchService.getStats();
|
|
215
|
+
res.json({
|
|
216
|
+
isRunning: stats.isRunning,
|
|
217
|
+
autoProcessingEnabled: stats.autoProcessingEnabled,
|
|
218
|
+
watchedDirectories: stats.watchedDirectories,
|
|
219
|
+
activeWatchers: stats.activeWatchers,
|
|
220
|
+
lastUpdated: new Date().toISOString(),
|
|
221
|
+
stats: {
|
|
222
|
+
filesAdded: stats.filesAdded,
|
|
223
|
+
filesModified: stats.filesModified,
|
|
224
|
+
filesRemoved: stats.filesRemoved,
|
|
225
|
+
uploadsTriggered: stats.uploadsTriggered,
|
|
226
|
+
pipelinesTriggered: stats.pipelinesTriggered,
|
|
227
|
+
errorsEncountered: stats.errorsEncountered,
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
} catch (error) {
|
|
231
|
+
logger.error(`Error getting watcher status: ${error.message}`);
|
|
232
|
+
res.status(500).json({ error: error.message });
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* GET /api/watcher/queue-stats
|
|
238
|
+
* Get queue statistics
|
|
239
|
+
*/
|
|
240
|
+
router.get('/queue-stats', async (req, res) => {
|
|
241
|
+
try {
|
|
242
|
+
const queueStats = await watchService.getQueueStats();
|
|
243
|
+
if (!queueStats) {
|
|
244
|
+
return res.status(500).json({ error: 'Could not fetch queue stats' });
|
|
245
|
+
}
|
|
246
|
+
res.json(queueStats);
|
|
247
|
+
} catch (error) {
|
|
248
|
+
logger.error(`Error getting queue stats: ${error.message}`);
|
|
249
|
+
res.status(500).json({ error: error.message });
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* GET /api/watcher/directories
|
|
255
|
+
* Get watched directories info
|
|
256
|
+
*/
|
|
257
|
+
router.get('/directories', (req, res) => {
|
|
258
|
+
try {
|
|
259
|
+
const directories = Array.from(watchService.watchedDirs).map((dir) => {
|
|
260
|
+
const config = watchService.directoryConfigs.get(dir);
|
|
261
|
+
const isReady = watchService.watcherReady.get(dir) === true;
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
path: dir,
|
|
265
|
+
isReady,
|
|
266
|
+
folderStructure: config?.folderStructure || 'default',
|
|
267
|
+
};
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
res.json({
|
|
271
|
+
directories,
|
|
272
|
+
totalDirectories: directories.length,
|
|
273
|
+
});
|
|
274
|
+
} catch (error) {
|
|
275
|
+
logger.error(`Error getting watched directories: ${error.message}`);
|
|
276
|
+
res.status(500).json({ error: error.message });
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* POST /api/watcher/start
|
|
282
|
+
* Start the watcher
|
|
283
|
+
*/
|
|
284
|
+
router.post('/start', async (req, res) => {
|
|
285
|
+
try {
|
|
286
|
+
const { directories, autoProcessing = true, folderStructure = 'arela' } =
|
|
287
|
+
req.body;
|
|
288
|
+
|
|
289
|
+
if (!directories || directories.length === 0) {
|
|
290
|
+
return res.status(400).json({ error: 'No directories specified' });
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Add watchers for each directory
|
|
294
|
+
for (const dir of directories) {
|
|
295
|
+
await watchService.addWatcher(dir, {}, { folderStructure });
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Enable auto-processing if requested
|
|
299
|
+
if (autoProcessing) {
|
|
300
|
+
watchService.enableAutoProcessing({ batchSize: 10 });
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Start watching
|
|
304
|
+
await watchService.start();
|
|
305
|
+
|
|
306
|
+
res.json({
|
|
307
|
+
success: true,
|
|
308
|
+
message: 'Watcher started successfully',
|
|
309
|
+
stats: watchService.getStats(),
|
|
310
|
+
});
|
|
311
|
+
} catch (error) {
|
|
312
|
+
logger.error(`Error starting watcher: ${error.message}`);
|
|
313
|
+
res.status(500).json({ error: error.message });
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* POST /api/watcher/stop
|
|
319
|
+
* Stop the watcher
|
|
320
|
+
*/
|
|
321
|
+
router.post('/stop', async (req, res) => {
|
|
322
|
+
try {
|
|
323
|
+
const finalStats = watchService.getStats();
|
|
324
|
+
await watchService.stop('API request');
|
|
325
|
+
|
|
326
|
+
res.json({
|
|
327
|
+
success: true,
|
|
328
|
+
message: 'Watcher stopped successfully',
|
|
329
|
+
finalStats,
|
|
330
|
+
});
|
|
331
|
+
} catch (error) {
|
|
332
|
+
logger.error(`Error stopping watcher: ${error.message}`);
|
|
333
|
+
res.status(500).json({ error: error.message });
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* POST /api/watcher/enable-auto-processing
|
|
339
|
+
* Enable automatic processing pipeline
|
|
340
|
+
*/
|
|
341
|
+
router.post('/enable-auto-processing', (req, res) => {
|
|
342
|
+
try {
|
|
343
|
+
const { batchSize = 10 } = req.body;
|
|
344
|
+
|
|
345
|
+
watchService.enableAutoProcessing({ batchSize });
|
|
346
|
+
|
|
347
|
+
res.json({
|
|
348
|
+
success: true,
|
|
349
|
+
autoProcessingEnabled: watchService.isAutoProcessingEnabled(),
|
|
350
|
+
});
|
|
351
|
+
} catch (error) {
|
|
352
|
+
logger.error(`Error enabling auto-processing: ${error.message}`);
|
|
353
|
+
res.status(500).json({ error: error.message });
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* POST /api/watcher/disable-auto-processing
|
|
359
|
+
* Disable automatic processing pipeline
|
|
360
|
+
*/
|
|
361
|
+
router.post('/disable-auto-processing', (req, res) => {
|
|
362
|
+
try {
|
|
363
|
+
watchService.disableAutoProcessing();
|
|
364
|
+
|
|
365
|
+
res.json({
|
|
366
|
+
success: true,
|
|
367
|
+
autoProcessingEnabled: watchService.isAutoProcessingEnabled(),
|
|
368
|
+
});
|
|
369
|
+
} catch (error) {
|
|
370
|
+
logger.error(`Error disabling auto-processing: ${error.message}`);
|
|
371
|
+
res.status(500).json({ error: error.message });
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
export default router;
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## 🖥️ Integración en arela-api Express App
|
|
381
|
+
|
|
382
|
+
### Archivo: `app.js` o `index.js`
|
|
383
|
+
|
|
384
|
+
```javascript
|
|
385
|
+
import express from 'express';
|
|
386
|
+
import watcherRoutes from './routes/watcher.routes.js';
|
|
387
|
+
|
|
388
|
+
const app = express();
|
|
389
|
+
|
|
390
|
+
// ... other middleware ...
|
|
391
|
+
|
|
392
|
+
// Mount watcher routes
|
|
393
|
+
app.use('/api/watcher', watcherRoutes);
|
|
394
|
+
|
|
395
|
+
// ... rest of app ...
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## 📦 Importación de WatchService
|
|
401
|
+
|
|
402
|
+
Para que arela-api pueda acceder a WatchService desde arela-uploader, hay dos opciones:
|
|
403
|
+
|
|
404
|
+
### Opción 1: Exportar como módulo NPM (Recomendado)
|
|
405
|
+
1. En `arela-uploader/package.json`:
|
|
406
|
+
```json
|
|
407
|
+
{
|
|
408
|
+
"name": "@arela/uploader",
|
|
409
|
+
"exports": {
|
|
410
|
+
"./services": "./src/services/index.js"
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
2. En `arela-uploader/src/services/index.js`:
|
|
416
|
+
```javascript
|
|
417
|
+
export { default as watchService } from './WatchService.js';
|
|
418
|
+
export { default as databaseService } from './DatabaseService.js';
|
|
419
|
+
export { default as autoProcessingService } from './AutoProcessingService.js';
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
3. En arela-api:
|
|
423
|
+
```bash
|
|
424
|
+
npm install /path/to/arela-uploader
|
|
425
|
+
# o si está publicado en npm
|
|
426
|
+
npm install @arela/uploader
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
4. Usar en arela-api:
|
|
430
|
+
```javascript
|
|
431
|
+
import { watchService } from '@arela/uploader/services';
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### Opción 2: Compartir código vía monorepo
|
|
435
|
+
Usar Lerna o Yarn Workspaces para mantener ambos proyectos en el mismo workspace.
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
## 🎨 Ejemplo de Interfaz Frontend
|
|
440
|
+
|
|
441
|
+
```javascript
|
|
442
|
+
// hooks/useWatcher.js
|
|
443
|
+
import { useState, useEffect } from 'react';
|
|
444
|
+
|
|
445
|
+
export const useWatcher = () => {
|
|
446
|
+
const [status, setStatus] = useState(null);
|
|
447
|
+
const [queue, setQueue] = useState(null);
|
|
448
|
+
const [loading, setLoading] = useState(false);
|
|
449
|
+
const [error, setError] = useState(null);
|
|
450
|
+
|
|
451
|
+
useEffect(() => {
|
|
452
|
+
const interval = setInterval(async () => {
|
|
453
|
+
try {
|
|
454
|
+
setLoading(true);
|
|
455
|
+
const [statusRes, queueRes] = await Promise.all([
|
|
456
|
+
fetch('/api/watcher/status'),
|
|
457
|
+
fetch('/api/watcher/queue-stats'),
|
|
458
|
+
]);
|
|
459
|
+
|
|
460
|
+
const statusData = await statusRes.json();
|
|
461
|
+
const queueData = await queueRes.json();
|
|
462
|
+
|
|
463
|
+
setStatus(statusData);
|
|
464
|
+
setQueue(queueData);
|
|
465
|
+
setError(null);
|
|
466
|
+
} catch (err) {
|
|
467
|
+
setError(err.message);
|
|
468
|
+
} finally {
|
|
469
|
+
setLoading(false);
|
|
470
|
+
}
|
|
471
|
+
}, 5000); // Refrescar cada 5 segundos
|
|
472
|
+
|
|
473
|
+
return () => clearInterval(interval);
|
|
474
|
+
}, []);
|
|
475
|
+
|
|
476
|
+
const startWatcher = async (directories) => {
|
|
477
|
+
const res = await fetch('/api/watcher/start', {
|
|
478
|
+
method: 'POST',
|
|
479
|
+
headers: { 'Content-Type': 'application/json' },
|
|
480
|
+
body: JSON.stringify({ directories, autoProcessing: true }),
|
|
481
|
+
});
|
|
482
|
+
return res.json();
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
const stopWatcher = async () => {
|
|
486
|
+
const res = await fetch('/api/watcher/stop', { method: 'POST' });
|
|
487
|
+
return res.json();
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
return {
|
|
491
|
+
status,
|
|
492
|
+
queue,
|
|
493
|
+
loading,
|
|
494
|
+
error,
|
|
495
|
+
startWatcher,
|
|
496
|
+
stopWatcher,
|
|
497
|
+
};
|
|
498
|
+
};
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
---
|
|
502
|
+
|
|
503
|
+
## 🔐 Consideraciones de Seguridad
|
|
504
|
+
|
|
505
|
+
1. **Autenticación**: Todos los endpoints deben requerir autenticación JWT o similar
|
|
506
|
+
2. **Autorización**: Solo usuarios con rol `admin` o `uploader` pueden controlar el watcher
|
|
507
|
+
3. **Rate Limiting**: Implementar rate limiting en los endpoints de control (start/stop)
|
|
508
|
+
4. **Auditoría**: Registrar todas las acciones de control del watcher
|
|
509
|
+
|
|
510
|
+
Ejemplo con middleware de autenticación:
|
|
511
|
+
```javascript
|
|
512
|
+
import { verifyAuth } from '../middleware/auth.js';
|
|
513
|
+
|
|
514
|
+
router.get('/status', verifyAuth, (req, res) => {
|
|
515
|
+
// ... endpoint
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
router.post('/start', verifyAuth, requireRole('admin'), async (req, res) => {
|
|
519
|
+
// ... endpoint
|
|
520
|
+
});
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
---
|
|
524
|
+
|
|
525
|
+
## 📊 WebSocket para actualizaciones en tiempo real (Opcional)
|
|
526
|
+
|
|
527
|
+
Para mayor eficiencia, implementar WebSocket en lugar de polling:
|
|
528
|
+
|
|
529
|
+
```javascript
|
|
530
|
+
// socket.io integration
|
|
531
|
+
io.on('connection', (socket) => {
|
|
532
|
+
socket.on('subscribe-watcher', async () => {
|
|
533
|
+
// Enviar status inicial
|
|
534
|
+
socket.emit('watcher:status', watchService.getStats());
|
|
535
|
+
|
|
536
|
+
// Actualizar cada 5 segundos
|
|
537
|
+
const interval = setInterval(async () => {
|
|
538
|
+
socket.emit('watcher:status', watchService.getStats());
|
|
539
|
+
socket.emit('watcher:queue', await watchService.getQueueStats());
|
|
540
|
+
}, 5000);
|
|
541
|
+
|
|
542
|
+
socket.on('disconnect', () => clearInterval(interval));
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
---
|
|
548
|
+
|
|
549
|
+
## ✅ Checklist de Implementación
|
|
550
|
+
|
|
551
|
+
- [ ] Crear archivo `routes/watcher.routes.js` con endpoints
|
|
552
|
+
- [ ] Integrar rutas en `app.js`
|
|
553
|
+
- [ ] Exportar WatchService desde arela-uploader
|
|
554
|
+
- [ ] Importar en arela-api
|
|
555
|
+
- [ ] Agregar autenticación a endpoints
|
|
556
|
+
- [ ] Agregar validación de inputs
|
|
557
|
+
- [ ] Agregar logging y error handling
|
|
558
|
+
- [ ] Implementar caché en frontend (SWR, React Query)
|
|
559
|
+
- [ ] Agregar WebSocket para actualizaciones real-time (opcional)
|
|
560
|
+
- [ ] Documentar endpoints en OpenAPI/Swagger
|
|
561
|
+
- [ ] Crear tests de integración
|
|
562
|
+
|