121 lines
3.9 KiB
JavaScript
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}`);
|
|
});
|