60 lines
1.8 KiB
JavaScript
60 lines
1.8 KiB
JavaScript
/**
|
|
* Pipeline routes — orchestrate the data pipeline.
|
|
*
|
|
* POST /api/pipeline/refresh body: { sport, graded? }
|
|
* GET /api/pipeline/status
|
|
*
|
|
* Refresh is the only write path; it's the one n8n calls. We gate it with
|
|
* a shared secret so a stray POST from the open internet can't trigger an
|
|
* upstream fan-out.
|
|
*/
|
|
|
|
const express = require('express');
|
|
const provider = require('../services/UnifiedOddsProvider');
|
|
const { isActiveSport, shouldCollect, SPORTS } = require('../config/sports');
|
|
|
|
const router = express.Router();
|
|
|
|
const SUPPORTED = Object.keys(SPORTS);
|
|
|
|
function requirePipelineSecret(req, res, next) {
|
|
const expected = process.env.PIPELINE_SECRET;
|
|
if (!expected) return res.status(503).json({ error: 'PIPELINE_SECRET not configured' });
|
|
const got = req.get('X-Pipeline-Secret') || req.body?.secret;
|
|
if (!got || got !== expected) {
|
|
return res.status(401).json({ error: 'invalid pipeline secret' });
|
|
}
|
|
return next();
|
|
}
|
|
|
|
router.post('/refresh', requirePipelineSecret, async (req, res) => {
|
|
const sport = String(req.body?.sport || '').toLowerCase();
|
|
if (!sport || !SUPPORTED.includes(sport)) {
|
|
return res.status(400).json({ error: 'invalid or missing sport', supported: SUPPORTED });
|
|
}
|
|
try {
|
|
const out = await provider.fullRefresh(sport, {
|
|
gradedProps: Array.isArray(req.body?.graded) ? req.body.graded : [],
|
|
});
|
|
return res.json(out);
|
|
} catch (err) {
|
|
return res.status(502).json({ error: 'refresh failed', detail: err?.message || 'unknown' });
|
|
}
|
|
});
|
|
|
|
router.get('/status', async (_req, res) => {
|
|
const sports = Object.values(SPORTS).map((s) => ({
|
|
key: s.key,
|
|
label: s.label,
|
|
active: s.active,
|
|
collect: s.collectData,
|
|
}));
|
|
return res.json({
|
|
sports,
|
|
runtime: provider.status(),
|
|
ts: new Date().toISOString(),
|
|
});
|
|
});
|
|
|
|
module.exports = router;
|