fix: generate CSV filename from company name instead of using multer callback
Some checks failed
CI/CD Pipeline / Backend - Build, Test & Push (push) Failing after 1m32s
CI/CD Pipeline / Frontend - Build, Test & Push (push) Successful in 11m35s
CI/CD Pipeline / Integration Tests (push) Has been skipped
CI/CD Pipeline / Deployment Summary (push) Has been skipped
CI/CD Pipeline / Discord Notification (Failure) (push) Has been skipped
CI/CD Pipeline / Discord Notification (Success) (push) Has been skipped

Fixed CSV file upload to properly generate filename based on company name. The previous implementation tried to read `req.body.companyName` in multer's filename callback, but the body is not yet parsed at that point, causing files to be named "unknown.csv".

## Solution
1. Use temporary filename during upload (timestamp + random)
2. After validation and parsing, rename file to proper company name format
3. Delete old file if it exists before renaming
4. Store final filename in database configuration

## Changes
- Multer filename callback now generates temporary filename
- Added file renaming logic after successful validation
- Updated database records to use final filename instead of temp name
- Added logging for file operations

## Impact
- New CSV uploads will have correct filenames (e.g., "ssc-consolidation.csv")
- No more "unknown.csv" files
- Existing "unknown.csv" needs to be manually deleted via dashboard

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
David 2025-11-17 19:57:10 +01:00
parent aeb3d2a75d
commit f5eabf4861

View File

@ -70,14 +70,12 @@ export class CsvRatesAdminController {
storage: diskStorage({ storage: diskStorage({
destination: './apps/backend/src/infrastructure/storage/csv-storage/rates', destination: './apps/backend/src/infrastructure/storage/csv-storage/rates',
filename: (req, file, cb) => { filename: (req, file, cb) => {
// Generate filename: company-name.csv // Use timestamp + random string to avoid conflicts
const companyName = req.body.companyName || 'unknown'; // We'll rename it later once we have the company name from req.body
const sanitized = companyName const timestamp = Date.now();
.toLowerCase() const randomStr = Math.random().toString(36).substring(7);
.replace(/\s+/g, '-') const tempFilename = `temp-${timestamp}-${randomStr}.csv`;
.replace(/[^a-z0-9-]/g, ''); cb(null, tempFilename);
const filename = `${sanitized}.csv`;
cb(null, filename);
}, },
}), }),
fileFilter: (req, file, cb) => { fileFilter: (req, file, cb) => {
@ -147,9 +145,16 @@ export class CsvRatesAdminController {
} }
try { try {
// Generate final filename based on company name
const sanitizedCompanyName = dto.companyName
.toLowerCase()
.replace(/\s+/g, '-')
.replace(/[^a-z0-9-]/g, '');
const finalFilename = `${sanitizedCompanyName}.csv`;
// Auto-convert CSV if needed (FOB FRET → Standard format) // Auto-convert CSV if needed (FOB FRET → Standard format)
const conversionResult = await this.csvConverter.autoConvert(file.path, dto.companyName); const conversionResult = await this.csvConverter.autoConvert(file.path, dto.companyName);
const filePathToValidate = conversionResult.convertedPath; let filePathToValidate = conversionResult.convertedPath;
if (conversionResult.wasConverted) { if (conversionResult.wasConverted) {
this.logger.log( this.logger.log(
@ -177,13 +182,28 @@ export class CsvRatesAdminController {
this.logger.log(`Successfully parsed ${ratesCount} rates from ${file.filename}`); this.logger.log(`Successfully parsed ${ratesCount} rates from ${file.filename}`);
// Rename file to final name (company-name.csv)
const fs = require('fs');
const path = require('path');
const finalPath = path.join(path.dirname(filePathToValidate), finalFilename);
// Delete old file if exists
if (fs.existsSync(finalPath)) {
fs.unlinkSync(finalPath);
this.logger.log(`Deleted old file: ${finalFilename}`);
}
// Rename temp file to final name
fs.renameSync(filePathToValidate, finalPath);
this.logger.log(`Renamed ${file.filename} to ${finalFilename}`);
// Check if config exists for this company // Check if config exists for this company
const existingConfig = await this.csvConfigRepository.findByCompanyName(dto.companyName); const existingConfig = await this.csvConfigRepository.findByCompanyName(dto.companyName);
if (existingConfig) { if (existingConfig) {
// Update existing configuration // Update existing configuration
await this.csvConfigRepository.update(existingConfig.id, { await this.csvConfigRepository.update(existingConfig.id, {
csvFilePath: file.filename, csvFilePath: finalFilename,
uploadedAt: new Date(), uploadedAt: new Date(),
uploadedBy: user.id, uploadedBy: user.id,
rowCount: ratesCount, rowCount: ratesCount,
@ -204,7 +224,7 @@ export class CsvRatesAdminController {
// Create new configuration // Create new configuration
await this.csvConfigRepository.create({ await this.csvConfigRepository.create({
companyName: dto.companyName, companyName: dto.companyName,
csvFilePath: file.filename, csvFilePath: finalFilename,
type: 'CSV_ONLY', type: 'CSV_ONLY',
hasApi: false, hasApi: false,
apiConnector: null, apiConnector: null,
@ -226,7 +246,7 @@ export class CsvRatesAdminController {
return { return {
success: true, success: true,
ratesCount, ratesCount,
csvFilePath: file.filename, csvFilePath: finalFilename,
companyName: dto.companyName, companyName: dto.companyName,
uploadedAt: new Date(), uploadedAt: new Date(),
}; };