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(); const PORT = 3002; const VERSION = "1.1.0-DEBUG (Timeout 600s)"; // Middleware app.use(cors()); app.use(bodyParser.json({ limit: '10mb' })); const PYTHON_EXECUTABLE = 'python3'; const SCRIPT_PATH = path.join(__dirname, 'b2b_marketing_orchestrator.py'); const dbScript = '/app/market_db_manager.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 process exited with code ${code}`); if (pythonOutput.length > 0) { console.log(`[${new Date().toISOString()}] Stdout (first 500 chars): ${pythonOutput.substring(0, 500)}...`); } else { console.log(`[${new Date().toISOString()}] Stdout is empty.`); } if (pythonError.length > 0) { console.log(`[${new Date().toISOString()}] Stderr (first 500 chars): ${pythonError.substring(0, 500)}...`); } if (code !== 0) { console.error(`Python error (Code ${code}): ${pythonError}`); return res.status(500).json({ error: 'Backend error', details: pythonError, version: VERSION }); } try { res.header('X-Server-Timeout', '600000'); const responseData = JSON.parse(pythonOutput); // Add version info to the response if it's an object if (typeof responseData === 'object' && responseData !== null) { responseData._backend_version = VERSION; } res.json(responseData); } catch (e) { console.error(`JSON Parse Error: ${e.message}`); console.error(`Raw Output was: ${pythonOutput}`); res.status(500).json({ error: 'Invalid JSON', raw: pythonOutput, details: e.message, version: VERSION }); } }); }; // --- API ROUTES --- const router = express.Router(); router.post('/start-generation', (req, res) => { const { companyUrl, language, regions, focus } = req.body; 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); }); router.post('/next-step', (req, res) => { const { analysisData, language, channels, generationStep, focusIndustry } = req.body; 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); const originalJson = res.json.bind(res); res.json = (data) => { if (fs.existsSync(contextFilePath)) fs.unlinkSync(contextFilePath); originalJson(data); }; runPythonScript(args, res); } catch (e) { res.status(500).json({ error: e.message }); } }); router.get('/projects', (req, res) => runPythonScript([dbScript, 'list'], res)); router.get('/projects/:id', (req, res) => runPythonScript([dbScript, 'load', req.params.id], res)); router.delete('/projects/:id', (req, res) => runPythonScript([dbScript, 'delete', req.params.id], res)); router.post('/save-project', (req, res) => { 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(req.body)); runPythonScript([dbScript, 'save', tempFilePath], res); } catch (e) { res.status(500).json({ error: 'Save failed' }); } }); // Register API Router app.use('/api', router); app.use('/b2b/api', router); // Extra safety for Nginx pathing // --- SERVE STATIC FRONTEND --- const distPath = path.join(__dirname, 'dist'); app.use('/b2b', express.static(distPath)); app.use(express.static(distPath)); app.get('*', (req, res) => { // If it's an API call that missed the router, don't serve index.html if (req.url.startsWith('/api')) return res.status(404).json({ error: 'Not found' }); res.sendFile(path.join(distPath, 'index.html')); }); const initializeDatabase = () => { console.log(`[${new Date().toISOString()}] Initializing B2B database...`); const proc = spawn(PYTHON_EXECUTABLE, [dbScript, 'init']); proc.on('close', (code) => console.log(`[${new Date().toISOString()}] DB init finished (Code ${code})`)); }; const server = app.listen(PORT, () => { console.log(`B2B Marketing Assistant API Bridge running on port ${PORT} (Version: ${VERSION})`); initializeDatabase(); }); // Set timeout to 10 minutes (600s) to handle long AI generation steps server.setTimeout(600000); server.keepAliveTimeout = 610000; server.headersTimeout = 620000;