admin / SAynapse-Horizon
publicSelf Hosted Cyber Threat Intelligence Hub
SAynapse-Horizon / Synapse-Horizon / backend / src / server.js
3186 B · main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | import express from 'express' import cors from 'cors' import { runSources, describeSources, SOURCE_KEYS } from './sources/index.js' const PORT = Number(process.env.PORT) || 4000 const app = express() app.use(cors()) app.use(express.json()) // ----------------------------------------------------------------------------- // In-memory cache. Holds the last successful pull per source plus a merged view. // ----------------------------------------------------------------------------- const cache = { bySource: {}, // key -> { iocs, status, ts } generatedAt: null, } const mergedIocs = () => Object.values(cache.bySource) .flatMap((s) => s.iocs) .sort((a, b) => new Date(b.detectedAt) - new Date(a.detectedAt)) const statusMap = () => { const out = {} for (const s of describeSources()) { const cached = cache.bySource[s.key] out[s.key] = { requiresKey: s.requiresKey, configured: s.configured, status: cached?.status?.status || (s.configured ? 'idle' : 'unconfigured'), count: cached?.iocs?.length || 0, message: cached?.status?.message || null, lastPull: cached?.ts || null, } } return out } const snapshot = () => ({ generatedAt: cache.generatedAt, total: mergedIocs().length, sources: statusMap(), iocs: mergedIocs(), }) /** Pull the given sources live and fold the results into the cache. */ async function sync(keys = SOURCE_KEYS) { const { iocs, status } = await runSources(keys) const ts = new Date().toISOString() const grouped = {} for (const key of keys) grouped[key] = [] for (const ioc of iocs) (grouped[ioc.source] ||= []).push(ioc) for (const key of keys) { cache.bySource[key] = { iocs: grouped[key] || [], status: status[key] || { status: 'idle' }, ts } } cache.generatedAt = ts return snapshot() } // ----------------------------------------------------------------------------- // Routes // ----------------------------------------------------------------------------- app.get('/api/health', (_req, res) => res.json({ ok: true, uptime: process.uptime() })) app.get('/api/sources', (_req, res) => res.json({ sources: statusMap() })) app.get('/api/feeds', (_req, res) => res.json(snapshot())) app.post('/api/sync', async (req, res) => { const requested = Array.isArray(req.body?.sources) ? req.body.sources : SOURCE_KEYS const keys = requested.filter((k) => SOURCE_KEYS.includes(k)) try { const result = await sync(keys.length ? keys : SOURCE_KEYS) res.json(result) } catch (err) { res.status(500).json({ error: String(err?.message || err) }) } }) app.listen(PORT, '0.0.0.0', () => { const configured = describeSources().filter((s) => s.configured).map((s) => s.key) console.log(`[synapse-horizon-api] listening on :${PORT}`) console.log(`[synapse-horizon-api] configured sources: ${configured.join(', ') || '(none — set API keys or HORIZON_DEMO=1)'}`) // Warm the cache in the background so /api/feeds has data on first load. sync() .then((s) => console.log(`[synapse-horizon-api] initial sync complete — ${s.total} IOCs`)) .catch((e) => console.error('[synapse-horizon-api] initial sync failed:', e.message)) }) |