Spaces:
Running
Running
Update backend/server.js
Browse files- 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 |
-
//
|
| 40 |
-
const
|
| 41 |
destination: (req, file, cb) => {
|
| 42 |
-
cb(null, path.join(__dirname, '
|
| 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
|
| 51 |
-
storage:
|
| 52 |
-
limits: { fileSize:
|
| 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 |
-
|
|
|
|
| 128 |
return new Promise((resolve, reject) => {
|
| 129 |
-
const scriptPath = path.join(__dirname, 'python',
|
| 130 |
-
const pythonProcess = spawn('
|
| 131 |
|
| 132 |
let dataString = '';
|
| 133 |
let errorString = '';
|
| 134 |
|
| 135 |
-
pythonProcess.stdout.on('data', (data) => {
|
| 136 |
-
|
| 137 |
-
});
|
| 138 |
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
|
|
|
| 142 |
|
| 143 |
pythonProcess.on('close', (code) => {
|
| 144 |
if (code === 0) {
|
| 145 |
try {
|
| 146 |
-
|
| 147 |
-
resolve(result);
|
| 148 |
} catch (error) {
|
| 149 |
-
|
|
|
|
| 150 |
}
|
| 151 |
} else {
|
| 152 |
-
console.
|
| 153 |
-
|
| 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 |
-
// ---
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 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 |
-
|
| 200 |
-
|
| 201 |
-
});
|
| 202 |
|
| 203 |
-
|
| 204 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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',
|
| 229 |
-
|
| 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 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 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 |
-
// ---
|
| 324 |
-
|
| 325 |
-
console.log(
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 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 |
-
|
| 346 |
-
|
| 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',
|
| 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:
|
| 392 |
-
let
|
| 393 |
try {
|
| 394 |
-
|
| 395 |
-
console.log('✅
|
| 396 |
} catch (error) {
|
| 397 |
-
|
|
|
|
|
|
|
| 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,
|
| 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
|
| 529 |
-
// send back the
|
| 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 |
});
|