@arela/uploader 1.0.5 → 1.0.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arela/uploader",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "CLI to upload files/directories to Arela",
5
5
  "bin": {
6
6
  "arela": "./src/index.js"
@@ -272,18 +272,17 @@ export class PushCommand {
272
272
  break;
273
273
  }
274
274
 
275
- // Upload files in smaller batches
275
+ // Upload files in smaller batches using new CLI upload endpoint
276
276
  for (let i = 0; i < files.length; i += uploadBatchSize) {
277
277
  const uploadBatch = files.slice(i, i + uploadBatchSize);
278
- const batchResults = await this.#uploadBatch(
278
+ const batchResults = await this.#uploadBatchViaCli(
279
+ tableName,
279
280
  uploadBatch,
280
281
  uploadApiConfig,
281
282
  );
282
283
 
283
- // Update results in database
284
- await this.scanApiService.batchUpdateUpload(tableName, batchResults);
285
-
286
- // Update counters
284
+ // Update counters from API response
285
+ // Note: The CLI endpoint now handles updating the scan table directly
287
286
  batchResults.forEach((result) => {
288
287
  results.processed++;
289
288
  if (result.uploaded) {
@@ -317,8 +316,160 @@ export class PushCommand {
317
316
  }
318
317
 
319
318
  /**
320
- * Upload a batch of files
319
+ * Upload a batch of files using the new CLI upload endpoint
320
+ * The endpoint updates the CLI scan table directly
321
+ * @private
322
+ */
323
+ async #uploadBatchViaCli(tableName, files, uploadApiConfig) {
324
+ const pushConfig = appConfig.getPushConfig();
325
+ const results = [];
326
+
327
+ // Process files one by one (simpler for now, can optimize to true batch later)
328
+ for (const file of files) {
329
+ const result = await this.#uploadFileViaCli(
330
+ tableName,
331
+ file,
332
+ uploadApiConfig,
333
+ pushConfig,
334
+ );
335
+ results.push(result);
336
+ }
337
+
338
+ return results;
339
+ }
340
+
341
+ /**
342
+ * Upload a single file using the CLI upload endpoint
343
+ * @private
344
+ */
345
+ async #uploadFileViaCli(tableName, file, uploadApiConfig, pushConfig) {
346
+ const result = {
347
+ id: file.id,
348
+ uploaded: false,
349
+ uploadError: null,
350
+ uploadPath: null,
351
+ uploadedToStorageId: null,
352
+ };
353
+
354
+ try {
355
+ // Check if file exists
356
+ if (!fs.existsSync(file.absolute_path)) {
357
+ result.uploadError =
358
+ 'FILE_NOT_FOUND: File does not exist on filesystem';
359
+ // Update the scan table with the error
360
+ await this.scanApiService.batchUpdateUpload(tableName, [result]);
361
+ return result;
362
+ }
363
+
364
+ // Get file stats
365
+ const stats = fs.statSync(file.absolute_path);
366
+ if (!stats.isFile()) {
367
+ result.uploadError = 'NOT_A_FILE: Path is not a regular file';
368
+ await this.scanApiService.batchUpdateUpload(tableName, [result]);
369
+ return result;
370
+ }
371
+
372
+ // Construct upload path using arela_path
373
+ // arela_path format: RFC/Year/Patente/Aduana/Pedimento/
374
+ const uploadPath = `${file.arela_path}${file.file_name}`;
375
+ result.uploadPath = uploadPath;
376
+
377
+ // Create form data for CLI upload endpoint
378
+ const form = new FormData();
379
+
380
+ // Encode fileId and folderStructure in the filename
381
+ // Format: [fileId][folderStructure]filename
382
+ const folderStructure = file.arela_path.endsWith('/')
383
+ ? file.arela_path.slice(0, -1)
384
+ : file.arela_path;
385
+
386
+ const encodedFilename = `[${file.id}][${folderStructure}]${file.file_name}`;
387
+
388
+ // Create a read stream with the encoded filename
389
+ const fileStream = fs.createReadStream(file.absolute_path);
390
+ form.append('files', fileStream, {
391
+ filename: encodedFilename,
392
+ contentType: this.#getMimeType(file.file_extension),
393
+ });
394
+
395
+ // Add required fields for CLI upload
396
+ form.append('tableName', tableName);
397
+ form.append('rfc', file.rfc);
398
+ form.append('bucket', pushConfig.bucket);
399
+ form.append('autoDetect', 'true');
400
+ form.append('autoOrganize', 'false');
401
+ form.append('batchSize', '1');
402
+ form.append('clientVersion', appConfig.packageVersion);
403
+
404
+ // Upload file using new CLI upload endpoint
405
+ const response = await fetch(
406
+ `${uploadApiConfig.baseUrl}/api/storage/cli-upload`,
407
+ {
408
+ method: 'POST',
409
+ headers: {
410
+ 'x-api-key': uploadApiConfig.token,
411
+ ...form.getHeaders(),
412
+ },
413
+ body: form,
414
+ },
415
+ );
416
+
417
+ if (!response.ok) {
418
+ const errorText = await response.text();
419
+ result.uploadError = `HTTP ${response.status}: ${errorText}`;
420
+ logger.error(`✗ Failed: ${file.file_name} - ${result.uploadError}`);
421
+ return result;
422
+ }
423
+
424
+ const apiResult = await response.json();
425
+
426
+ // Check response from CLI upload endpoint
427
+ if (apiResult.uploaded && apiResult.uploaded.length > 0) {
428
+ const uploadedFile = apiResult.uploaded[0];
429
+ result.uploaded = true;
430
+ result.uploadedToStorageId = uploadedFile.storageId;
431
+ logger.info(`✓ Uploaded: ${file.file_name} → ${uploadPath}`);
432
+ } else if (apiResult.errors && apiResult.errors.length > 0) {
433
+ const error = apiResult.errors[0];
434
+ result.uploadError = `UPLOAD_FAILED: ${error.error || 'Upload failed'}`;
435
+ logger.error(`✗ Failed: ${file.file_name} - ${result.uploadError}`);
436
+ } else {
437
+ result.uploadError = 'Unknown upload error - no files uploaded';
438
+ logger.error(`✗ Failed: ${file.file_name} - ${result.uploadError}`);
439
+ }
440
+ } catch (error) {
441
+ result.uploadError = `UPLOAD_ERROR: ${error.message}`;
442
+ logger.error(`✗ Error uploading ${file.file_name}:`, error.message);
443
+ }
444
+
445
+ return result;
446
+ }
447
+
448
+ /**
449
+ * Get MIME type from file extension
450
+ * @private
451
+ */
452
+ #getMimeType(extension) {
453
+ const mimeTypes = {
454
+ pdf: 'application/pdf',
455
+ doc: 'application/msword',
456
+ docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
457
+ xls: 'application/vnd.ms-excel',
458
+ xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
459
+ txt: 'text/plain',
460
+ jpg: 'image/jpeg',
461
+ jpeg: 'image/jpeg',
462
+ png: 'image/png',
463
+ gif: 'image/gif',
464
+ xml: 'application/xml',
465
+ };
466
+ return mimeTypes[extension?.toLowerCase()] || 'application/octet-stream';
467
+ }
468
+
469
+ /**
470
+ * Upload a batch of files (legacy - kept for compatibility)
321
471
  * @private
472
+ * @deprecated Use #uploadBatchViaCli instead
322
473
  */
