gaialive commited on
Commit
e9fa848
·
verified ·
1 Parent(s): 0f05a73

Update backend/server.js

Browse files
Files changed (1) hide show
  1. backend/server.js +75 -276
backend/server.js CHANGED
@@ -21,7 +21,7 @@ app.use(express.json());
21
  app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
22
 
23
  // Ensure directories exist
24
- const dirs = ['uploads', 'temp', 'results'];
25
  dirs.forEach(dir => {
26
  const dirPath = path.join(__dirname, dir);
27
  if (!fs.existsSync(dirPath)) {
@@ -35,76 +35,21 @@ dirs.forEach(dir => {
35
  console.log('DATABASE: Temporarily disabled for deployment. No data will be saved or retrieved.');
36
  const db = null;
37
 
38
-
39
- // --- MULTER CONFIG FOR IMAGE UPLOADS ---
40
- const imageStorage = multer.diskStorage({
41
  destination: (req, file, cb) => {
42
- cb(null, path.join(__dirname, 'uploads/'));
43
  },
44
  filename: (req, file, cb) => {
45
- const uniqueName = `${Date.now()}-${Math.round(Math.random() * 1E9)}${path.extname(file.originalname)}`;
46
  cb(null, uniqueName);
47
  }
48
  });
49
 
50
- const imageUpload = multer({
51
- storage: imageStorage,
52
- limits: { fileSize: 10 * 1024 * 1024 }, // 10MB limit
53
- fileFilter: (req, file, cb) => {
54
- if (file.mimetype.startsWith('image/')) {
55
- cb(null, true);
56
- } else {
57
- cb(new Error('Only image files are allowed!'), false);
58
- }
59
- }
60
- });
61
-
62
- // --- MULTER CONFIG FOR AUDIO UPLOADS ---
63
- const audioStorage = multer.diskStorage({
64
- destination: function (req, file, cb) {
65
- cb(null, path.join(__dirname, 'temp/')); // Save audio files in a temp directory
66
- },
67
- filename: function (req, file, cb) {
68
- const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
69
- cb(null, 'audio-' + uniqueSuffix + path.extname(file.originalname));
70
- }
71
- });
72
-
73
- const audioUpload = multer({
74
- storage: audioStorage,
75
- limits: { fileSize: 25 * 1024 * 1024 }, // 25MB limit for audio
76
- fileFilter: (req, file, cb) => {
77
- if (file.mimetype.startsWith('audio/')) {
78
- cb(null, true);
79
- } else {
80
- cb(new Error('Only audio files are allowed!'), false);
81
- }
82
- }
83
- });
84
-
85
- // --- MULTER CONFIG FOR DNA SEQUENCE FILES ---
86
- const dnaFileStorage = multer.diskStorage({
87
- destination: function (req, file, cb) {
88
- cb(null, path.join(__dirname, 'temp/')); // Save DNA files in the same temp directory
89
- },
90
- filename: function (req, file, cb) {
91
- const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
92
- cb(null, 'dna-' + uniqueSuffix + path.extname(file.originalname));
93
- }
94
- });
95
-
96
- const dnaUpload = multer({
97
- storage: dnaFileStorage,
98
- limits: { fileSize: 50 * 1024 * 1024 }, // 50MB limit for sequence files
99
- fileFilter: (req, file, cb) => {
100
- // Loosely accept text-based formats common for DNA
101
- const allowedTypes = ['.fasta', '.fastq', '.txt', '.fa', '.fq'];
102
- if (allowedTypes.includes(path.extname(file.originalname).toLowerCase())) {
103
- cb(null, true);
104
- } else {
105
- cb(new Error('Invalid file type for DNA analysis!'), false);
106
- }
107
- }
108
  });
109
 
110
  // --- HELPER FUNCTIONS ---
@@ -124,244 +69,98 @@ function fallbackAnalysis(imagePath, waterSource) {
124
  };
125
  }
126
 
127
- function callPythonAnalysis(imagePath, waterSource) {
 
128
  return new Promise((resolve, reject) => {
129
- const scriptPath = path.join(__dirname, 'python', 'water_analysis.py');
130
- const pythonProcess = spawn('python', [scriptPath, imagePath, waterSource || 'unknown']);
131
 
132
  let dataString = '';
133
  let errorString = '';
134
 
135
- pythonProcess.stdout.on('data', (data) => {
136
- dataString += data.toString();
137
- });
138
 
139
- pythonProcess.stderr.on('data', (data) => {
140
- errorString += data.toString();
141
- });
 
142
 
143
  pythonProcess.on('close', (code) => {
144
  if (code === 0) {
145
  try {
146
- const result = JSON.parse(dataString);
147
- resolve(result);
148
  } catch (error) {
149
- reject(new Error('Failed to parse Python output'));
 
150
  }
151
  } else {
152
- console.log(`Python script at ${scriptPath} not available or failed, using fallback. Error: ${errorString}`);
153
- resolve(fallbackAnalysis(imagePath, waterSource));
154
  }
155
  });
156
  });
157
  }
158
 
159
- // Dummy C++ preprocessor call for demonstration
160
- async function callCppPreprocessing(imagePath) {
161
- return new Promise((resolve, reject) => {
162
- // In a real scenario, you would spawn a C++ executable.
163
- // For this demo, we'll just simulate a failure to show the fallback.
164
- reject(new Error("C++ preprocessor not found or failed."));
165
- });
166
- }
167
-
168
  // --- API ROUTES ---
169
 
170
- // --- BIODIVERSITYEAR AUDIO ANALYSIS ENDPOINT ---
171
- app.post('/api/analyze-audio', audioUpload.single('audioFile'), (req, res) => {
172
- console.log(' Backend: Received audio file analysis request.');
173
-
174
- if (!req.file || req.file.size === 0) {
175
- if (req.file) {
176
- fs.unlink(req.file.path, (err) => {
177
- if (err) console.error(`- Error deleting empty file: ${err.message}`);
178
- });
179
- }
180
- return res.status(400).json({ message: 'The provided audio file is empty or missing. Please record for at least one second.' });
181
- }
182
-
183
- console.log(`- Audio file saved to: ${req.file.path}`);
184
- console.log(`- Region: ${req.body.region}, Habitat: ${req.body.habitat}`);
185
- console.log('▶️ Calling Python AI script for audio analysis...');
186
-
187
- const pythonProcess = spawn('python', [
188
- path.join(__dirname, 'python', 'audio_analyzer.py'),
189
- req.file.path
190
- ]);
191
-
192
- let analysisResult = '';
193
- let errorOutput = '';
194
-
195
- pythonProcess.stdout.on('data', (data) => {
196
- analysisResult += data.toString();
197
- });
198
 
199
- pythonProcess.stderr.on('data', (data) => {
200
- errorOutput += data.toString();
201
- });
202
 
203
- pythonProcess.on('close', (code) => {
204
- // Clean up the uploaded audio file after analysis
 
 
 
 
 
 
205
  fs.unlink(req.file.path, (err) => {
206
  if (err) console.error(`- Error deleting temp audio file: ${err.message}`);
207
- else console.log(`- Temporary audio file ${req.file.path} deleted.`);
208
  });
209
-
210
- if (code === 0) {
211
- console.log('✅ Backend: Python audio script finished successfully.');
212
- try {
213
- const jsonData = JSON.parse(analysisResult);
214
- res.status(200).json(jsonData);
215
- } catch (e) {
216
- console.error('❌ Backend: Error parsing JSON from Python audio script.', e);
217
- res.status(500).json({ message: 'Failed to parse audio analysis result.' });
218
- }
219
- } else {
220
- console.error(`❌ Backend: Python audio script exited with error code ${code}.`);
221
- console.error(`- Python Error: ${errorOutput}`);
222
- res.status(500).json({ message: 'Error during audio analysis.', error: errorOutput });
223
- }
224
- });
225
  });
226
 
227
  // --- BIO-STREAM AI DNA ANALYSIS ENDPOINT ---
228
- app.post('/api/analyze-dna', dnaUpload.single('dnaFile'), (req, res) => {
229
- console.log('✅ Backend: Received DNA file analysis request for Bio-Stream AI.');
230
-
231
- if (!req.file) {
232
- return res.status(400).json({ message: 'No DNA file was uploaded.' });
233
- }
234
-
235
- console.log(`- DNA file saved to: ${req.file.path}`);
236
- console.log('▶️ Calling Python AI script for DNA analysis...');
237
 
238
- const pythonProcess = spawn('python', [
239
- path.join(__dirname, 'python', 'dna_analyzer.py'),
240
- req.file.path // Pass the full path of the uploaded file to the script
241
- ]);
242
-
243
- let analysisResult = '';
244
- let errorOutput = '';
245
-
246
- pythonProcess.stdout.on('data', (data) => {
247
- analysisResult += data.toString();
248
- });
249
-
250
- pythonProcess.stderr.on('data', (data) => {
251
- errorOutput += data.toString();
252
- });
253
-
254
- pythonProcess.on('close', (code) => {
255
- // IMPORTANT: Clean up the uploaded DNA file after analysis is complete
256
  fs.unlink(req.file.path, (err) => {
257
  if (err) console.error(`- Error deleting temp DNA file: ${err.message}`);
258
- else console.log(`- Temporary DNA file ${req.file.path} deleted.`);
259
  });
260
-
261
- if (code === 0) {
262
- console.log('✅ Backend: Python DNA analysis script finished successfully.');
263
- try {
264
- const jsonData = JSON.parse(analysisResult);
265
- res.status(200).json(jsonData);
266
- } catch (e) {
267
- console.error('❌ Backend: Error parsing JSON from Python DNA script.', e);
268
- res.status(500).json({ message: 'Failed to parse DNA analysis result.' });
269
- }
270
- } else {
271
- console.error(`❌ Backend: Python DNA script exited with error code ${code}.`);
272
- console.error(`- Python Error: ${errorOutput}`);
273
- res.status(500).json({ message: 'Error during DNA analysis.', error: errorOutput });
274
- }
275
- });
276
- });
277
-
278
- // --- PHANTOM FOOTPRINT ANALYSIS ENDPOINT ---
279
- app.post('/api/analyze-footprint', (req, res) => {
280
- const { url } = req.body;
281
- console.log(`✅ Backend: Received Phantom Footprint analysis request for URL: ${url}`);
282
-
283
- if (!url) {
284
- return res.status(400).json({ message: 'Product URL is required.' });
285
  }
286
-
287
- const pythonProcess = spawn('python', [
288
- path.join(__dirname, 'python', 'phantom_footprint_analyzer.py')
289
- ]);
290
-
291
- let analysisResult = '';
292
- let errorOutput = '';
293
-
294
- pythonProcess.stdin.write(JSON.stringify({ url: url }));
295
- pythonProcess.stdin.end();
296
-
297
- pythonProcess.stdout.on('data', (data) => {
298
- analysisResult += data.toString();
299
- });
300
-
301
- pythonProcess.stderr.on('data', (data) => {
302
- errorOutput += data.toString();
303
- });
304
-
305
- pythonProcess.on('close', (code) => {
306
- if (code === 0) {
307
- console.log('✅ Backend: Python footprint script finished successfully.');
308
- try {
309
- const jsonData = JSON.parse(analysisResult);
310
- res.status(200).json(jsonData);
311
- } catch (e) {
312
- console.error('❌ Backend: Error parsing JSON from Python script.', e);
313
- res.status(500).json({ message: 'Failed to parse analysis result.' });
314
- }
315
- } else {
316
- console.error(`❌ Backend: Python footprint script exited with error code ${code}.`);
317
- console.error(`- Python Error: ${errorOutput}`);
318
- res.status(500).json({ message: 'Error during footprint analysis.', error: errorOutput });
319
- }
320
- });
321
  });
322
 
323
- // --- E-WASTE ANALYSIS ENDPOINT ---
324
- app.post('/api/analyze-ewaste', (req, res) => {
325
- console.log('✅ Backend: Received e-waste form analysis request.');
326
-
327
- const pythonProcess = spawn('python', [
328
- path.join(__dirname, 'python', 'ewaste_analyzer.py')
329
- ]);
330
-
331
- let analysisResult = '';
332
- let errorOutput = '';
333
-
334
- pythonProcess.stdin.write(JSON.stringify(req.body));
335
- pythonProcess.stdin.end();
336
-
337
- pythonProcess.stdout.on('data', (data) => {
338
- analysisResult += data.toString();
339
- });
340
-
341
- pythonProcess.stderr.on('data', (data) => {
342
- errorOutput += data.toString();
343
- });
344
 
345
- pythonProcess.on('close', (code) => {
346
- if (code === 0) {
347
- console.log('✅ Backend: Python e-waste script finished successfully.');
348
- try {
349
- const jsonData = JSON.parse(analysisResult);
350
- res.status(200).json(jsonData);
351
- } catch (e) {
352
- console.error('❌ Backend: Error parsing JSON from Python script.', e);
353
- res.status(500).json({ message: 'Failed to parse e-waste analysis result.' });
354
- }
355
- } else {
356
- console.error(`❌ Backend: Python e-waste script exited with error code ${code}.`);
357
- console.error(`- Python Error: ${errorOutput}`);
358
- res.status(500).json({ message: 'Error during e-waste analysis.', error: errorOutput });
359
- }
360
- });
361
- });
362
 
363
  // --- AQUALENS WATER ANALYSIS ENDPOINT ---
364
- app.post('/api/analyze-water', imageUpload.single('image'), async (req, res) => {
365
  const startTime = Date.now();
366
 
367
  try {
@@ -388,19 +187,17 @@ app.post('/api/analyze-water', imageUpload.single('image'), async (req, res) =>
388
 
389
  console.log('✅ Image preprocessing completed');
390
 
391
- // Step 2: Advanced preprocessing with C++ (if available)
392
- let processedImagePath = preprocessedPath;
393
  try {
394
- processedImagePath = await callCppPreprocessing(preprocessedPath);
395
- console.log('✅ C++ preprocessing completed');
396
  } catch (error) {
397
- console.log('⚠️ C++ preprocessing not available, using Sharp preprocessing');
 
 
398
  }
399
 
400
- // Step 3: AI analysis with Python
401
- const analysisResult = await callPythonAnalysis(processedImagePath, waterSource);
402
- console.log('✅ AI analysis completed');
403
-
404
  const processingTime = (Date.now() - startTime) / 1000;
405
 
406
  // Step 4: Determine overall quality and safety
@@ -466,7 +263,7 @@ app.post('/api/analyze-water', imageUpload.single('image'), async (req, res) =>
466
  }
467
 
468
  setTimeout(() => {
469
- [preprocessedPath, processedImagePath].forEach(p => {
470
  if (fs.existsSync(p) && p !== imagePath) {
471
  fs.unlinkSync(p);
472
  }
@@ -509,6 +306,8 @@ app.post('/api/analyze-water', imageUpload.single('image'), async (req, res) =>
509
  details: error.message,
510
  timestamp: new Date().toISOString()
511
  });
 
 
512
  }
513
  });
514
 
@@ -525,8 +324,8 @@ app.get('/api/water-map', (req, res) => {
525
  const buildPath = path.join(__dirname, '..', 'web', 'build');
526
  app.use(express.static(buildPath));
527
 
528
- // The "catchall" handler: for any request that doesn't match one of our API routes,
529
- // send back the React app's index.html file. This allows React Router to handle the route.
530
  app.get('*', (req, res) => {
531
  res.sendFile(path.join(buildPath, 'index.html'));
532
  });
 
21
  app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
22
 
23
  // Ensure directories exist
24
+ const dirs = ['uploads', 'temp', 'results']; // Removed 'database' to prevent permission errors
25
  dirs.forEach(dir => {
26
  const dirPath = path.join(__dirname, dir);
27
  if (!fs.existsSync(dirPath)) {
 
35
  console.log('DATABASE: Temporarily disabled for deployment. No data will be saved or retrieved.');
36
  const db = null;
37
 
38
+ // --- UNIFIED MULTER CONFIG ---
39
+ // A single configuration for handling all file uploads to a temporary directory.
40
+ const storage = multer.diskStorage({
41
  destination: (req, file, cb) => {
42
+ cb(null, path.join(__dirname, 'temp/'));
43
  },
44
  filename: (req, file, cb) => {
45
+ const uniqueName = `${file.fieldname}-${Date.now()}-${Math.round(Math.random() * 1E9)}${path.extname(file.originalname)}`;
46
  cb(null, uniqueName);
47
  }
48
  });
49
 
50
+ const upload = multer({
51
+ storage: storage,
52
+ limits: { fileSize: 50 * 1024 * 1024 }, // 50MB general limit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  });
54
 
55
  // --- HELPER FUNCTIONS ---
 
69
  };
70
  }
71
 
72
+ // Refactored helper to call any Python script, handle JSON, and manage errors.
73
+ function callPythonScript(scriptName, args = [], inputData = null) {
74
  return new Promise((resolve, reject) => {
75
+ const scriptPath = path.join(__dirname, 'python', scriptName);
76
+ const pythonProcess = spawn('python3', [scriptPath, ...args]);
77
 
78
  let dataString = '';
79
  let errorString = '';
80
 
81
+ pythonProcess.stdout.on('data', (data) => { dataString += data.toString(); });
82
+ pythonProcess.stderr.on('data', (data) => { errorString += data.toString(); });
 
83
 
84
+ if (inputData) {
85
+ pythonProcess.stdin.write(JSON.stringify(inputData));
86
+ pythonProcess.stdin.end();
87
+ }
88
 
89
  pythonProcess.on('close', (code) => {
90
  if (code === 0) {
91
  try {
92
+ resolve(JSON.parse(dataString));
 
93
  } catch (error) {
94
+ console.error(`Error parsing JSON from ${scriptName}:`, dataString);
95
+ reject(new Error(`Failed to parse Python output from ${scriptName}`));
96
  }
97
  } else {
98
+ console.error(`Error in ${scriptName} (code ${code}): ${errorString}`);
99
+ reject(new Error(errorString || `Python script ${scriptName} exited with code ${code}`));
100
  }
101
  });
102
  });
103
  }
104
 
 
 
 
 
 
 
 
 
 
105
  // --- API ROUTES ---
106
 
107
+ // --- HEALTH CHECK ENDPOINT ---
108
+ // This is crucial for platforms like Hugging Face to know the app is ready.
109
+ app.get('/api/health', (req, res) => {
110
+ res.status(200).json({ status: 'ok', message: 'Backend is running' });
111
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
+ // --- BIODIVERSITYEAR AUDIO ANALYSIS ENDPOINT ---
114
+ app.post('/api/analyze-audio', upload.single('audioFile'), async (req, res) => {
115
+ if (!req.file) return res.status(400).json({ message: 'Audio file is missing.' });
116
 
117
+ try {
118
+ console.log('▶️ Calling Python AI script for audio analysis...');
119
+ const result = await callPythonScript('audio_analyzer.py', [req.file.path]);
120
+ res.status(200).json(result);
121
+ } catch (error) {
122
+ res.status(500).json({ message: 'Error during audio analysis.', error: error.message });
123
+ } finally {
124
+ // Clean up the temporary file
125
  fs.unlink(req.file.path, (err) => {
126
  if (err) console.error(`- Error deleting temp audio file: ${err.message}`);
 
127
  });
128
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  });
130
 
131
  // --- BIO-STREAM AI DNA ANALYSIS ENDPOINT ---
132
+ app.post('/api/analyze-dna', upload.single('dnaFile'), async (req, res) => {
133
+ if (!req.file) return res.status(400).json({ message: 'DNA file is missing.' });
 
 
 
 
 
 
 
134
 
135
+ try {
136
+ console.log('▶️ Calling Python AI script for DNA analysis...');
137
+ const result = await callPythonScript('dna_analyzer.py', [req.file.path]);
138
+ res.status(200).json(result);
139
+ } catch (error) {
140
+ res.status(500).json({ message: 'Error during DNA analysis.', error: error.message });
141
+ } finally {
 
 
 
 
 
 
 
 
 
 
 
142
  fs.unlink(req.file.path, (err) => {
143
  if (err) console.error(`- Error deleting temp DNA file: ${err.message}`);
 
144
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  });
147
 
148
+ // --- JSON-based Analysis Endpoints (Footprint, E-Waste) ---
149
+ async function handleJsonAnalysis(req, res, scriptName, logMessage) {
150
+ console.log(`✅ Backend: Received ${logMessage} request.`);
151
+ try {
152
+ const result = await callPythonScript(scriptName, [], req.body);
153
+ res.status(200).json(result);
154
+ } catch (error) {
155
+ res.status(500).json({ message: `Error during ${logMessage} analysis.`, error: error.message });
156
+ }
157
+ }
 
 
 
 
 
 
 
 
 
 
 
158
 
159
+ app.post('/api/analyze-footprint', (req, res) => handleJsonAnalysis(req, res, 'phantom_footprint_analyzer.py', 'Phantom Footprint'));
160
+ app.post('/api/analyze-ewaste', (req, res) => handleJsonAnalysis(req, res, 'ewaste_analyzer.py', 'E-Waste'));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
 
162
  // --- AQUALENS WATER ANALYSIS ENDPOINT ---
163
+ app.post('/api/analyze-water', upload.single('image'), async (req, res) => {
164
  const startTime = Date.now();
165
 
166
  try {
 
187
 
188
  console.log('✅ Image preprocessing completed');
189
 
190
+ // Step 2: AI analysis with Python
191
+ let analysisResult;
192
  try {
193
+ analysisResult = await callPythonScript('water_analysis.py', [preprocessedPath, waterSource || 'unknown']);
194
+ console.log('✅ AI analysis completed');
195
  } catch (error) {
196
+ // If the python script fails, use the fallback.
197
+ console.log(`Python script for water analysis failed, using fallback. Error: ${error.message}`);
198
+ analysisResult = fallbackAnalysis(preprocessedPath, waterSource);
199
  }
200
 
 
 
 
 
201
  const processingTime = (Date.now() - startTime) / 1000;
202
 
203
  // Step 4: Determine overall quality and safety
 
263
  }
264
 
265
  setTimeout(() => {
266
+ [preprocessedPath, imagePath].forEach(p => {
267
  if (fs.existsSync(p) && p !== imagePath) {
268
  fs.unlinkSync(p);
269
  }
 
306
  details: error.message,
307
  timestamp: new Date().toISOString()
308
  });
309
+ } finally {
310
+ if (req.file) fs.unlink(req.file.path, (err) => { if (err) console.error(`- Error deleting temp image file: ${err.message}`); });
311
  }
312
  });
313
 
 
324
  const buildPath = path.join(__dirname, '..', 'web', 'build');
325
  app.use(express.static(buildPath));
326
 
327
+ // The "catchall" handler: for any request that doesn't match an API route or a static file,
328
+ // send back the main index.html file. This allows React Router to handle client-side routing.
329
  app.get('*', (req, res) => {
330
  res.sendFile(path.join(buildPath, 'index.html'));
331
  });