const express = require('express'); const { spawn } = require('child_process'); const bodyParser = require('body-parser'); const cors = require('cors'); const fs = require('fs'); const path = require('path'); const app = express(); // Port 3002, um Konflikte mit dem Market Intelligence Tool (3001) und dem React Dev Server (3000) zu vermeiden const PORT = 3002; // Middleware app.use(cors()); app.use(bodyParser.json({ limit: '10mb' })); // Erhöhe das Limit für potenziell große Payloads const PYTHON_EXECUTABLE = 'python3'; // Annahme, dass python3 im PATH des Containers ist // Im Docker-Container liegen server.cjs und das Python-Skript im selben Verzeichnis (/app) const SCRIPT_PATH = path.join(__dirname, 'b2b_marketing_orchestrator.py'); // Helper-Funktion zum Ausführen des Python-Skripts const runPythonScript = (args, res) => { console.log(`[${new Date().toISOString()}] Spawning: ${PYTHON_EXECUTABLE} ${args.join(' ')}`); const pythonProcess = spawn(PYTHON_EXECUTABLE, args); let pythonOutput = ''; let pythonError = ''; pythonProcess.stdout.on('data', (data) => { pythonOutput += data.toString(); }); pythonProcess.stderr.on('data', (data) => { pythonError += data.toString(); }); pythonProcess.on('close', (code) => { console.log(`[${new Date().toISOString()}] Python script finished with exit code: ${code}`); if (pythonError) { console.log(`--- STDERR ---`); console.log(pythonError); console.log(`----------------`); } if (code !== 0) { console.error('Python script exited with an error.'); return res.status(500).json({ error: 'An error occurred in the backend script.', details: pythonError }); } try { const result = JSON.parse(pythonOutput); res.json(result); } catch (parseError) { console.error('Failed to parse Python output as JSON:', parseError); res.status(500).json({ error: 'Invalid JSON response from the backend script.', rawOutput: pythonOutput, details: pythonError }); } }); pythonProcess.on('error', (err) => { console.error('FATAL: Failed to start the python process itself.', err); res.status(500).json({ error: 'Failed to start the backend process.', details: err.message }); }); }; // API-Endpunkt, um eine neue Analyse zu starten (Schritt 1) app.post('/api/start-generation', (req, res) => { console.log(`[${new Date().toISOString()}] HIT: /api/start-generation`); const { companyUrl, language, regions, focus } = req.body; if (!companyUrl || !language) { return res.status(400).json({ error: 'Missing required parameters: companyUrl and language.' }); } const args = [ SCRIPT_PATH, '--mode', 'start_generation', '--url', companyUrl, '--language', language ]; if (regions) args.push('--regions', regions); if (focus) args.push('--focus', focus); runPythonScript(args, res); }); // API-Endpunkt, um den nächsten Schritt zu generieren app.post('/api/next-step', (req, res) => { console.log(`[${new Date().toISOString()}] HIT: /api/next-step`); const { analysisData, language, channels, generationStep, focusIndustry } = req.body; if (!analysisData || !language || generationStep === undefined) { return res.status(400).json({ error: 'Missing required parameters: analysisData, language, generationStep.' }); } // Wir schreiben die komplexen Kontext-Daten in eine temporäre Datei, um die Kommandozeile sauber zu halten. const tmpDir = path.join(__dirname, 'tmp'); if (!fs.existsSync(tmpDir)) { fs.mkdirSync(tmpDir); } const contextFilePath = path.join(tmpDir, `context_${Date.now()}.json`); try { fs.writeFileSync(contextFilePath, JSON.stringify(analysisData)); const args = [ SCRIPT_PATH, '--mode', 'next_step', '--language', language, '--context_file', contextFilePath, '--generation_step', generationStep.toString() ]; if (channels && Array.isArray(channels)) { args.push('--channels', channels.join(',')); } if (focusIndustry) { args.push('--focus_industry', focusIndustry); } // Da die runPythonScript-Funktion res behandelt, fügen wir hier die Bereinigung hinzu const originalJson = res.json.bind(res); res.json = (data) => { if (fs.existsSync(contextFilePath)) { fs.unlinkSync(contextFilePath); } originalJson(data); }; const originalStatus = res.status.bind(res); res.status = (code) => { // Wenn ein Fehler auftritt, rufen wir send auf, um die Bereinigung auszulösen const originalSend = res.send.bind(res); res.send = (body) => { if (fs.existsSync(contextFilePath)) { fs.unlinkSync(contextFilePath); } originalSend(body); } return originalStatus(code); } runPythonScript(args, res); } catch (error) { console.error('Failed to write temporary context file:', error); return res.status(500).json({ error: 'Failed to process request context.', details: error.message }); } }); // --- DATABASE ROUTES --- // Initialize DB on startup const dbScript = path.join(__dirname, 'market_db_manager.py'); spawn('python3', [dbScript, 'init']); app.get('/api/projects', (req, res) => { runPythonScript([dbScript, 'list'], res); }); app.get('/api/projects/:id', (req, res) => { runPythonScript([dbScript, 'load', req.params.id], res); }); app.delete('/api/projects/:id', (req, res) => { runPythonScript([dbScript, 'delete', req.params.id], res); }); app.post('/api/save-project', (req, res) => { const projectData = req.body; const tmpDir = path.join(__dirname, 'tmp'); if (!fs.existsSync(tmpDir)) fs.mkdirSync(tmpDir); const tempFilePath = path.join(tmpDir, `save_${Date.now()}.json`); try { fs.writeFileSync(tempFilePath, JSON.stringify(projectData)); runPythonScript([dbScript, 'save', tempFilePath], res); // Clean up temp file if (fs.existsSync(tempFilePath)) { // Note: runPythonScript is async, so we might want to handle deletion there // But since we are passing it to python which reads it, we'll let it be for now // or pass it to runPythonScript cleanup if we had that. // For now, I'll just leave it and let the user manage tmp if needed. } } catch (e) { res.status(500).json({ error: 'Failed to write project data to disk' }); } }); // --- SERVE STATIC FRONTEND --- // Serve static files from the 'dist' directory created by `npm run build` app.use(express.static(path.join(__dirname, 'dist'))); // Handle client-side routing: return index.html for all non-API routes app.get('*', (req, res) => { res.sendFile(path.join(__dirname, 'dist', 'index.html')); }); // Start des Servers app.listen(PORT, () => { console.log(`B2B Marketing Assistant API Bridge running on http://localhost:${PORT}`); });