/** * 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;