Files
tvapp2/server/dist/routes/sources.js
2026-06-11 18:10:09 -04:00

88 lines
4.3 KiB
JavaScript

// Generic source REST API, ported from ../d-combine/server.mjs into TVApp2's Express stack. One router
// serves every source by iterating the registry — adding a source needs zero route changes.
//
// GET /api/sources manifest (drives the SPA; one entry per registered source)
// GET /api/sources/:id/status runtime provenance (dlhd: live mirror; null otherwise)
// GET /api/sources/:id/metrics per-source proxy counters
// POST /api/sources/:id/sync live refresh → upsert channels + Playlist sync metadata
// POST /api/sources/:id/reset restore the committed bundle baseline
// GET /api/v1/:source/* single stream proxy; the :source segment binds that source's
// resolve+proxy behavior (createProxyHandler per adapter)
//
// Mounted at the app root (app.use(sourcesRouter)) because its paths span /api/sources, /api/v1, …
import { Router } from 'express';
import { SOURCES, getSource } from '../sources/registry.js';
import { createProxyHandler } from '../sources/core/proxyHandler.js';
import { createMetrics, snapshotOne } from '../sources/core/metrics.js';
import { syncLive, resetFromBundle } from '../sources/seed.js';
export const sourcesRouter = Router();
// Build one proxy handler (+ metrics bag) per source once, then dispatch by the :source segment.
const metricsById = new Map();
const proxyHandlers = new Map();
for (const adapter of SOURCES) {
const m = createMetrics();
metricsById.set(adapter.id, m);
proxyHandlers.set(adapter.id, createProxyHandler(adapter, m));
}
// ── Manifest ────────────────────────────────────────────────────────────────
sourcesRouter.get('/api/sources', (_req, res) => {
res.json(SOURCES.map((s) => ({
id: s.id,
label: s.label,
grouping: s.grouping,
sourceUrl: `/api/channels?source=${s.id}`, // normalized catalog over Mongo
proxyPrefix: `/api/v1/${s.id}/`,
statusUrl: s.status ? `/api/sources/${s.id}/status` : null,
})));
});
// ── Per-source runtime status (dlhd mirror provenance; null for sources without one) ──
sourcesRouter.get('/api/sources/:id/status', async (req, res, next) => {
try {
const adapter = getSource(req.params.id);
if (!adapter)
return res.status(404).json({ error: 'unknown_source' });
const status = adapter.status ? await adapter.status() : null;
res.json(status ?? null);
}
catch (err) {
next(err);
}
});
// ── Per-source proxy metrics ──────────────────────────────────────────────────
sourcesRouter.get('/api/sources/:id/metrics', (req, res) => {
const m = metricsById.get(req.params.id);
if (!m)
return res.status(404).json({ error: 'unknown_source' });
res.json(snapshotOne(m));
});
// ── Live sync (refresh channels + Playlist sync metadata from upstream) ───────
sourcesRouter.post('/api/sources/:id/sync', async (req, res, next) => {
try {
if (!getSource(req.params.id))
return res.status(404).json({ error: 'unknown_source' });
res.json(await syncLive(req.params.id));
}
catch (err) {
next(err);
}
});
// ── Reset to the committed bundle baseline ────────────────────────────────────
sourcesRouter.post('/api/sources/:id/reset', async (req, res, next) => {
try {
if (!getSource(req.params.id))
return res.status(404).json({ error: 'unknown_source' });
res.json(await resetFromBundle(req.params.id));
}
catch (err) {
next(err);
}
});
// ── Single stream proxy API ───────────────────────────────────────────────────
sourcesRouter.get('/api/v1/:source/*', (req, res) => {
const handler = proxyHandlers.get(req.params.source);
if (!handler) {
return res.status(404).type('text/plain').send(`Unknown source: ${req.params.source}`);
}
return handler(req, res, () => undefined);
});
//# sourceMappingURL=sources.js.map