Skip to content

Commit 41a50a7

Browse files
authored
Merge pull request #9 from jembi/error_handling_csv_upload
Added detailed error handling
2 parents e3e5994 + 07a044f commit 41a50a7

File tree

2 files changed

+204
-131
lines changed

2 files changed

+204
-131
lines changed

src/routes/index.ts

+92-64
Original file line numberDiff line numberDiff line change
@@ -3,101 +3,129 @@ import multer from 'multer';
33
import { getConfig } from '../config/config';
44
import { getCsvHeaders } from '../utils/file-validators';
55
import logger from '../logger';
6-
import fs from 'fs';
6+
import fs from 'fs/promises';
77
import path from 'path';
88
import { uploadToMinio } from '../utils/minioClient';
9-
import e from 'express';
10-
const routes = express.Router();
119

10+
// Constants
11+
const VALID_MIME_TYPES = ['text/csv', 'application/json'] as const;
12+
type ValidMimeType = typeof VALID_MIME_TYPES[number];
13+
14+
interface UploadResponse {
15+
status: 'success' | 'error';
16+
code: string;
17+
message: string;
18+
}
19+
20+
const routes = express.Router();
1221
const bodySizeLimit = getConfig().bodySizeLimit;
13-
const jsonBodyParser = express.json({
14-
type: 'application/json',
15-
limit: bodySizeLimit,
22+
const upload = multer({
23+
storage: multer.memoryStorage(),
24+
fileFilter: (_, file, cb) => {
25+
cb(null, VALID_MIME_TYPES.includes(file.mimetype as ValidMimeType));
26+
}
1627
});
1728

18-
const upload = multer({ storage: multer.memoryStorage() });
29+
// Helper functions
30+
const createErrorResponse = (code: string, message: string): UploadResponse => ({
31+
status: 'error',
32+
code,
33+
message
34+
});
1935

20-
const saveCsvToTmp = (fileBuffer: Buffer, fileName: string): string => {
36+
const createSuccessResponse = (code: string, message: string): UploadResponse => ({
37+
status: 'success',
38+
code,
39+
message
40+
});
41+
42+
const saveCsvToTmp = async (fileBuffer: Buffer, fileName: string): Promise<string> => {
2143
const tmpDir = path.join(process.cwd(), 'tmp');
22-
23-
// Create tmp directory if it doesn't exist
24-
if (!fs.existsSync(tmpDir)) {
25-
fs.mkdirSync(tmpDir);
26-
}
44+
await fs.mkdir(tmpDir, { recursive: true });
2745

2846
const fileUrl = path.join(tmpDir, fileName);
29-
fs.writeFileSync(fileUrl, fileBuffer);
30-
logger.info(`fileUrl: ${fileUrl}`);
47+
await fs.writeFile(fileUrl, fileBuffer);
48+
logger.info(`File saved: ${fileUrl}`);
3149

3250
return fileUrl;
3351
};
3452

35-
const isValidFileType = (file: Express.Multer.File): boolean => {
36-
const validMimeTypes = ['text/csv', 'application/json'];
37-
return validMimeTypes.includes(file.mimetype);
38-
};
39-
40-
function validateJsonFile(buffer: Buffer): boolean {
53+
const validateJsonFile = (buffer: Buffer): boolean => {
4154
try {
4255
JSON.parse(buffer.toString());
4356
return true;
4457
} catch {
4558
return false;
4659
}
47-
}
48-
49-
routes.post('/upload', upload.single('file'), async (req, res) => {
50-
const file = req.file;
51-
const bucket = req.query.bucket;
60+
};
5261

53-
if (!file) {
54-
logger.error('No file uploaded');
55-
return res.status(400).send('No file uploaded');
62+
// File handlers
63+
const handleCsvFile = async (
64+
file: Express.Multer.File,
65+
bucket: string
66+
): Promise<UploadResponse> => {
67+
const headers = getCsvHeaders(file.buffer);
68+
if (!headers) {
69+
return createErrorResponse('INVALID_CSV_FORMAT', 'Invalid CSV file format');
5670
}
5771

58-
if (!bucket) {
59-
logger.error('No bucket provided');
60-
return res.status(400).send('No bucket provided');
72+
const fileUrl = await saveCsvToTmp(file.buffer, file.originalname);
73+
try {
74+
const uploadResult = await uploadToMinio(fileUrl, file.originalname, bucket, file.mimetype);
75+
await fs.unlink(fileUrl);
76+
77+
return uploadResult.success
78+
? createSuccessResponse('UPLOAD_SUCCESS', uploadResult.message)
79+
: createErrorResponse('UPLOAD_FAILED', uploadResult.message);
80+
} catch (error) {
81+
logger.error('Error uploading file to Minio:', error);
82+
throw error;
6183
}
84+
};
6285

63-
if (!isValidFileType(file)) {
64-
logger.error(`Invalid file type: ${file.mimetype}`);
65-
return res.status(400).send('Invalid file type. Please upload either a CSV or JSON file');
86+
const handleJsonFile = (file: Express.Multer.File): UploadResponse => {
87+
if (!validateJsonFile(file.buffer)) {
88+
return createErrorResponse('INVALID_JSON_FORMAT', 'Invalid JSON file format');
6689
}
90+
return createSuccessResponse('JSON_VALID', 'JSON file is valid - Future implementation');
91+
};
6792

68-
// For CSV files, validate headers
69-
if (file.mimetype === 'text/csv') {
70-
const headers = getCsvHeaders(file.buffer);
71-
if (!headers) {
72-
return res.status(400).send('Invalid CSV file format');
73-
}
74-
const fileUrl = saveCsvToTmp(file.buffer, file.originalname);
75-
try {
76-
const uploadResult = await uploadToMinio(fileUrl, file.originalname, bucket as string, file.mimetype);
77-
// Clean up the temporary file
78-
fs.unlinkSync(fileUrl);
79-
80-
if (uploadResult) {
81-
return res.status(201).send(`File ${file.originalname} uploaded in bucket ${bucket}`);
82-
} else {
83-
return res.status(400).send(`Object ${file.originalname} already exists in bucket ${bucket}`);
84-
}
85-
} catch (error) {
86-
// Clean up the temporary file in case of error
87-
fs.unlinkSync(fileUrl);
88-
logger.error('Error uploading file to Minio:', error);
89-
return res.status(500).send('Error uploading file');
93+
// Main route handler
94+
routes.post('/upload', upload.single('file'), async (req, res) => {
95+
try {
96+
const file = req.file;
97+
const bucket = req.query.bucket as string;
98+
99+
if (!file) {
100+
logger.error('No file uploaded');
101+
return res.status(400).json(
102+
createErrorResponse('FILE_MISSING', 'No file uploaded')
103+
);
90104
}
91-
} else if (file.mimetype === 'application/json') {
92-
if (!validateJsonFile(file.buffer)) {
93-
return res.status(400).send('Invalid JSON file format');
105+
106+
if (!bucket) {
107+
logger.error('No bucket provided');
108+
return res.status(400).json(
109+
createErrorResponse('BUCKET_MISSING', 'No bucket provided')
110+
);
94111
}
95112

96-
return res.status(200).send('JSON file is valid - Future implementation');
97-
} else {
98-
return res.status(400).send('Invalid file type. Please upload either a CSV or JSON file');
113+
const response = file.mimetype === 'text/csv'
114+
? await handleCsvFile(file, bucket)
115+
: handleJsonFile(file);
116+
117+
const statusCode = response.status === 'success' ? 201 : 400;
118+
return res.status(statusCode).json(response);
119+
120+
} catch (error) {
121+
logger.error('Error processing upload:', error);
122+
return res.status(500).json(
123+
createErrorResponse(
124+
'INTERNAL_SERVER_ERROR',
125+
error instanceof Error ? error.message : 'Unknown error'
126+
)
127+
);
99128
}
100-
101129
});
102130

103131
export default routes;

0 commit comments

Comments
 (0)