- 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.
143 lines
5.0 KiB
JavaScript
143 lines
5.0 KiB
JavaScript
import { randomUUID } from 'node:crypto';
|
|
import { getDb } from './db/index.js';
|
|
|
|
function rowToStation(row) {
|
|
if (!row) return null;
|
|
return {
|
|
id: row.id,
|
|
uuid: row.uuid,
|
|
name: row.name,
|
|
slug: row.slug,
|
|
homepage: row.homepage,
|
|
country: row.country,
|
|
genres: row.genres ? JSON.parse(row.genres) : [],
|
|
description: row.description,
|
|
image_url: row.image_url,
|
|
source: row.source,
|
|
source_ref: row.source_ref,
|
|
category: row.category,
|
|
enabled: !!row.enabled,
|
|
created_at: row.created_at,
|
|
updated_at: row.updated_at
|
|
};
|
|
}
|
|
|
|
export function listStations({ q, source, category, enabled = true } = {}) {
|
|
const db = getDb();
|
|
const where = [];
|
|
const params = [];
|
|
if (enabled !== null) { where.push('enabled = ?'); params.push(enabled ? 1 : 0); }
|
|
if (source) { where.push('source = ?'); params.push(source); }
|
|
if (category) { where.push('category = ?'); params.push(category); }
|
|
if (q) { where.push('(name LIKE ? OR genres LIKE ? OR country LIKE ?)'); params.push(`%${q}%`, `%${q}%`, `%${q}%`); }
|
|
const sql = `SELECT * FROM stations ${where.length ? 'WHERE ' + where.join(' AND ') : ''} ORDER BY name COLLATE NOCASE`;
|
|
return db.prepare(sql).all(...params).map(rowToStation);
|
|
}
|
|
|
|
export function getStation(id) {
|
|
return rowToStation(getDb().prepare('SELECT * FROM stations WHERE id = ?').get(id));
|
|
}
|
|
|
|
export function getStationByUuid(uuid) {
|
|
return rowToStation(getDb().prepare('SELECT * FROM stations WHERE uuid = ?').get(uuid));
|
|
}
|
|
|
|
export function getStreamsForStation(stationId) {
|
|
return getDb().prepare(
|
|
'SELECT * FROM streams WHERE station_id = ? ORDER BY priority ASC, id ASC'
|
|
).all(stationId);
|
|
}
|
|
|
|
export function getStreamByUuid(uuid) {
|
|
return getDb().prepare('SELECT * FROM streams WHERE uuid = ?').get(uuid);
|
|
}
|
|
|
|
export function slugify(name) {
|
|
return name.toLowerCase()
|
|
.replace(/[^a-z0-9]+/g, '-')
|
|
.replace(/^-+|-+$/g, '')
|
|
.slice(0, 80) || `station-${Date.now()}`;
|
|
}
|
|
|
|
export function uniqueSlug(base) {
|
|
const db = getDb();
|
|
let slug = base, n = 1;
|
|
while (db.prepare('SELECT 1 FROM stations WHERE slug = ?').get(slug)) {
|
|
n += 1;
|
|
slug = `${base}-${n}`;
|
|
}
|
|
return slug;
|
|
}
|
|
|
|
export function createStation(input, userId) {
|
|
const db = getDb();
|
|
const slug = input.slug || uniqueSlug(slugify(input.name));
|
|
const uuid = input.uuid || randomUUID();
|
|
const info = db.prepare(`
|
|
INSERT INTO stations (uuid, name, slug, homepage, country, genres, description, image_url, source, source_ref, category, created_by)
|
|
VALUES (@uuid, @name, @slug, @homepage, @country, @genres, @description, @image_url, @source, @source_ref, @category, @created_by)
|
|
`).run({
|
|
uuid,
|
|
name: input.name,
|
|
slug,
|
|
homepage: input.homepage ?? null,
|
|
country: input.country ?? null,
|
|
genres: JSON.stringify(input.genres ?? []),
|
|
description: input.description ?? null,
|
|
image_url: input.image_url ?? null,
|
|
source: input.source ?? 'manual',
|
|
source_ref: input.source_ref ?? null,
|
|
category: input.category ?? null,
|
|
created_by: userId ?? null
|
|
});
|
|
const id = info.lastInsertRowid;
|
|
for (const s of input.streams ?? []) {
|
|
db.prepare(`INSERT INTO streams (uuid, station_id, url, format, bitrate, label, priority)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)`)
|
|
.run(s.uuid || randomUUID(), id, s.url, s.format ?? 'unknown', s.bitrate ?? null, s.label ?? null, s.priority ?? 0);
|
|
}
|
|
return getStation(id);
|
|
}
|
|
|
|
export function updateStation(id, patch) {
|
|
const db = getDb();
|
|
const cur = getStation(id);
|
|
if (!cur) return null;
|
|
const next = { ...cur, ...patch };
|
|
db.prepare(`
|
|
UPDATE stations
|
|
SET name=@name, homepage=@homepage, country=@country, genres=@genres,
|
|
description=@description, image_url=@image_url, category=@category,
|
|
enabled=@enabled, updated_at=datetime('now')
|
|
WHERE id=@id
|
|
`).run({
|
|
id,
|
|
name: next.name,
|
|
homepage: next.homepage ?? null,
|
|
country: next.country ?? null,
|
|
genres: JSON.stringify(next.genres ?? []),
|
|
description: next.description ?? null,
|
|
image_url: next.image_url ?? null,
|
|
category: next.category ?? null,
|
|
enabled: next.enabled ? 1 : 0
|
|
});
|
|
return getStation(id);
|
|
}
|
|
|
|
export function deleteStation(id) {
|
|
return getDb().prepare('DELETE FROM stations WHERE id = ?').run(id).changes > 0;
|
|
}
|
|
|
|
export function addStream(stationId, s) {
|
|
const uuid = s.uuid || randomUUID();
|
|
const info = getDb().prepare(`
|
|
INSERT INTO streams (uuid, station_id, url, format, bitrate, label, priority)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
`).run(uuid, stationId, s.url, s.format ?? 'unknown', s.bitrate ?? null, s.label ?? null, s.priority ?? 0);
|
|
return getDb().prepare('SELECT * FROM streams WHERE id = ?').get(info.lastInsertRowid);
|
|
}
|
|
|
|
export function deleteStream(streamId) {
|
|
return getDb().prepare('DELETE FROM streams WHERE id = ?').run(streamId).changes > 0;
|
|
}
|