fix: Finalize B2B routing and DB initialization for Nginx compatibility
This commit is contained in:
@@ -6,230 +6,105 @@ const fs = require('fs');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
// Port 3002, um Konflikte mit dem Market Intelligence Tool (3001) und dem React Dev Server (3000) zu vermeiden
|
|
||||||
const PORT = 3002;
|
const PORT = 3002;
|
||||||
|
|
||||||
// Middleware
|
// Middleware
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
app.use(bodyParser.json({ limit: '10mb' })); // Erhöhe das Limit für potenziell große Payloads
|
app.use(bodyParser.json({ limit: '10mb' }));
|
||||||
|
|
||||||
const PYTHON_EXECUTABLE = 'python3'; // Annahme, dass python3 im PATH des Containers ist
|
const PYTHON_EXECUTABLE = 'python3';
|
||||||
// Im Docker-Container liegen server.cjs und das Python-Skript im selben Verzeichnis (/app)
|
|
||||||
const SCRIPT_PATH = path.join(__dirname, 'b2b_marketing_orchestrator.py');
|
const SCRIPT_PATH = path.join(__dirname, 'b2b_marketing_orchestrator.py');
|
||||||
const dbScript = '/app/market_db_manager.py'; // Absoluter Pfad zum DB Manager im Container
|
const dbScript = '/app/market_db_manager.py';
|
||||||
|
|
||||||
|
|
||||||
// Helper-Funktion zum Ausführen des Python-Skripts
|
// Helper-Funktion zum Ausführen des Python-Skripts
|
||||||
const runPythonScript = (args, res) => {
|
const runPythonScript = (args, res) => {
|
||||||
console.log(`[${new Date().toISOString()}] Spawning: ${PYTHON_EXECUTABLE} ${args.join(' ')}`);
|
console.log(`[${new Date().toISOString()}] Spawning: ${PYTHON_EXECUTABLE} ${args.join(' ')}`);
|
||||||
|
|
||||||
const pythonProcess = spawn(PYTHON_EXECUTABLE, args);
|
const pythonProcess = spawn(PYTHON_EXECUTABLE, args);
|
||||||
|
|
||||||
let pythonOutput = '';
|
let pythonOutput = '';
|
||||||
let pythonError = '';
|
let pythonError = '';
|
||||||
|
|
||||||
pythonProcess.stdout.on('data', (data) => {
|
pythonProcess.stdout.on('data', (data) => { pythonOutput += data.toString(); });
|
||||||
pythonOutput += data.toString();
|
pythonProcess.stderr.on('data', (data) => { pythonError += data.toString(); });
|
||||||
});
|
|
||||||
|
|
||||||
pythonProcess.stderr.on('data', (data) => {
|
|
||||||
pythonError += data.toString();
|
|
||||||
});
|
|
||||||
|
|
||||||
pythonProcess.on('close', (code) => {
|
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) {
|
if (code !== 0) {
|
||||||
console.error('Python script exited with an error.');
|
console.error(`Python error (Code ${code}): ${pythonError}`);
|
||||||
return res.status(500).json({
|
return res.status(500).json({ error: 'Backend error', details: pythonError });
|
||||||
error: 'An error occurred in the backend script.',
|
|
||||||
details: pythonError
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = JSON.parse(pythonOutput);
|
res.json(JSON.parse(pythonOutput));
|
||||||
res.json(result);
|
} catch (e) {
|
||||||
} catch (parseError) {
|
res.status(500).json({ error: 'Invalid JSON', raw: pythonOutput });
|
||||||
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 ROUTES ---
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
// API-Endpunkt, um eine neue Analyse zu starten (Schritt 1)
|
router.post('/start-generation', (req, res) => {
|
||||||
app.post('/api/start-generation', (req, res) => {
|
|
||||||
console.log(`[${new Date().toISOString()}] HIT: /api/start-generation`);
|
|
||||||
const { companyUrl, language, regions, focus } = req.body;
|
const { companyUrl, language, regions, focus } = req.body;
|
||||||
|
const args = [SCRIPT_PATH, '--mode', 'start_generation', '--url', companyUrl, '--language', language];
|
||||||
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 (regions) args.push('--regions', regions);
|
||||||
if (focus) args.push('--focus', focus);
|
if (focus) args.push('--focus', focus);
|
||||||
|
|
||||||
runPythonScript(args, res);
|
runPythonScript(args, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.post('/next-step', (req, 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;
|
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');
|
const tmpDir = path.join(__dirname, 'tmp');
|
||||||
if (!fs.existsSync(tmpDir)) {
|
if (!fs.existsSync(tmpDir)) fs.mkdirSync(tmpDir);
|
||||||
fs.mkdirSync(tmpDir);
|
|
||||||
}
|
|
||||||
const contextFilePath = path.join(tmpDir, `context_${Date.now()}.json`);
|
const contextFilePath = path.join(tmpDir, `context_${Date.now()}.json`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fs.writeFileSync(contextFilePath, JSON.stringify(analysisData));
|
fs.writeFileSync(contextFilePath, JSON.stringify(analysisData));
|
||||||
|
const args = [SCRIPT_PATH, '--mode', 'next_step', '--language', language, '--context_file', contextFilePath, '--generation_step', generationStep.toString()];
|
||||||
const args = [
|
if (channels && Array.isArray(channels)) args.push('--channels', channels.join(','));
|
||||||
SCRIPT_PATH,
|
if (focusIndustry) args.push('--focus_industry', focusIndustry);
|
||||||
'--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);
|
const originalJson = res.json.bind(res);
|
||||||
res.json = (data) => {
|
res.json = (data) => {
|
||||||
if (fs.existsSync(contextFilePath)) {
|
if (fs.existsSync(contextFilePath)) fs.unlinkSync(contextFilePath);
|
||||||
fs.unlinkSync(contextFilePath);
|
|
||||||
}
|
|
||||||
originalJson(data);
|
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);
|
runPythonScript(args, res);
|
||||||
|
} catch (e) { res.status(500).json({ error: e.message }); }
|
||||||
} 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 ---
|
router.get('/projects', (req, res) => runPythonScript([dbScript, 'list'], res));
|
||||||
|
router.get('/projects/:id', (req, res) => runPythonScript([dbScript, 'load', req.params.id], res));
|
||||||
app.get('/api/projects', (req, res) => {
|
router.delete('/projects/:id', (req, res) => runPythonScript([dbScript, 'delete', req.params.id], res));
|
||||||
runPythonScript([dbScript, 'list'], res);
|
router.post('/save-project', (req, 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');
|
const tmpDir = path.join(__dirname, 'tmp');
|
||||||
if (!fs.existsSync(tmpDir)) fs.mkdirSync(tmpDir);
|
if (!fs.existsSync(tmpDir)) fs.mkdirSync(tmpDir);
|
||||||
const tempFilePath = path.join(tmpDir, `save_${Date.now()}.json`);
|
const tempFilePath = path.join(tmpDir, `save_${Date.now()}.json`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fs.writeFileSync(tempFilePath, JSON.stringify(projectData));
|
fs.writeFileSync(tempFilePath, JSON.stringify(req.body));
|
||||||
runPythonScript([dbScript, 'save', tempFilePath], res);
|
runPythonScript([dbScript, 'save', tempFilePath], res);
|
||||||
} catch (e) {
|
} catch (e) { res.status(500).json({ error: 'Save failed' }); }
|
||||||
res.status(500).json({ error: 'Failed to write project data to disk' });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Register API Router
|
||||||
|
app.use('/api', router);
|
||||||
|
app.use('/b2b/api', router); // Extra safety for Nginx pathing
|
||||||
|
|
||||||
// --- SERVE STATIC FRONTEND ---
|
// --- SERVE STATIC FRONTEND ---
|
||||||
// Serve static files from the 'dist' directory created by `npm run build`
|
const distPath = path.join(__dirname, 'dist');
|
||||||
app.use(express.static(path.join(__dirname, 'dist')));
|
app.use('/b2b', express.static(distPath));
|
||||||
|
app.use(express.static(distPath));
|
||||||
|
|
||||||
// Handle client-side routing: return index.html for all non-API routes
|
|
||||||
app.get('*', (req, res) => {
|
app.get('*', (req, res) => {
|
||||||
res.sendFile(path.join(__dirname, 'dist', 'index.html'));
|
// 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'));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Separate function to initialize the database, called after server starts
|
|
||||||
const initializeDatabase = () => {
|
const initializeDatabase = () => {
|
||||||
console.log(`[${new Date().toISOString()}] Initializing B2B database...`);
|
console.log(`[${new Date().toISOString()}] Initializing B2B database...`);
|
||||||
const pythonProcess = spawn(PYTHON_EXECUTABLE, [dbScript, 'init']);
|
const proc = spawn(PYTHON_EXECUTABLE, [dbScript, 'init']);
|
||||||
|
proc.on('close', (code) => console.log(`[${new Date().toISOString()}] DB init finished (Code ${code})`));
|
||||||
pythonProcess.stdout.on('data', (data) => {
|
|
||||||
console.log(`[DB Init STDOUT]: ${data.toString().trim()}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
pythonProcess.stderr.on('data', (data) => {
|
|
||||||
console.error(`[DB Init STDERR]: ${data.toString().trim()}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
pythonProcess.on('close', (code) => {
|
|
||||||
if (code === 0) {
|
|
||||||
console.log(`[${new Date().toISOString()}] B2B database initialized successfully.`);
|
|
||||||
} else {
|
|
||||||
console.error(`[${new Date().toISOString()}] B2B database initialization failed with code: ${code}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
pythonProcess.on('error', (err) => {
|
|
||||||
console.error(`[${new Date().toISOString()}] Failed to spawn Python for DB init:`, err);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Start des Servers
|
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
console.log(`B2B Marketing Assistant API Bridge running on http://localhost:${PORT}`);
|
console.log(`B2B Marketing Assistant API Bridge running on port ${PORT}`);
|
||||||
initializeDatabase(); // Initialize DB after server is listening
|
initializeDatabase();
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user