@arela/uploader 0.2.8 → 0.2.9

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": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "description": "CLI to upload files/directories to Arela",
5
5
  "bin": {
6
6
  "arela": "./src/index.js"
@@ -28,10 +28,10 @@ class Config {
28
28
  const __dirname = path.dirname(__filename);
29
29
  const packageJsonPath = path.resolve(__dirname, '../../package.json');
30
30
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
31
- return packageJson.version || '0.2.8';
31
+ return packageJson.version || '0.2.9';
32
32
  } catch (error) {
33
33
  console.warn('⚠️ Could not read package.json version, using fallback');
34
- return '0.2.8';
34
+ return '0.2.9';
35
35
  }
36
36
  }
37
37
 
@@ -615,32 +615,66 @@ export class DatabaseService {
615
615
  : existingPath + '/';
616
616
 
617
617
  // Find all files with the same base path that don't have arela_path yet
618
- const { data: relatedFiles, error: relatedError } = await supabase
619
- .from('uploader')
620
- .select('id, filename, original_path')
621
- .like('original_path', `${basePath}%`)
622
- .is('arela_path', null)
623
- .neq('id', pedimento.id); // Exclude the pedimento itself
618
+ // Use pagination to handle cases with more than 1000 related files
619
+ let allRelatedFiles = [];
620
+ let from = 0;
621
+ const pageSize = 1000;
622
+ let hasMoreData = true;
623
+ let paginationError = null;
624
624
 
625
- if (relatedError) {
626
- logger.error(
627
- `Error finding related files for ${pedimento.filename}: ${relatedError.message}`,
628
- );
629
- totalErrors++;
625
+ logger.info(`Searching for related files in base path: ${basePath}`);
626
+
627
+ while (hasMoreData) {
628
+ const { data: relatedFilesPage, error: relatedError } = await supabase
629
+ .from('uploader')
630
+ .select('id, filename, original_path')
631
+ .like('original_path', `${basePath}%`)
632
+ .is('arela_path', null)
633
+ .neq('id', pedimento.id) // Exclude the pedimento itself
634
+ .range(from, from + pageSize - 1);
635
+
636
+ if (relatedError) {
637
+ logger.error(
638
+ `Error finding related files for ${pedimento.filename}: ${relatedError.message}`,
639
+ );
640
+ paginationError = relatedError;
641
+ totalErrors++;
642
+ break;
643
+ }
644
+
645
+ if (relatedFilesPage && relatedFilesPage.length > 0) {
646
+ allRelatedFiles = allRelatedFiles.concat(relatedFilesPage);
647
+
648
+ // Check if we got a full page, indicating there might be more data
649
+ if (relatedFilesPage.length < pageSize) {
650
+ hasMoreData = false;
651
+ } else {
652
+ from += pageSize;
653
+ logger.info(
654
+ `Fetched ${relatedFilesPage.length} files, continuing pagination (total so far: ${allRelatedFiles.length})`,
655
+ );
656
+ }
657
+ } else {
658
+ hasMoreData = false;
659
+ }
660
+ }
661
+
662
+ // Continue with error handling if pagination failed
663
+ if (paginationError) {
630
664
  continue;
631
665
  }
632
666
 
633
- if (!relatedFiles || relatedFiles.length === 0) {
667
+ if (!allRelatedFiles || allRelatedFiles.length === 0) {
634
668
  logger.info(`No related files found for ${pedimento.filename}`);
635
669
  continue;
636
670
  }
637
671
 
638
672
  logger.info(
639
- `Found ${relatedFiles.length} related files to update for ${pedimento.filename}`,
673
+ `Found ${allRelatedFiles.length} related files to update for ${pedimento.filename}`,
640
674
  );
641
675
 
642
676
  // Process files in batches
643
- const fileIds = relatedFiles.map((f) => f.id);
677
+ const fileIds = allRelatedFiles.map((f) => f.id);
644
678
 
645
679
  for (let i = 0; i < fileIds.length; i += BATCH_SIZE) {
646
680
  const batchIds = fileIds.slice(i, i + BATCH_SIZE);
@@ -1041,22 +1075,44 @@ export class DatabaseService {
1041
1075
  for (let i = 0; i < uniqueArelaPaths.length; i += chunkSize) {
1042
1076
  const pathChunk = uniqueArelaPaths.slice(i, i + chunkSize);
1043
1077
 
1044
- const { data: chunkFiles, error: chunkError } = await supabase
1045
- .from('uploader')
1046
- .select(
1047
- 'id, original_path, arela_path, filename, rfc, document_type, status',
1048
- )
1049
- .in('arela_path', pathChunk)
1050
- .neq('status', 'file-uploaded')
1051
- .not('original_path', 'is', null);
1078
+ // Query with pagination to get all results (Supabase default limit is 1000)
1079
+ let chunkFiles = [];
1080
+ let from = 0;
1081
+ const pageSize = 1000;
1082
+ let hasMoreData = true;
1052
1083
 
1053
- if (chunkError) {
1054
- throw new Error(
1055
- `Error querying files for arela_paths chunk: ${chunkError.message}`,
1056
- );
1084
+ while (hasMoreData) {
1085
+ const { data: pageData, error: chunkError } = await supabase
1086
+ .from('uploader')
1087
+ .select(
1088
+ 'id, original_path, arela_path, filename, rfc, document_type, status',
1089
+ )
1090
+ .in('arela_path', pathChunk)
1091
+ .neq('status', 'file-uploaded')
1092
+ .not('original_path', 'is', null)
1093
+ .range(from, from + pageSize - 1);
1094
+
1095
+ if (chunkError) {
1096
+ throw new Error(
1097
+ `Error querying files for arela_paths chunk: ${chunkError.message}`,
1098
+ );
1099
+ }
1100
+
1101
+ if (pageData && pageData.length > 0) {
1102
+ chunkFiles = chunkFiles.concat(pageData);
1103
+
1104
+ // Check if we got a full page, indicating there might be more data
1105
+ if (pageData.length < pageSize) {
1106
+ hasMoreData = false;
1107
+ } else {
1108
+ from += pageSize;
1109
+ }
1110
+ } else {
1111
+ hasMoreData = false;
1112
+ }
1057
1113
  }
1058
1114
 
1059
- if (chunkFiles && chunkFiles.length > 0) {
1115
+ if (chunkFiles.length > 0) {
1060
1116
  allReadyFiles = allReadyFiles.concat(chunkFiles);
1061
1117
  }
1062
1118
  }
@@ -1242,17 +1298,36 @@ export class DatabaseService {
1242
1298
  { uploadPath: uploadPath },
1243
1299
  );
1244
1300
 
1245
- // Update database status
1246
- await supabase
1247
- .from('uploader')
1248
- .update({
1249
- status: 'file-uploaded',
1250
- message: 'Successfully uploaded to Supabase',
1251
- })
1252
- .eq('id', file.id);
1301
+ // Check upload result before updating database status
1302
+ if (uploadResult.success) {
1303
+ await supabase
1304
+ .from('uploader')
1305
+ .update({
1306
+ status: 'file-uploaded',
1307
+ message: 'Successfully uploaded to Supabase',
1308
+ })
1309
+ .eq('id', file.id);
1310
+
1311
+ logger.info(`✅ Uploaded: ${file.filename}`);
1312
+ return { success: true, filename: file.filename };
1313
+ } else {
1314
+ await supabase
1315
+ .from('uploader')
1316
+ .update({
1317
+ status: 'upload-error',
1318
+ message: `Upload failed: ${uploadResult.error}`,
1319
+ })
1320
+ .eq('id', file.id);
1253
1321
 
1254
- logger.info(`✅ Uploaded: ${file.filename}`);
1255
- return { success: true, filename: file.filename };
1322
+ logger.error(
1323
+ `❌ Upload failed: ${file.filename} - ${uploadResult.error}`,
1324
+ );
1325
+ return {
1326
+ success: false,
1327
+ error: uploadResult.error,
1328
+ filename: file.filename,
1329
+ };
1330
+ }
1256
1331
  } catch (error) {
1257
1332
  logger.error(
1258
1333
  `❌ Error processing file ${file.filename}: ${error.message}`,
@@ -1335,23 +1410,188 @@ export class DatabaseService {
1335
1410
  { folderStructure: fullFolderStructure },
1336
1411
  );
1337
1412
 
1338
- if (uploadResult.success) {
1339
- // Update all files as uploaded
1340
- const fileIds = validFiles.map((f) => f.dbRecord.id);
1341
- await supabase
1342
- .from('uploader')
1343
- .update({
1344
- status: 'file-uploaded',
1345
- message: 'Successfully uploaded to Arela API (batch)',
1346
- })
1347
- .in('id', fileIds);
1413
+ if (uploadResult.success && uploadResult.data) {
1414
+ const apiResult = uploadResult.data;
1348
1415
 
1349
- uploaded += validFiles.length;
1350
1416
  logger.info(
1351
- `✅ Batch uploaded: ${validFiles.length} files to ${fullFolderStructure}`,
1417
+ `📋 Processing API response: ${apiResult.uploaded?.length || 0} uploaded, ${apiResult.errors?.length || 0} errors`,
1418
+ );
1419
+
1420
+ // Debug logging to understand API response structure
1421
+ logger.debug(
1422
+ `🔍 API Response structure: ${JSON.stringify(apiResult, null, 2)}`,
1352
1423
  );
1424
+ if (apiResult.uploaded && apiResult.uploaded.length > 0) {
1425
+ logger.debug(
1426
+ `🔍 First uploaded file structure: ${JSON.stringify(apiResult.uploaded[0], null, 2)}`,
1427
+ );
1428
+ }
1429
+
1430
+ // Create filename to file mapping for quick lookup
1431
+ const fileNameToRecord = new Map();
1432
+ validFiles.forEach((f) => {
1433
+ fileNameToRecord.set(f.fileData.name, f.dbRecord);
1434
+ });
1435
+
1436
+ // Debug: Log expected filenames
1437
+ logger.debug(
1438
+ `🔍 Expected filenames: ${Array.from(fileNameToRecord.keys()).join(', ')}`,
1439
+ );
1440
+
1441
+ // Handle successfully uploaded files
1442
+ if (apiResult.uploaded && apiResult.uploaded.length > 0) {
1443
+ const successfulFileIds = [];
1444
+ const matchedFilenames = [];
1445
+
1446
+ apiResult.uploaded.forEach((uploadedFile) => {
1447
+ // Try multiple possible property names for filename
1448
+ const possibleFilename =
1449
+ uploadedFile.fileName ||
1450
+ uploadedFile.filename ||
1451
+ uploadedFile.name ||
1452
+ uploadedFile.file_name ||
1453
+ uploadedFile.originalName ||
1454
+ uploadedFile.original_name;
1455
+
1456
+ logger.debug(
1457
+ `🔍 Trying to match uploaded file: ${JSON.stringify(uploadedFile)}`,
1458
+ );
1459
+
1460
+ const dbRecord = fileNameToRecord.get(possibleFilename);
1461
+ if (dbRecord) {
1462
+ successfulFileIds.push(dbRecord.id);
1463
+ matchedFilenames.push(possibleFilename);
1464
+ logger.debug(`✅ Matched file: ${possibleFilename}`);
1465
+ } else {
1466
+ logger.warn(
1467
+ `⚠️ Could not match uploaded file with any known filename: ${JSON.stringify(uploadedFile)}`,
1468
+ );
1469
+ }
1470
+ });
1471
+
1472
+ // If no individual files matched but API indicates success, use fallback
1473
+ if (
1474
+ successfulFileIds.length === 0 &&
1475
+ apiResult.uploaded.length > 0
1476
+ ) {
1477
+ logger.warn(
1478
+ `🔄 Fallback: No individual file matches found, but API indicates ${apiResult.uploaded.length} uploads. Marking all ${validFiles.length} batch files as uploaded.`,
1479
+ );
1480
+ validFiles.forEach((f) => successfulFileIds.push(f.dbRecord.id));
1481
+ }
1482
+
1483
+ if (successfulFileIds.length > 0) {
1484
+ await supabase
1485
+ .from('uploader')
1486
+ .update({
1487
+ status: 'file-uploaded',
1488
+ message: 'Successfully uploaded to Arela API (batch)',
1489
+ })
1490
+ .in('id', successfulFileIds);
1491
+
1492
+ uploaded += successfulFileIds.length;
1493
+ logger.info(
1494
+ `✅ Batch upload successful: ${successfulFileIds.length} files uploaded`,
1495
+ );
1496
+ }
1497
+ }
1498
+
1499
+ // Handle failed files
1500
+ if (apiResult.errors && apiResult.errors.length > 0) {
1501
+ const failedFileIds = [];
1502
+
1503
+ apiResult.errors.forEach((errorInfo) => {
1504
+ // Try multiple possible property names for filename in errors
1505
+ const possibleFilename =
1506
+ errorInfo.fileName ||
1507
+ errorInfo.filename ||
1508
+ errorInfo.name ||
1509
+ errorInfo.file_name ||
1510
+ errorInfo.originalName ||
1511
+ errorInfo.original_name;
1512
+
1513
+ const dbRecord = fileNameToRecord.get(possibleFilename);
1514
+ if (dbRecord) {
1515
+ failedFileIds.push(dbRecord.id);
1516
+ } else {
1517
+ logger.warn(
1518
+ `⚠️ Could not match error file: ${JSON.stringify(errorInfo)}`,
1519
+ );
1520
+ }
1521
+ });
1522
+
1523
+ if (failedFileIds.length > 0) {
1524
+ await supabase
1525
+ .from('uploader')
1526
+ .update({
1527
+ status: 'upload-error',
1528
+ message: `Upload failed: ${apiResult.errors[0].error}`,
1529
+ })
1530
+ .in('id', failedFileIds);
1531
+
1532
+ errors += failedFileIds.length;
1533
+ logger.error(
1534
+ `❌ Batch upload errors: ${failedFileIds.length} files failed`,
1535
+ );
1536
+ }
1537
+ }
1538
+
1539
+ // Handle any remaining files that weren't in uploaded or errors arrays
1540
+ // Use robust filename extraction for both uploaded and error files
1541
+ const extractFilename = (fileObj) => {
1542
+ return (
1543
+ fileObj.fileName ||
1544
+ fileObj.filename ||
1545
+ fileObj.name ||
1546
+ fileObj.file_name ||
1547
+ fileObj.originalName ||
1548
+ fileObj.original_name
1549
+ );
1550
+ };
1551
+
1552
+ const processedFileNames = new Set([
1553
+ ...(apiResult.uploaded || []).map(extractFilename).filter(Boolean),
1554
+ ...(apiResult.errors || []).map(extractFilename).filter(Boolean),
1555
+ ]);
1556
+
1557
+ const unprocessedFiles = validFiles.filter(
1558
+ (f) => !processedFileNames.has(f.fileData.name),
1559
+ );
1560
+ if (unprocessedFiles.length > 0) {
1561
+ // Only mark as unprocessed if we haven't already handled all files through fallback logic
1562
+ // If we used fallback (all files marked as uploaded), don't mark any as unprocessed
1563
+ const alreadyHandledCount = uploaded + errors;
1564
+ const shouldMarkUnprocessed =
1565
+ alreadyHandledCount < validFiles.length;
1566
+
1567
+ if (shouldMarkUnprocessed) {
1568
+ const unprocessedIds = unprocessedFiles.map((f) => f.dbRecord.id);
1569
+ await supabase
1570
+ .from('uploader')
1571
+ .update({
1572
+ status: 'upload-error',
1573
+ message: 'File not found in API response',
1574
+ })
1575
+ .in('id', unprocessedIds);
1576
+
1577
+ errors += unprocessedIds.length;
1578
+ logger.warn(
1579
+ `⚠️ Unprocessed files: ${unprocessedIds.length} files not found in API response`,
1580
+ );
1581
+ logger.debug(
1582
+ `🔍 API response uploaded array: ${JSON.stringify(apiResult.uploaded)}`,
1583
+ );
1584
+ logger.debug(
1585
+ `🔍 Expected filenames: ${validFiles.map((f) => f.fileData.name).join(', ')}`,
1586
+ );
1587
+ } else {
1588
+ logger.debug(
1589
+ `✅ All files already handled (uploaded: ${uploaded}, errors: ${errors}), skipping unprocessed marking`,
1590
+ );
1591
+ }
1592
+ }
1353
1593
  } else {
1354
- // Update all files as failed
1594
+ // Complete batch failure - mark all files as failed
1355
1595
  const fileIds = validFiles.map((f) => f.dbRecord.id);
1356
1596
  await supabase
1357
1597
  .from('uploader')
@@ -1363,7 +1603,7 @@ export class DatabaseService {
1363
1603
 
1364
1604
  errors += validFiles.length;
1365
1605
  logger.error(
1366
- `❌ Batch upload failed: ${validFiles.length} files - ${uploadResult.error}`,
1606
+ `❌ Complete batch failure: ${validFiles.length} files - ${uploadResult.error}`,
1367
1607
  );
1368
1608
  }
1369
1609
  }
@@ -134,8 +134,16 @@ export class ApiUploadService extends BaseUploadService {
134
134
 
135
135
  const result = await response.json();
136
136
 
137
+ // Determine if the operation was successful
138
+ // Success means at least one file was uploaded successfully, even if some failed
139
+ const hasUploads = result.uploaded && result.uploaded.length > 0;
140
+ const hasErrors = result.errors && result.errors.length > 0;
141
+ // Consider it a success if there are uploads, or if there are no errors at all
142
+ const success =
143
+ hasUploads || (!hasErrors && result.stats?.totalFiles > 0);
144
+
137
145
  // Normalize response format to match DatabaseService expectations
138
- return { success: true, data: result };
146
+ return { success, data: result };
139
147
  } catch (fetchError) {
140
148
  // Return normalized error format
141
149
  return { success: false, error: fetchError.message };
@@ -88,10 +88,10 @@ export class SupabaseUploadService extends BaseUploadService {
88
88
  });
89
89
 
90
90
  if (error) {
91
- throw new Error(error.message);
91
+ return { success: false, error: error.message };
92
92
  }
93
93
 
94
- return data;
94
+ return { success: true, data };
95
95
  }
96
96
 
97
97
  /**