feat(gtm): add aspect ratio & corporate design; fix(market): harden backend logging & json parsing
This commit is contained in:
@@ -8,15 +8,48 @@ const path = require('path');
|
||||
const app = express();
|
||||
const PORT = 3001;
|
||||
|
||||
// --- DEBUG LOGGING ---
|
||||
// WICHTIG: Im Docker-Container market-backend ist nur /app/Log gemountet!
|
||||
const LOG_DIR = '/app/Log';
|
||||
const LOG_FILE = path.join(LOG_DIR, 'backend_debug.log');
|
||||
const DUMP_FILE = path.join(LOG_DIR, 'server_dump.txt');
|
||||
|
||||
const logToFile = (message) => {
|
||||
try {
|
||||
if (!fs.existsSync(LOG_DIR)) return;
|
||||
const timestamp = new Date().toISOString();
|
||||
const logLine = `[${timestamp}] ${message}\n`;
|
||||
fs.appendFileSync(LOG_FILE, logLine);
|
||||
} catch (e) {
|
||||
console.error("Failed to write to debug log:", e);
|
||||
}
|
||||
};
|
||||
|
||||
const dumpToFile = (content) => {
|
||||
try {
|
||||
if (!fs.existsSync(LOG_DIR)) return;
|
||||
const separator = `\n\n--- DUMP ${new Date().toISOString()} ---\n`;
|
||||
fs.appendFileSync(DUMP_FILE, separator + content + "\n--- END DUMP ---\n");
|
||||
} catch (e) {
|
||||
console.error("Failed to dump:", e);
|
||||
}
|
||||
};
|
||||
|
||||
// Middleware
|
||||
app.use(cors());
|
||||
app.use(bodyParser.json({ limit: '50mb' }));
|
||||
|
||||
app.use((req, res, next) => {
|
||||
logToFile(`INCOMING REQUEST: ${req.method} ${req.url}`);
|
||||
next();
|
||||
});
|
||||
|
||||
// Helper für Python-Aufrufe, um Code-Duplizierung zu vermeiden
|
||||
const runPython = (args, res, tempFilesToDelete = []) => {
|
||||
// Im Docker (python:3.11-slim Image) nutzen wir das globale python3
|
||||
const pythonExecutable = 'python3';
|
||||
|
||||
logToFile(`Spawning python: ${args.join(' ')}`);
|
||||
console.log(`Spawning command: ${pythonExecutable} ${args.join(' ')}`);
|
||||
|
||||
const pythonProcess = spawn(pythonExecutable, args);
|
||||
@@ -33,31 +66,10 @@ const runPython = (args, res, tempFilesToDelete = []) => {
|
||||
});
|
||||
|
||||
pythonProcess.on('close', (code) => {
|
||||
console.log(`Python script finished with exit code: ${code}`);
|
||||
if (pythonOutput.length > 500) {
|
||||
console.log(`--- STDOUT (Truncated) ---`);
|
||||
console.log(pythonOutput.substring(0, 500) + '...');
|
||||
} else {
|
||||
console.log(`--- STDOUT ---`);
|
||||
console.log(pythonOutput);
|
||||
}
|
||||
logToFile(`Python exited with code ${code}`);
|
||||
|
||||
if (pythonError) {
|
||||
console.log(`--- STDERR ---`);
|
||||
console.log(pythonError);
|
||||
}
|
||||
console.log(`----------------`);
|
||||
|
||||
// Aufräumen
|
||||
tempFilesToDelete.forEach(file => {
|
||||
if (fs.existsSync(file)) {
|
||||
try {
|
||||
fs.unlinkSync(file);
|
||||
} catch (e) {
|
||||
console.error(`Failed to delete temp file ${file}:`, e.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
// GLOBAL DUMP
|
||||
dumpToFile(`STDOUT:\n${pythonOutput}\n\nSTDERR:\n${pythonError}`);
|
||||
|
||||
if (code !== 0) {
|
||||
console.error(`Python script exited with error.`);
|
||||
@@ -65,19 +77,34 @@ const runPython = (args, res, tempFilesToDelete = []) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const result = JSON.parse(pythonOutput);
|
||||
// Versuche JSON zu parsen
|
||||
// Suche nach dem ersten '{' und dem letzten '}', falls Müll davor/dahinter ist
|
||||
let jsonString = pythonOutput;
|
||||
const match = pythonOutput.match(/\{[\s\S]*\}/);
|
||||
if (match) {
|
||||
jsonString = match[0];
|
||||
}
|
||||
|
||||
const result = JSON.parse(jsonString);
|
||||
res.json(result);
|
||||
} catch (parseError) {
|
||||
logToFile(`JSON Parse Error. Full Output dumped to server_dump.txt`);
|
||||
console.error('Failed to parse Python output as JSON:', parseError);
|
||||
res.status(500).json({ error: 'Invalid JSON from Python script', rawOutput: pythonOutput, details: pythonError });
|
||||
res.status(500).json({
|
||||
error: 'Invalid JSON from Python script',
|
||||
rawOutputSnippet: pythonOutput.substring(0, 200),
|
||||
details: pythonError
|
||||
});
|
||||
}
|
||||
|
||||
// Aufräumen
|
||||
tempFilesToDelete.forEach(file => {
|
||||
if (fs.existsSync(file)) try { fs.unlinkSync(file); } catch(e){}
|
||||
});
|
||||
});
|
||||
|
||||
pythonProcess.on('error', (err) => {
|
||||
console.error('FATAL: Failed to start python process itself.', err);
|
||||
tempFilesToDelete.forEach(file => {
|
||||
if (fs.existsSync(file)) fs.unlinkSync(file);
|
||||
});
|
||||
logToFile(`FATAL: Failed to start python process: ${err.message}`);
|
||||
res.status(500).json({ error: 'Failed to start Python process', details: err.message });
|
||||
});
|
||||
};
|
||||
@@ -85,8 +112,8 @@ const runPython = (args, res, tempFilesToDelete = []) => {
|
||||
|
||||
// API-Endpunkt für generateSearchStrategy
|
||||
app.post('/api/generate-search-strategy', async (req, res) => {
|
||||
console.log(`[${new Date().toISOString()}] HIT: /api/generate-search-strategy`);
|
||||
const { referenceUrl, contextContent } = req.body;
|
||||
logToFile(`[${new Date().toISOString()}] HIT: /api/generate-search-strategy`);
|
||||
const { referenceUrl, contextContent, language } = req.body;
|
||||
|
||||
if (!referenceUrl || !contextContent) {
|
||||
return res.status(400).json({ error: 'Missing referenceUrl or contextContent' });
|
||||
@@ -100,7 +127,13 @@ app.post('/api/generate-search-strategy', async (req, res) => {
|
||||
fs.writeFileSync(tempContextFilePath, contextContent);
|
||||
|
||||
const pythonScript = path.join(__dirname, '..', 'market_intel_orchestrator.py');
|
||||
const scriptArgs = [pythonScript, '--mode', 'generate_strategy', '--reference_url', referenceUrl, '--context_file', tempContextFilePath];
|
||||
const scriptArgs = [
|
||||
pythonScript,
|
||||
'--mode', 'generate_strategy',
|
||||
'--reference_url', referenceUrl,
|
||||
'--context_file', tempContextFilePath,
|
||||
'--language', language || 'de'
|
||||
];
|
||||
|
||||
runPython(scriptArgs, res, [tempContextFilePath]);
|
||||
|
||||
@@ -112,8 +145,8 @@ app.post('/api/generate-search-strategy', async (req, res) => {
|
||||
|
||||
// API-Endpunkt für identifyCompetitors
|
||||
app.post('/api/identify-competitors', async (req, res) => {
|
||||
console.log(`[${new Date().toISOString()}] HIT: /api/identify-competitors`);
|
||||
const { referenceUrl, targetMarket, contextContent, referenceCity, referenceCountry, summaryOfOffer } = req.body;
|
||||
logToFile(`[${new Date().toISOString()}] HIT: /api/identify-competitors`);
|
||||
const { referenceUrl, targetMarket, contextContent, referenceCity, referenceCountry, summaryOfOffer, language } = req.body;
|
||||
|
||||
if (!referenceUrl || !targetMarket) {
|
||||
return res.status(400).json({ error: 'Missing referenceUrl or targetMarket' });
|
||||
@@ -135,7 +168,8 @@ app.post('/api/identify-competitors', async (req, res) => {
|
||||
pythonScript,
|
||||
'--mode', 'identify_competitors',
|
||||
'--reference_url', referenceUrl,
|
||||
'--target_market', targetMarket
|
||||
'--target_market', targetMarket,
|
||||
'--language', language || 'de'
|
||||
];
|
||||
|
||||
if (contextContent) scriptArgs.push('--context_file', tempContextFilePath);
|
||||
@@ -152,8 +186,8 @@ app.post('/api/identify-competitors', async (req, res) => {
|
||||
|
||||
// API-Endpunkt für analyze-company (Deep Tech Audit)
|
||||
app.post('/api/analyze-company', async (req, res) => {
|
||||
console.log(`[${new Date().toISOString()}] HIT: /api/analyze-company`);
|
||||
const { companyName, strategy, targetMarket } = req.body;
|
||||
logToFile(`[${new Date().toISOString()}] HIT: /api/analyze-company`);
|
||||
const { companyName, strategy, targetMarket, language } = req.body;
|
||||
|
||||
if (!companyName || !strategy) {
|
||||
return res.status(400).json({ error: 'Missing companyName or strategy' });
|
||||
@@ -165,7 +199,8 @@ app.post('/api/analyze-company', async (req, res) => {
|
||||
'--mode', 'analyze_company',
|
||||
'--company_name', companyName,
|
||||
'--strategy_json', JSON.stringify(strategy),
|
||||
'--target_market', targetMarket || 'Germany'
|
||||
'--target_market', targetMarket || 'Germany',
|
||||
'--language', language || 'de'
|
||||
];
|
||||
|
||||
runPython(scriptArgs, res);
|
||||
@@ -173,12 +208,12 @@ app.post('/api/analyze-company', async (req, res) => {
|
||||
|
||||
// API-Endpunkt für generate-outreach
|
||||
app.post('/api/generate-outreach', async (req, res) => {
|
||||
console.log(`[${new Date().toISOString()}] HIT: /api/generate-outreach`);
|
||||
logToFile(`[${new Date().toISOString()}] HIT: /api/generate-outreach`);
|
||||
|
||||
// Set a long timeout for this specific route (5 minutes)
|
||||
req.setTimeout(300000);
|
||||
|
||||
const { companyData, knowledgeBase, referenceUrl, specific_role } = req.body;
|
||||
const { companyData, knowledgeBase, referenceUrl, specific_role, language } = req.body;
|
||||
|
||||
if (!companyData || !knowledgeBase) {
|
||||
return res.status(400).json({ error: 'Missing companyData or knowledgeBase' });
|
||||
@@ -199,7 +234,8 @@ app.post('/api/generate-outreach', async (req, res) => {
|
||||
'--mode', 'generate_outreach',
|
||||
'--company_data_file', tempDataFilePath,
|
||||
'--context_file', tempContextFilePath,
|
||||
'--reference_url', referenceUrl || ''
|
||||
'--reference_url', referenceUrl || '',
|
||||
'--language', language || 'de'
|
||||
];
|
||||
|
||||
if (specific_role) {
|
||||
|
||||
Reference in New Issue
Block a user