@@ -3,101 +3,129 @@ import multer from 'multer';
3
3
import { getConfig } from '../config/config' ;
4
4
import { getCsvHeaders } from '../utils/file-validators' ;
5
5
import logger from '../logger' ;
6
- import fs from 'fs' ;
6
+ import fs from 'fs/promises ' ;
7
7
import path from 'path' ;
8
8
import { uploadToMinio } from '../utils/minioClient' ;
9
- import e from 'express' ;
10
- const routes = express . Router ( ) ;
11
9
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 ( ) ;
12
21
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
+ }
16
27
} ) ;
17
28
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
+ } ) ;
19
35
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 > => {
21
43
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 } ) ;
27
45
28
46
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 } ` ) ;
31
49
32
50
return fileUrl ;
33
51
} ;
34
52
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 => {
41
54
try {
42
55
JSON . parse ( buffer . toString ( ) ) ;
43
56
return true ;
44
57
} catch {
45
58
return false ;
46
59
}
47
- }
48
-
49
- routes . post ( '/upload' , upload . single ( 'file' ) , async ( req , res ) => {
50
- const file = req . file ;
51
- const bucket = req . query . bucket ;
60
+ } ;
52
61
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' ) ;
56
70
}
57
71
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 ;
61
83
}
84
+ } ;
62
85
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 ') ;
66
89
}
90
+ return createSuccessResponse ( 'JSON_VALID' , 'JSON file is valid - Future implementation' ) ;
91
+ } ;
67
92
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
+ ) ;
90
104
}
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
+ ) ;
94
111
}
95
112
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
+ ) ;
99
128
}
100
-
101
129
} ) ;
102
130
103
131
export default routes ;
0 commit comments