Add API documentation and underground station importer
- Introduced a new HTML documentation page for the oradio API, including a JavaScript file to handle dynamic content and API requests. - Added a CSS file for styling the documentation page. - Implemented an underground station importer script that fetches data from Radio-Browser and writes it to a JSON file. - Created a stats module to compute and manage vote and play statistics for radio stations. - Added a polyfill for modulepreload to ensure compatibility with older browsers.
This commit is contained in:
@@ -10,64 +10,64 @@ export const router = Router();
|
||||
router.use(requireAdmin);
|
||||
|
||||
router.post('/health-check', async (_req, res) => {
|
||||
const n = await runHealthCheck();
|
||||
res.json({ checked: n });
|
||||
const n = await runHealthCheck();
|
||||
res.json({ checked: n });
|
||||
});
|
||||
|
||||
router.post('/reseed', (_req, res) => {
|
||||
res.json(applySeedIfEmpty());
|
||||
res.json(applySeedIfEmpty());
|
||||
});
|
||||
|
||||
router.get('/system', (_req, res) => {
|
||||
const db = getDb();
|
||||
res.json({
|
||||
stations: db.prepare('SELECT COUNT(*) AS n FROM stations').get().n,
|
||||
streams: db.prepare('SELECT COUNT(*) AS n FROM streams').get().n,
|
||||
users: db.prepare('SELECT COUNT(*) AS n FROM users').get().n,
|
||||
favorites: db.prepare('SELECT COUNT(*) AS n FROM favorites').get().n,
|
||||
node: process.version,
|
||||
uptime_s: Math.round(process.uptime())
|
||||
});
|
||||
const db = getDb();
|
||||
res.json({
|
||||
stations: db.prepare('SELECT COUNT(*) AS n FROM stations').get().n,
|
||||
streams: db.prepare('SELECT COUNT(*) AS n FROM streams').get().n,
|
||||
users: db.prepare('SELECT COUNT(*) AS n FROM users').get().n,
|
||||
favorites: db.prepare('SELECT COUNT(*) AS n FROM favorites').get().n,
|
||||
node: process.version,
|
||||
uptime_s: Math.round(process.uptime())
|
||||
});
|
||||
});
|
||||
|
||||
// Scrape an icon for a single station.
|
||||
router.post('/stations/:id/scrape-icon', async (req, res) => {
|
||||
const id = Number(req.params.id);
|
||||
const st = getStation(id);
|
||||
if (!st) return res.status(404).json({ error: 'not found' });
|
||||
const url = await scrapeIcon(st);
|
||||
if (!url) return res.status(404).json({ error: 'no icon found' });
|
||||
const updated = updateStation(id, { image_url: url });
|
||||
res.json({ id, image_url: url, station: updated });
|
||||
const id = Number(req.params.id);
|
||||
const st = getStation(id);
|
||||
if (!st) return res.status(404).json({ error: 'not found' });
|
||||
const url = await scrapeIcon(st);
|
||||
if (!url) return res.status(404).json({ error: 'no icon found' });
|
||||
const updated = updateStation(id, { image_url: url });
|
||||
res.json({ id, image_url: url, station: updated });
|
||||
});
|
||||
|
||||
// Bulk: scrape icons for every station (optionally only those missing one).
|
||||
router.post('/scrape-icons', async (req, res) => {
|
||||
const onlyMissing = req.query.all !== '1';
|
||||
const stations = listStations({ enabled: null }).filter((s) => !onlyMissing || !s.image_url);
|
||||
const results = { total: stations.length, updated: 0, skipped: 0, failed: 0, items: [] };
|
||||
// Limit concurrency to avoid hammering hosts.
|
||||
const concurrency = 4;
|
||||
let i = 0;
|
||||
async function worker() {
|
||||
while (i < stations.length) {
|
||||
const s = stations[i++];
|
||||
try {
|
||||
const url = await scrapeIcon(s);
|
||||
if (url) {
|
||||
updateStation(s.id, { image_url: url });
|
||||
results.updated++;
|
||||
results.items.push({ id: s.id, name: s.name, image_url: url });
|
||||
} else {
|
||||
results.failed++;
|
||||
results.items.push({ id: s.id, name: s.name, image_url: null });
|
||||
const onlyMissing = req.query.all !== '1';
|
||||
const stations = listStations({ enabled: null }).filter((s) => !onlyMissing || !s.image_url);
|
||||
const results = { total: stations.length, updated: 0, skipped: 0, failed: 0, items: [] };
|
||||
// Limit concurrency to avoid hammering hosts.
|
||||
const concurrency = 4;
|
||||
let i = 0;
|
||||
async function worker() {
|
||||
while (i < stations.length) {
|
||||
const s = stations[i++];
|
||||
try {
|
||||
const url = await scrapeIcon(s);
|
||||
if (url) {
|
||||
updateStation(s.id, { image_url: url });
|
||||
results.updated++;
|
||||
results.items.push({ id: s.id, name: s.name, image_url: url });
|
||||
} else {
|
||||
results.failed++;
|
||||
results.items.push({ id: s.id, name: s.name, image_url: null });
|
||||
}
|
||||
} catch (err) {
|
||||
results.failed++;
|
||||
results.items.push({ id: s.id, name: s.name, error: String(err?.message || err) });
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
results.failed++;
|
||||
results.items.push({ id: s.id, name: s.name, error: String(err?.message || err) });
|
||||
}
|
||||
}
|
||||
}
|
||||
await Promise.all(Array.from({ length: concurrency }, worker));
|
||||
res.json(results);
|
||||
await Promise.all(Array.from({ length: concurrency }, worker));
|
||||
res.json(results);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user