@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
package/src/config/config.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
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
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
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 (!
|
|
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 ${
|
|
673
|
+
`Found ${allRelatedFiles.length} related files to update for ${pedimento.filename}`,
|
|
640
674
|
);
|
|
641
675
|
|
|
642
676
|
// Process files in batches
|
|
643
|
-
const fileIds =
|
|
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
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
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
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
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
|
|
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
|
-
//
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
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
|
-
|
|
1255
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
`❌
|
|
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
|
|
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
|
-
|
|
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
|
/**
|