const express = require('express'); const cors = require('cors'); const path = require('path'); const fs = require('fs'); const trading = require('./lib/sources/trading'); const retail = require('./lib/sources/retail'); const supplyChain = require('./lib/sources/supply-chain'); const smartCity = require('./lib/sources/smart-city'); const fraud = require('./lib/sources/fraud'); const { fetchCameraFrame } = require('./lib/camera-proxy'); const PORT = process.env.PORT || 3010; const app = express(); app.use(cors()); app.use((req, res, next) => { res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin'); next(); }); app.use(express.json()); const SOURCES = { trading: { label: 'Trading & FX', fetch: () => trading.getTradingData(), interval: 2000 }, retail: { label: 'Retail Intelligence', fetch: () => retail.getRetailData(), interval: 2000 }, 'supply-chain': { label: 'Supply Chain', fetch: () => supplyChain.getSupplyChainData(), interval: 2500 }, fraud: { label: 'Fraud Detection', fetch: () => fraud.getFraudSnapshot(), interval: 1500 }, 'smart-city': { label: 'Smart City', fetch: () => smartCity.getSmartCityData(), interval: 1500 } }; app.get('/health', (req, res) => { res.json({ status: 'ok', service: 'mek-tech-showcase', ts: new Date().toISOString() }); }); app.get('/api/status', async (req, res) => { const status = {}; for (const [key, src] of Object.entries(SOURCES)) { try { await src.fetch(); status[key] = { ok: true, label: src.label }; } catch (e) { status[key] = { ok: false, label: src.label, error: e.message }; } } res.json({ ts: new Date().toISOString(), verticals: status }); }); app.get('/api/smart-city/camera/:id', (req, res) => { const cam = smartCity.getCameraById(req.params.id); if (!cam) return res.status(404).json({ error: 'Camera not found' }); res.json(cam); }); app.get('/api/smart-city/camera/:id/frame', async (req, res) => { try { const frame = await fetchCameraFrame(req.params.id); if (!frame) return res.status(502).json({ error: 'Frame unavailable' }); res.setHeader('Content-Type', frame.contentType); res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate'); res.setHeader('Access-Control-Allow-Origin', '*'); res.send(frame.buffer); } catch (e) { res.status(500).json({ error: e.message }); } }); app.get('/api/:vertical', async (req, res) => { const src = SOURCES[req.params.vertical]; if (!src) return res.status(404).json({ error: 'Unknown vertical' }); try { const data = await src.fetch(); res.json(data); } catch (e) { res.status(500).json({ error: e.message }); } }); app.get('/stream/:vertical', async (req, res) => { const vertical = req.params.vertical; const src = SOURCES[vertical]; if (!src) return res.status(404).end(); res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); res.flushHeaders(); let closed = false; req.on('close', () => { closed = true; }); async function push() { if (closed) return; try { const data = await src.fetch(); res.write(`data: ${JSON.stringify(data)}\n\n`); } catch (e) { res.write(`event: error\ndata: ${JSON.stringify({ error: e.message })}\n\n`); } } await push(); const iv = setInterval(async () => { if (closed) { clearInterval(iv); return; } await push(); }, src.interval); req.on('close', () => clearInterval(iv)); }); const distPath = path.join(__dirname, '../frontend/dist'); if (fs.existsSync(distPath)) { app.use(express.static(distPath)); app.get('*', (req, res) => { if (req.path.startsWith('/api') || req.path.startsWith('/stream')) return res.status(404).end(); res.sendFile(path.join(distPath, 'index.html')); }); } app.listen(PORT, '0.0.0.0', () => { console.log(`Mek-Tech Showcase running on http://0.0.0.0:${PORT}`); });