Spaces:
Sleeping
Sleeping
File size: 13,270 Bytes
7c97d6f e9fa848 7c97d6f e9fa848 7c97d6f e9fa848 7c97d6f e9fa848 7c97d6f e9fa848 7c97d6f e9fa848 7c97d6f e9fa848 7c97d6f e9fa848 7c97d6f e9fa848 7c97d6f e9fa848 7c97d6f e9fa848 7c97d6f e9fa848 7c97d6f e9fa848 7c97d6f e9fa848 7c97d6f e9fa848 7c97d6f e9fa848 7c97d6f e9fa848 7c97d6f e9fa848 7c97d6f e9fa848 7c97d6f e9fa848 7c97d6f e9fa848 7c97d6f e9fa848 7c97d6f e9fa848 7c97d6f e9fa848 7c97d6f e9fa848 7c97d6f e9fa848 7c97d6f e9fa848 7c97d6f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 | const express = require('express');
const cors = require('cors');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const { spawn } = require('child_process');
const sqlite3 = require('sqlite3').verbose();
const sharp = require('sharp');
const { v4: uuidv4 } = require('uuid');
require('dotenv').config();
const app = express();
// cPanel and other hosts use the PORT environment variable
const PORT = process.env.PORT || 5000;
// --- Middleware ---
// Enable CORS for all routes
app.use(cors());
// Parse JSON bodies for API requests
app.use(express.json());
app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
// Ensure directories exist
const dirs = ['uploads', 'temp', 'results']; // Removed 'database' to prevent permission errors
dirs.forEach(dir => {
const dirPath = path.join(__dirname, dir);
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true }); // Still need temp dirs for uploads
}
});
// --- DATABASE DISABLED ---
// The database is temporarily disabled for simplified deployment on Hugging Face.
// All database operations will be skipped, and the app will run without persistence.
console.log('DATABASE: Temporarily disabled for deployment. No data will be saved or retrieved.');
const db = null;
// --- UNIFIED MULTER CONFIG ---
// A single configuration for handling all file uploads to a temporary directory.
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, path.join(__dirname, 'temp/'));
},
filename: (req, file, cb) => {
const uniqueName = `${file.fieldname}-${Date.now()}-${Math.round(Math.random() * 1E9)}${path.extname(file.originalname)}`;
cb(null, uniqueName);
}
});
const upload = multer({
storage: storage,
limits: { fileSize: 50 * 1024 * 1024 }, // 50MB general limit
});
// --- HELPER FUNCTIONS ---
function fallbackAnalysis(imagePath, waterSource) {
console.log('--- Executing Node.js Fallback Analysis ---');
// This simulates the Python script's output when it's not available.
return {
ph: 7.1,
chlorine: 0.5,
nitrates: 5,
hardness: 150,
alkalinity: 120,
bacteria: 0,
confidence: 40, // Lower confidence to indicate it's a fallback
processingMethod: "Node.js Fallback"
};
}
// Refactored helper to call any Python script, handle JSON, and manage errors.
function callPythonScript(scriptName, args = [], inputData = null) {
return new Promise((resolve, reject) => {
const scriptPath = path.join(__dirname, 'python', scriptName);
const pythonProcess = spawn('python3', [scriptPath, ...args]);
let dataString = '';
let errorString = '';
pythonProcess.stdout.on('data', (data) => { dataString += data.toString(); });
pythonProcess.stderr.on('data', (data) => { errorString += data.toString(); });
if (inputData) {
pythonProcess.stdin.write(JSON.stringify(inputData));
pythonProcess.stdin.end();
}
pythonProcess.on('close', (code) => {
if (code === 0) {
try {
resolve(JSON.parse(dataString));
} catch (error) {
console.error(`Error parsing JSON from ${scriptName}:`, dataString);
reject(new Error(`Failed to parse Python output from ${scriptName}`));
}
} else {
console.error(`Error in ${scriptName} (code ${code}): ${errorString}`);
reject(new Error(errorString || `Python script ${scriptName} exited with code ${code}`));
}
});
});
}
// --- API ROUTES ---
// --- HEALTH CHECK ENDPOINT ---
// This is crucial for platforms like Hugging Face to know the app is ready.
app.get('/api/health', (req, res) => {
res.status(200).json({ status: 'ok', message: 'Backend is running' });
});
// --- BIODIVERSITYEAR AUDIO ANALYSIS ENDPOINT ---
app.post('/api/analyze-audio', upload.single('audioFile'), async (req, res) => {
if (!req.file) return res.status(400).json({ message: 'Audio file is missing.' });
try {
console.log('βΆοΈ Calling Python AI script for audio analysis...');
const result = await callPythonScript('audio_analyzer.py', [req.file.path]);
res.status(200).json(result);
} catch (error) {
res.status(500).json({ message: 'Error during audio analysis.', error: error.message });
} finally {
// Clean up the temporary file
fs.unlink(req.file.path, (err) => {
if (err) console.error(`- Error deleting temp audio file: ${err.message}`);
});
}
});
// --- BIO-STREAM AI DNA ANALYSIS ENDPOINT ---
app.post('/api/analyze-dna', upload.single('dnaFile'), async (req, res) => {
if (!req.file) return res.status(400).json({ message: 'DNA file is missing.' });
try {
console.log('βΆοΈ Calling Python AI script for DNA analysis...');
const result = await callPythonScript('dna_analyzer.py', [req.file.path]);
res.status(200).json(result);
} catch (error) {
res.status(500).json({ message: 'Error during DNA analysis.', error: error.message });
} finally {
fs.unlink(req.file.path, (err) => {
if (err) console.error(`- Error deleting temp DNA file: ${err.message}`);
});
}
});
// --- JSON-based Analysis Endpoints (Footprint, E-Waste) ---
async function handleJsonAnalysis(req, res, scriptName, logMessage) {
console.log(`β
Backend: Received ${logMessage} request.`);
try {
const result = await callPythonScript(scriptName, [], req.body);
res.status(200).json(result);
} catch (error) {
res.status(500).json({ message: `Error during ${logMessage} analysis.`, error: error.message });
}
}
app.post('/api/analyze-footprint', (req, res) => handleJsonAnalysis(req, res, 'phantom_footprint_analyzer.py', 'Phantom Footprint'));
app.post('/api/analyze-ewaste', (req, res) => handleJsonAnalysis(req, res, 'ewaste_analyzer.py', 'E-Waste'));
// --- AQUALENS WATER ANALYSIS ENDPOINT ---
app.post('/api/analyze-water', upload.single('image'), async (req, res) => {
const startTime = Date.now();
try {
if (!req.file) {
return res.status(400).json({ error: 'No image file provided' });
}
const { waterSource, latitude, longitude, userId } = req.body;
const imagePath = req.file.path;
const testId = uuidv4();
console.log(`π§ͺ Starting water analysis for test ${testId}`);
console.log(`π Location: ${latitude}, ${longitude}`);
console.log(`π° Water Source: ${waterSource}`);
// Step 1: Image preprocessing with Sharp (Node.js)
const preprocessedPath = path.join(__dirname, 'temp', `preprocessed_${testId}.jpg`);
await sharp(imagePath)
.resize(800, 600, { fit: 'inside' })
.normalize()
.sharpen()
.jpeg({ quality: 95 })
.toFile(preprocessedPath);
console.log('β
Image preprocessing completed');
// Step 2: AI analysis with Python
let analysisResult;
try {
analysisResult = await callPythonScript('water_analysis.py', [preprocessedPath, waterSource || 'unknown']);
console.log('β
AI analysis completed');
} catch (error) {
// If the python script fails, use the fallback.
console.log(`Python script for water analysis failed, using fallback. Error: ${error.message}`);
analysisResult = fallbackAnalysis(preprocessedPath, waterSource);
}
const processingTime = (Date.now() - startTime) / 1000;
// Step 4: Determine overall quality and safety
const { ph, chlorine, nitrates, hardness, alkalinity, bacteria } = analysisResult;
let overallQuality = 'Excellent';
let safetyLevel = 'Safe';
let alerts = [];
let recommendations = [];
if (ph < 6.5 || ph > 8.5) {
alerts.push(`pH levels outside safe range: ${ph.toFixed(1)}`);
recommendations.push('Consider pH adjustment or filtration system');
if (ph < 5.0 || ph > 9.5) {
safetyLevel = 'Unsafe';
overallQuality = 'Poor';
recommendations.push('Contact water authority immediately - pH outside safe drinking range');
} else {
overallQuality = 'Good';
}
}
if (chlorine > 4.0) {
alerts.push(`High chlorine levels: ${chlorine.toFixed(1)} ppm`);
recommendations.push('Let water sit uncovered for 30 minutes before drinking');
safetyLevel = 'Unsafe';
overallQuality = 'Poor';
} else if (chlorine < 0.2 && waterSource === 'Tap Water') {
alerts.push('Low chlorine in tap water may indicate contamination risk');
recommendations.push('Consider boiling water or using filtration');
}
if (nitrates > 10) {
alerts.push(`Elevated nitrate levels: ${nitrates} ppm`);
recommendations.push('Consider reverse osmosis filtration or alternative water source');
if (nitrates > 50) {
safetyLevel = 'Unsafe';
overallQuality = 'Poor';
recommendations.push('DO NOT DRINK - Especially dangerous for infants and pregnant women');
} else {
overallQuality = 'Fair';
}
}
if (bacteria > 0) {
alerts.push('Bacterial contamination detected');
recommendations.push('Boil water for 1 minute before drinking or use alternative source');
safetyLevel = 'Unsafe';
overallQuality = 'Poor';
}
if (hardness > 180) {
alerts.push(`Very hard water: ${hardness} ppm`);
recommendations.push('Consider water softener to protect plumbing and improve taste');
if (overallQuality === 'Excellent') overallQuality = 'Good';
}
// Step 5: Save to database
if (db) {
// Database logic would go here.
} else {
console.log('DATABASE: Skipping save operation for water analysis results.');
}
setTimeout(() => {
[preprocessedPath, imagePath].forEach(p => {
if (fs.existsSync(p) && p !== imagePath) {
fs.unlinkSync(p);
}
});
}, 5000);
console.log(`β
Analysis completed in ${processingTime.toFixed(2)}s`);
res.json({
success: true,
testId: testId,
results: {
ph: parseFloat(ph.toFixed(1)),
chlorine: parseFloat(chlorine.toFixed(1)),
nitrates: Math.round(nitrates),
hardness: Math.round(hardness),
alkalinity: Math.round(alkalinity),
bacteria: bacteria
},
overallQuality: overallQuality,
safetyLevel: safetyLevel,
alerts: alerts,
recommendations: recommendations,
confidence: Math.round(analysisResult.confidence || 95),
processingTime: parseFloat(processingTime.toFixed(2)),
colorAccuracy: analysisResult.colorAccuracy || '94%',
processingMethod: analysisResult.processingMethod || 'AI Analysis',
colorChannels: analysisResult.colorChannels || {},
timestamp: new Date().toISOString(),
location: {
latitude: parseFloat(latitude) || null,
longitude: parseFloat(longitude) || null
}
});
} catch (error) {
console.error('β Analysis error:', error);
res.status(500).json({
error: 'Analysis failed',
details: error.message,
timestamp: new Date().toISOString()
});
} finally {
if (req.file) fs.unlink(req.file.path, (err) => { if (err) console.error(`- Error deleting temp image file: ${err.message}`); });
}
});
// Get water quality map data
app.get('/api/water-map', (req, res) => {
// Since the database is disabled, return an empty array for map data.
console.log('DATABASE: /api/water-map called, returning empty dataset as DB is disabled.');
res.json({ success: true, data: [], count: 0 });
});
// --- STATIC FILE SERVING FOR PRODUCTION ---
// This is the crucial part for serving your React app in production.
// It serves the built React app from the 'web/build' folder.
const buildPath = path.join(__dirname, '..', 'web', 'build');
app.use(express.static(buildPath));
// The "catchall" handler: for any request that doesn't match an API route or a static file,
// send back the main index.html file. This allows React Router to handle client-side routing.
app.get('*', (req, res) => {
res.sendFile(path.join(buildPath, 'index.html'));
});
// --- Start Server ---
app.listen(PORT, () => {
console.log(`πΏ GreenPlus by GXS Main Backend Server running on port ${PORT}`);
console.log(`π Serving production React app from: ${buildPath}`);
}); |