From e030871b4e81b5368c5dd7f2aa309661dfb4bdb0 Mon Sep 17 00:00:00 2001 From: David Date: Mon, 17 Nov 2025 20:02:49 +0100 Subject: [PATCH] fix: handle missing CSV files gracefully in rate search with Promise.allSettled --- .../services/csv-rate-search.service.ts | 23 +++++++++- .../src/scripts/delete-orphaned-csv-config.ts | 42 +++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 apps/backend/src/scripts/delete-orphaned-csv-config.ts diff --git a/apps/backend/src/domain/services/csv-rate-search.service.ts b/apps/backend/src/domain/services/csv-rate-search.service.ts index ef61648..f3cd6b3 100644 --- a/apps/backend/src/domain/services/csv-rate-search.service.ts +++ b/apps/backend/src/domain/services/csv-rate-search.service.ts @@ -143,7 +143,20 @@ export class CsvRateSearchService implements SearchCsvRatesPort { // Pass company name from config to override CSV column value return this.csvRateLoader.loadRatesFromCsv(config.csvFilePath, email, config.companyName); }); - const rateArrays = await Promise.all(ratePromises); + + // Use allSettled to handle missing files gracefully + const results = await Promise.allSettled(ratePromises); + const rateArrays = results + .filter((result): result is PromiseFulfilledResult => result.status === 'fulfilled') + .map(result => result.value); + + // Log any failed file loads + const failures = results.filter(result => result.status === 'rejected'); + if (failures.length > 0) { + console.warn(`Failed to load ${failures.length} CSV files:`, + failures.map((f, idx) => `${configs[idx]?.csvFilePath}: ${(f as PromiseRejectedResult).reason}`)); + } + return rateArrays.flat(); } @@ -152,7 +165,13 @@ export class CsvRateSearchService implements SearchCsvRatesPort { const ratePromises = files.map(file => this.csvRateLoader.loadRatesFromCsv(file, 'bookings@example.com') ); - const rateArrays = await Promise.all(ratePromises); + + // Use allSettled here too for consistency + const results = await Promise.allSettled(ratePromises); + const rateArrays = results + .filter((result): result is PromiseFulfilledResult => result.status === 'fulfilled') + .map(result => result.value); + return rateArrays.flat(); } diff --git a/apps/backend/src/scripts/delete-orphaned-csv-config.ts b/apps/backend/src/scripts/delete-orphaned-csv-config.ts new file mode 100644 index 0000000..a3093ca --- /dev/null +++ b/apps/backend/src/scripts/delete-orphaned-csv-config.ts @@ -0,0 +1,42 @@ +import { NestFactory } from '@nestjs/core'; +import { AppModule } from '../app.module'; +import { CsvRateConfigRepository } from '@infrastructure/persistence/typeorm/repositories/csv-rate-config.repository'; + +/** + * Script to delete orphaned CSV rate configuration + * Usage: npm run ts-node src/scripts/delete-orphaned-csv-config.ts + */ +async function deleteOrphanedConfig() { + const app = await NestFactory.createApplicationContext(AppModule); + const repository = app.get(CsvRateConfigRepository); + + try { + console.log('🔍 Searching for orphaned test.csv configuration...'); + + const configs = await repository.findAll(); + const orphanedConfig = configs.find(c => c.csvFilePath === 'test.csv'); + + if (!orphanedConfig) { + console.log('✅ No orphaned test.csv configuration found'); + await app.close(); + return; + } + + console.log(`📄 Found orphaned config: ${orphanedConfig.companyName} - ${orphanedConfig.csvFilePath}`); + console.log(` ID: ${orphanedConfig.id}`); + console.log(` Uploaded: ${orphanedConfig.uploadedAt}`); + + // Delete the orphaned configuration + await repository.delete(orphanedConfig.companyName); + + console.log('✅ Successfully deleted orphaned test.csv configuration'); + + } catch (error: any) { + console.error('❌ Error deleting orphaned config:', error.message); + process.exit(1); + } + + await app.close(); +} + +deleteOrphanedConfig();