Files

121 lines
3.9 KiB
JavaScript

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}`);
});