- 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.
72 lines
2.9 KiB
JavaScript
72 lines
2.9 KiB
JavaScript
import { Router } from 'express';
|
|
import {
|
|
verifyPassword, createSession, destroySession, setSessionCookie, clearSessionCookie,
|
|
hashPassword, requireAdmin
|
|
} from '../auth.js';
|
|
import { getDb } from '../db/index.js';
|
|
|
|
export const router = Router();
|
|
|
|
router.post('/login', (req, res) => {
|
|
const { username, password } = req.body || {};
|
|
if (!username || !password) return res.status(400).json({ error: 'username + password required' });
|
|
const user = getDb().prepare('SELECT * FROM users WHERE username = ?').get(username);
|
|
if (!user || !verifyPassword(password, user.password_hash)) {
|
|
return res.status(401).json({ error: 'invalid credentials' });
|
|
}
|
|
const { token, expires } = createSession(user.id);
|
|
setSessionCookie(res, token, expires);
|
|
res.json({ id: user.id, username: user.username, role: user.role });
|
|
});
|
|
|
|
router.post('/logout', (req, res) => {
|
|
destroySession(req.session?.token);
|
|
clearSessionCookie(res);
|
|
res.json({ ok: true });
|
|
});
|
|
|
|
router.get('/me', (req, res) => {
|
|
if (!req.user) return res.status(401).json({ error: 'not signed in' });
|
|
res.json(req.user);
|
|
});
|
|
|
|
// Admin-only user management
|
|
router.get('/users', requireAdmin, (_req, res) => {
|
|
const users = getDb().prepare('SELECT id, username, role, created_at FROM users ORDER BY username').all();
|
|
res.json(users);
|
|
});
|
|
|
|
router.post('/users', requireAdmin, (req, res) => {
|
|
const { username, password, role = 'user' } = req.body || {};
|
|
if (!username || !password) return res.status(400).json({ error: 'username + password required' });
|
|
if (!['admin', 'user'].includes(role)) return res.status(400).json({ error: 'bad role' });
|
|
try {
|
|
const info = getDb().prepare('INSERT INTO users (username, password_hash, role) VALUES (?, ?, ?)')
|
|
.run(username, hashPassword(password), role);
|
|
getDb().prepare('INSERT INTO profiles (user_id, display_name) VALUES (?, ?)')
|
|
.run(info.lastInsertRowid, username);
|
|
res.status(201).json({ id: info.lastInsertRowid, username, role });
|
|
} catch (err) {
|
|
if (String(err).includes('UNIQUE')) return res.status(409).json({ error: 'username taken' });
|
|
throw err;
|
|
}
|
|
});
|
|
|
|
router.patch('/users/:id', requireAdmin, (req, res) => {
|
|
const id = Number(req.params.id);
|
|
const { password, role } = req.body || {};
|
|
const db = getDb();
|
|
if (password) db.prepare('UPDATE users SET password_hash = ? WHERE id = ?').run(hashPassword(password), id);
|
|
if (role && ['admin', 'user'].includes(role)) {
|
|
db.prepare('UPDATE users SET role = ? WHERE id = ?').run(role, id);
|
|
}
|
|
res.json({ ok: true });
|
|
});
|
|
|
|
router.delete('/users/:id', requireAdmin, (req, res) => {
|
|
const id = Number(req.params.id);
|
|
if (id === req.user.id) return res.status(400).json({ error: 'cannot delete self' });
|
|
getDb().prepare('DELETE FROM users WHERE id = ?').run(id);
|
|
res.json({ ok: true });
|
|
});
|