/** * Graph Viewer API for HuggingClaw * Simple D3-based graph visualization */ const express = require('express'); const neo4j = require('neo4j-driver'); const app = express(); const PORT = process.env.GRAPH_VIEWER_PORT || 7861; // Connect to Memgraph using Bolt protocol const driver = neo4j.driver( 'bolt://localhost:7687', neo4j.auth.basic('', '') ); // Security & Robustness Configuration (Bugs #19 & #20) const cors = require('cors'); const rateLimit = require('express-rate-limit'); // CORS Configuration for Obsidian integration app.use(cors({ origin: ['obsidian://*', 'https://*.hf.space'] })); // API Rate Limiting to prevent exhaustion app.use(rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100 // limit each IP to 100 requests per windowMs })); // Serve static files app.use(express.static('public')); // API: Get full graph app.get('/api/graph', async (req, res) => { const session = driver.session(); try { const result = await session.run('MATCH (n) RETURN n'); const edgesResult = await session.run('MATCH ()-[r]->() RETURN r'); res.json({ nodes: result.records.map(record => record.get('n').properties), edges: edgesResult.records.map(record => ({ ...record.get('r').properties, type: record.get('r').type })) }); } catch (error) { res.status(500).json({ error: error.message }); } finally { await session.close(); } }); // API: Get graph by project app.get('/api/graph/:project', async (req, res) => { const session = driver.session(); try { const { project } = req.params; const result = await session.run( 'MATCH (n {project: $project}) RETURN n', { project } ); const edgesResult = await session.run( 'MATCH (n {project: $project})-[r]->(m {project: $project}) RETURN r', { project } ); res.json({ nodes: result.records.map(record => record.get('n').properties), edges: edgesResult.records.map(record => ({ ...record.get('r').properties, type: record.get('r').type })) }); } catch (error) { res.status(500).json({ error: error.message }); } finally { await session.close(); } }); // API: Health check app.get('/health', async (req, res) => { let memgraphReady = false; const session = driver.session(); try { const result = await session.run('RETURN 1'); memgraphReady = result.records.length > 0; } catch (e) { memgraphReady = false; } finally { await session.close(); } res.json({ status: memgraphReady ? 'healthy' : 'degraded', version: '1.2.0', build_date: new Date().toISOString().split('T')[0], services: { memgraph: memgraphReady, graph_viewer: true, omniroute: 'check-port-20128' // Placeholder for port-based check }, timestamp: new Date().toISOString() }); }); // Start server app.listen(PORT, '0.0.0.0', () => { console.log(`📊 Graph Viewer running on port ${PORT}`); console.log(` http://localhost:${PORT}`); console.log(` API: http://localhost:${PORT}/api/graph`); }); // Graceful driver shutdown process.on('SIGTERM', () => { driver.close(); }); module.exports = app;