Add player functionality with HLS support and API integration

- Implemented a new Player class in player.js to handle audio playback, including HLS support using hls.js.
- Created a shared API module in api.js for making HTTP requests with proper error handling.
- Added DOM utility functions in dom.js for creating and clearing elements.
- Introduced WebSocket connection handling in ws.js for real-time updates.
- Developed a comprehensive CSS stylesheet for styling the application, including a high-contrast theme.
This commit is contained in:
Marco Mooren
2026-05-10 14:43:00 +02:00
commit e0a60f7b64
51 changed files with 9022 additions and 0 deletions

71
server/routes/auth.js Normal file
View File

@@ -0,0 +1,71 @@
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 });
});