323
474
  async #uploadBatch(files, uploadApiConfig) {
324
475
  const uploadPromises = files.map((file) =>
@@ -34,10 +34,10 @@ class Config {
34
34
  const __dirname = path.dirname(__filename);
35
35
  const packageJsonPath = path.resolve(__dirname, '../../package.json');
36
36
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
37
- return packageJson.version || '1.0.5';
37
+ return packageJson.version || '1.0.6';
38
38
  } catch (error) {
39
39
  console.warn('⚠️ Could not read package.json version, using fallback');
40
- return '1.0.5';
40
+ return '1.0.6';
41
41
  }
42
42
  }
43
43
 
@@ -189,7 +189,7 @@ export class PathNormalizer {
189
189
 
190
190
  const basePrefix = 'scan_';
191
191
  const prefix = `${basePrefix}${sanitizedCompany}_${sanitizedServer}_`;
192
-
192
+
193
193
  // Check if even prefix + hash exceeds limit
194
194
  if (prefix.length + hash.length > 63) {
195
195
  // Company/server names are too long, need to truncate them too
@@ -197,15 +197,15 @@ export class PathNormalizer {
197
197
  const halfLength = Math.floor(maxCompanyServerLength / 2);
198
198
  const companyLength = halfLength;
199
199
  const serverLength = maxCompanyServerLength - companyLength;
200
-
200
+
201
201
  const truncatedCompany = sanitizedCompany.substring(0, companyLength);
202
202
  const truncatedServer = sanitizedServer.substring(0, serverLength);
203
-
203
+
204
204
  tableName = `${basePrefix}${truncatedCompany}_${truncatedServer}_${hash}`;
205
205
  } else {
206
206
  // Preserve start and end of path, put hash in middle
207
207
  const availableSpace = 63 - prefix.length - hash.length - 2; // -2 for underscores around hash
208
-
208
+
209
209
  if (availableSpace <= 0 || !sanitizedPath) {
210
210
  // If no space for path or path is empty, just use hash
211
211
  tableName = `${prefix}${hash}`;
@@ -217,11 +217,13 @@ export class PathNormalizer {
217
217
  const halfSpace = Math.floor(availableSpace / 2);
218
218
  const startLength = halfSpace;
219
219
  const endLength = availableSpace - startLength;
220
-
220
+
221
221
  // Extract start and end portions of the sanitized path
222
222
  const pathStart = sanitizedPath.substring(0, startLength);
223
- const pathEnd = sanitizedPath.substring(sanitizedPath.length - endLength);
224
-
223
+ const pathEnd = sanitizedPath.substring(
224
+ sanitizedPath.length - endLength,
225
+ );
226
+
225
227
  // Build table name with start, hash, and end
226
228
  tableName = `${prefix}${pathStart}_${hash}_${pathEnd}`;
227
229
  }