mirror of
https://github.com/TheBinaryNinja/tvapp2.git
synced 2026-06-12 15:05:41 -04:00
initial push from external dev branches
This commit is contained in:
13
server/dist/routes/activeStreams.js
vendored
Normal file
13
server/dist/routes/activeStreams.js
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Router } from 'express';
|
||||
import { ActiveStream } from '../models/ActiveStream.js';
|
||||
export const activeStreamsRouter = Router();
|
||||
activeStreamsRouter.get('/', async (_req, res, next) => {
|
||||
try {
|
||||
const docs = await ActiveStream.find({}, { _id: 0 }).lean();
|
||||
res.json(docs);
|
||||
}
|
||||
catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
//# sourceMappingURL=activeStreams.js.map
|
||||
1
server/dist/routes/activeStreams.js.map
vendored
Normal file
1
server/dist/routes/activeStreams.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"activeStreams.js","sourceRoot":"","sources":["../../src/routes/activeStreams.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,EAAE,CAAC;AAE5C,mBAAmB,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;IACrD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAC5D,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,CAAC;AACH,CAAC,CAAC,CAAC"}
|
||||
13
server/dist/routes/activity.js
vendored
Normal file
13
server/dist/routes/activity.js
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Router } from 'express';
|
||||
import { Activity } from '../models/Activity.js';
|
||||
export const activityRouter = Router();
|
||||
activityRouter.get('/', async (_req, res, next) => {
|
||||
try {
|
||||
const docs = await Activity.find({}, { _id: 0, order: 0 }).sort({ order: 1 }).lean();
|
||||
res.json(docs);
|
||||
}
|
||||
catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
//# sourceMappingURL=activity.js.map
|
||||
1
server/dist/routes/activity.js.map
vendored
Normal file
1
server/dist/routes/activity.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"activity.js","sourceRoot":"","sources":["../../src/routes/activity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,EAAE,CAAC;AAEvC,cAAc,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;IAChD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACrF,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,CAAC;AACH,CAAC,CAAC,CAAC"}
|
||||
22
server/dist/routes/channels.js
vendored
Normal file
22
server/dist/routes/channels.js
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Router } from 'express';
|
||||
import { Channel } from '../models/Channel.js';
|
||||
import { SourceChannel } from '../models/SourceChannel.js';
|
||||
export const channelsRouter = Router();
|
||||
// GET /api/channels → legacy mock channels (drives the existing dashboard bootstrap)
|
||||
// GET /api/channels?source=<id> → canonical normalized SourceChannel docs for a (Default) source
|
||||
// playlist (the d-combine "path forward" contract, served over Mongo)
|
||||
channelsRouter.get('/', async (req, res, next) => {
|
||||
try {
|
||||
const source = typeof req.query.source === 'string' ? req.query.source : null;
|
||||
if (source) {
|
||||
const docs = await SourceChannel.find({ source }).sort({ groupKey: 1, name: 1 }).lean();
|
||||
return res.json(docs);
|
||||
}
|
||||
const docs = await Channel.find({}, { _id: 0 }).lean();
|
||||
res.json(docs);
|
||||
}
|
||||
catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
//# sourceMappingURL=channels.js.map
|
||||
1
server/dist/routes/channels.js.map
vendored
Normal file
1
server/dist/routes/channels.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"channels.js","sourceRoot":"","sources":["../../src/routes/channels.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAE3D,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,EAAE,CAAC;AAEvC,gGAAgG;AAChG,iGAAiG;AACjG,sGAAsG;AACtG,cAAc,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;IAC/C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,OAAO,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;QAC9E,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;YACxF,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxB,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACvD,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,CAAC;AACH,CAAC,CAAC,CAAC"}
|
||||
13
server/dist/routes/customPlaylists.js
vendored
Normal file
13
server/dist/routes/customPlaylists.js
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Router } from 'express';
|
||||
import { CustomPlaylist } from '../models/CustomPlaylist.js';
|
||||
export const customPlaylistsRouter = Router();
|
||||
customPlaylistsRouter.get('/', async (_req, res, next) => {
|
||||
try {
|
||||
const docs = await CustomPlaylist.find({}, { _id: 0 }).lean();
|
||||
res.json(docs);
|
||||
}
|
||||
catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
//# sourceMappingURL=customPlaylists.js.map
|
||||
1
server/dist/routes/customPlaylists.js.map
vendored
Normal file
1
server/dist/routes/customPlaylists.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"customPlaylists.js","sourceRoot":"","sources":["../../src/routes/customPlaylists.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAE7D,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,EAAE,CAAC;AAE9C,qBAAqB,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;IACvD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAC9D,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,CAAC;AACH,CAAC,CAAC,CAAC"}
|
||||
13
server/dist/routes/epgSources.js
vendored
Normal file
13
server/dist/routes/epgSources.js
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Router } from 'express';
|
||||
import { EpgSource } from '../models/EpgSource.js';
|
||||
export const epgSourcesRouter = Router();
|
||||
epgSourcesRouter.get('/', async (_req, res, next) => {
|
||||
try {
|
||||
const docs = await EpgSource.find({}, { _id: 0 }).lean();
|
||||
res.json(docs);
|
||||
}
|
||||
catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
//# sourceMappingURL=epgSources.js.map
|
||||
1
server/dist/routes/epgSources.js.map
vendored
Normal file
1
server/dist/routes/epgSources.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"epgSources.js","sourceRoot":"","sources":["../../src/routes/epgSources.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEnD,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,EAAE,CAAC;AAEzC,gBAAgB,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;IAClD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACzD,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,CAAC;AACH,CAAC,CAAC,CAAC"}
|
||||
7
server/dist/routes/health.js
vendored
Normal file
7
server/dist/routes/health.js
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Router } from 'express';
|
||||
import { isConnected } from '../db.js';
|
||||
export const healthRouter = Router();
|
||||
healthRouter.get('/', (_req, res) => {
|
||||
res.json({ ok: true, mongo: isConnected() ? 'connected' : 'disconnected' });
|
||||
});
|
||||
//# sourceMappingURL=health.js.map
|
||||
1
server/dist/routes/health.js.map
vendored
Normal file
1
server/dist/routes/health.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/routes/health.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEvC,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,EAAE,CAAC;AAErC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;IAClC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC;AAC9E,CAAC,CAAC,CAAC"}
|
||||
106
server/dist/routes/playlists.js
vendored
Normal file
106
server/dist/routes/playlists.js
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
import { Router } from 'express';
|
||||
import { Playlist } from '../models/Playlist.js';
|
||||
import { PlaylistChannel } from '../models/PlaylistChannel.js';
|
||||
import { Channel } from '../models/Channel.js';
|
||||
import { SourceChannel } from '../models/SourceChannel.js';
|
||||
import { toUiChannel } from '../sources/translate.js';
|
||||
export const playlistsRouter = Router();
|
||||
// Channel count comes from SourceChannel for (Default) source playlists, else the legacy join table.
|
||||
async function channelCountFor(doc) {
|
||||
if (doc.source)
|
||||
return SourceChannel.countDocuments({ source: doc.source });
|
||||
return PlaylistChannel.countDocuments({ playlistId: doc.id });
|
||||
}
|
||||
playlistsRouter.get('/', async (_req, res, next) => {
|
||||
try {
|
||||
const docs = await Playlist.find({}, { _id: 0 }).lean();
|
||||
const [legacyCounts, sourceCounts] = await Promise.all([
|
||||
PlaylistChannel.aggregate([
|
||||
{ $group: { _id: '$playlistId', count: { $sum: 1 } } },
|
||||
]),
|
||||
SourceChannel.aggregate([
|
||||
{ $group: { _id: '$source', count: { $sum: 1 } } },
|
||||
]),
|
||||
]);
|
||||
const legacyById = new Map(legacyCounts.map((c) => [c._id, c.count]));
|
||||
const sourceBySource = new Map(sourceCounts.map((c) => [c._id, c.count]));
|
||||
res.json(docs.map((d) => ({
|
||||
...d,
|
||||
channels: d.source ? sourceBySource.get(d.source) ?? 0 : legacyById.get(d.id) ?? 0,
|
||||
})));
|
||||
}
|
||||
catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
playlistsRouter.get('/:id', async (req, res, next) => {
|
||||
try {
|
||||
const doc = await Playlist.findOne({ id: req.params.id }, { _id: 0 }).lean();
|
||||
if (!doc)
|
||||
return res.status(404).json({ error: 'not_found' });
|
||||
res.json({ ...doc, channels: await channelCountFor(doc) });
|
||||
}
|
||||
catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
// List channels in a playlist. (Default) source playlists project the canonical SourceChannel docs
|
||||
// through the translation layer (UI shape, nulls for unmapped fields); legacy playlists use the join.
|
||||
playlistsRouter.get('/:id/channels', async (req, res, next) => {
|
||||
try {
|
||||
const playlist = await Playlist.findOne({ id: req.params.id }).lean();
|
||||
if (!playlist)
|
||||
return res.status(404).json({ error: 'not_found' });
|
||||
if (playlist.source) {
|
||||
const docs = await SourceChannel.find({ source: playlist.source })
|
||||
.sort({ groupKey: 1, name: 1 })
|
||||
.lean();
|
||||
return res.json(docs.map((d, order) => ({ ...toUiChannel(d), order })));
|
||||
}
|
||||
const memberships = await PlaylistChannel.find({ playlistId: req.params.id }, { _id: 0 })
|
||||
.sort({ order: 1 })
|
||||
.lean();
|
||||
const channelIds = memberships.map((m) => m.channelId);
|
||||
const channels = await Channel.find({ id: { $in: channelIds } }, { _id: 0 }).lean();
|
||||
const byId = new Map(channels.map((c) => [c.id, c]));
|
||||
res.json(memberships
|
||||
.map((m) => {
|
||||
const ch = byId.get(m.channelId);
|
||||
return ch ? { ...ch, order: m.order } : null;
|
||||
})
|
||||
.filter(Boolean));
|
||||
}
|
||||
catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
// Add a channel to a (legacy) playlist (idempotent on the unique pair).
|
||||
playlistsRouter.post('/:id/channels', async (req, res, next) => {
|
||||
try {
|
||||
const { channelId, order } = req.body ?? {};
|
||||
if (typeof channelId !== 'string' || typeof order !== 'number') {
|
||||
return res.status(400).json({ error: 'channelId (string) and order (number) required' });
|
||||
}
|
||||
const doc = await PlaylistChannel.findOneAndUpdate({ playlistId: req.params.id, channelId }, { $set: { order } }, { upsert: true, new: true, projection: { _id: 0 } }).lean();
|
||||
res.status(201).json(doc);
|
||||
}
|
||||
catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
// Remove a channel from a (legacy) playlist.
|
||||
playlistsRouter.delete('/:id/channels/:channelId', async (req, res, next) => {
|
||||
try {
|
||||
const result = await PlaylistChannel.deleteOne({
|
||||
playlistId: req.params.id,
|
||||
channelId: req.params.channelId,
|
||||
});
|
||||
if (result.deletedCount === 0)
|
||||
return res.status(404).json({ error: 'not_found' });
|
||||
res.status(204).end();
|
||||
}
|
||||
catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
//# sourceMappingURL=playlists.js.map
|
||||
1
server/dist/routes/playlists.js.map
vendored
Normal file
1
server/dist/routes/playlists.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
19
server/dist/routes/programs.js
vendored
Normal file
19
server/dist/routes/programs.js
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Router } from 'express';
|
||||
import { Program } from '../models/Program.js';
|
||||
export const programsRouter = Router();
|
||||
// All programs, grouped by channelId — matches the EPG_PROGRAMS shape the SPA expects.
|
||||
programsRouter.get('/', async (_req, res, next) => {
|
||||
try {
|
||||
const docs = await Program.find({}, { _id: 0 }).sort({ channelId: 1, start: 1 }).lean();
|
||||
const grouped = {};
|
||||
for (const d of docs) {
|
||||
const list = grouped[d.channelId] ?? (grouped[d.channelId] = []);
|
||||
list.push({ start: d.start, end: d.end, title: d.title, cat: d.cat });
|
||||
}
|
||||
res.json(grouped);
|
||||
}
|
||||
catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
//# sourceMappingURL=programs.js.map
|
||||
1
server/dist/routes/programs.js.map
vendored
Normal file
1
server/dist/routes/programs.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"programs.js","sourceRoot":"","sources":["../../src/routes/programs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAE/C,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,EAAE,CAAC;AAEvC,uFAAuF;AACvF,cAAc,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;IAChD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACxF,MAAM,OAAO,GAAsF,EAAE,CAAC;QACtG,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,CAAC;YACjE,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;QACxE,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,CAAC;AACH,CAAC,CAAC,CAAC"}
|
||||
88
server/dist/routes/sources.js
vendored
Normal file
88
server/dist/routes/sources.js
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
// Generic source RestAPI, 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: ${req.params.id}` });
|
||||
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: ${req.params.id}` });
|
||||
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: ${req.params.id}` });
|
||||
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: ${req.params.id}` });
|
||||
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
|
||||
1
server/dist/routes/sources.js.map
vendored
Normal file
1
server/dist/routes/sources.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"sources.js","sourceRoot":"","sources":["../../src/routes/sources.ts"],"names":[],"mappings":"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"}
|
||||
13
server/dist/routes/streamSessions.js
vendored
Normal file
13
server/dist/routes/streamSessions.js
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Router } from 'express';
|
||||
import { StreamSession } from '../models/StreamSession.js';
|
||||
export const streamSessionsRouter = Router();
|
||||
streamSessionsRouter.get('/', async (_req, res, next) => {
|
||||
try {
|
||||
const docs = await StreamSession.find({}, { _id: 0, order: 0 }).sort({ order: 1 }).lean();
|
||||
res.json(docs);
|
||||
}
|
||||
catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
//# sourceMappingURL=streamSessions.js.map
|
||||
1
server/dist/routes/streamSessions.js.map
vendored
Normal file
1
server/dist/routes/streamSessions.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"streamSessions.js","sourceRoot":"","sources":["../../src/routes/streamSessions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAE3D,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,EAAE,CAAC;AAE7C,oBAAoB,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;IACtD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAC1F,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,CAAC;AACH,CAAC,CAAC,CAAC"}
|
||||
Reference in New Issue
Block a user