98 lines
4.9 KiB
JavaScript
98 lines
4.9 KiB
JavaScript
const express = require('express');
|
||
const fs = require('fs');
|
||
const bcrypt = require('bcryptjs');
|
||
const multer = require('multer');
|
||
const db = require('../db');
|
||
const { authenticate, requireAdmin } = require('../middleware/auth');
|
||
|
||
const router = express.Router();
|
||
const DB_PATH = process.env.DB_PATH || '/data/dickendock.db';
|
||
const upload = multer({ dest: '/tmp/' });
|
||
|
||
// GET /api/admin/backup
|
||
router.get('/backup', authenticate, requireAdmin, (req, res) => {
|
||
if (!fs.existsSync(DB_PATH)) return res.status(404).json({ error: 'DB nicht gefunden' });
|
||
const date = new Date().toISOString().split('T')[0];
|
||
res.download(DB_PATH, `dickendock-backup-${date}.db`);
|
||
});
|
||
|
||
// POST /api/admin/restore
|
||
router.post('/restore', authenticate, requireAdmin, upload.single('database'), (req, res) => {
|
||
if (!req.file) return res.status(400).json({ error: 'Keine Datei erhalten' });
|
||
try {
|
||
if (fs.existsSync(DB_PATH)) fs.copyFileSync(DB_PATH, DB_PATH + '.bak');
|
||
fs.copyFileSync(req.file.path, DB_PATH);
|
||
fs.unlinkSync(req.file.path);
|
||
res.json({ success: true });
|
||
} catch (e) {
|
||
res.status(500).json({ error: e.message });
|
||
}
|
||
});
|
||
|
||
// GET /api/admin/users – alle Benutzer mit Statistiken
|
||
router.get('/users', authenticate, requireAdmin, (req, res) => {
|
||
const users = db.prepare("SELECT id, username, role, created_at FROM users ORDER BY role DESC, username").all();
|
||
const stats = users.map(u => {
|
||
const archiv = db.prepare('SELECT COUNT(*) c FROM calculations WHERE user_id=?').get(u.id).c;
|
||
const bestellung = db.prepare('SELECT COUNT(*) c FROM orders WHERE user_id=?').get(u.id).c;
|
||
const todos = db.prepare('SELECT COUNT(*) c FROM todos WHERE user_id=?').get(u.id).c;
|
||
const links = db.prepare('SELECT COUNT(*) c FROM quick_links WHERE user_id=?').get(u.id).c;
|
||
const files = db.prepare('SELECT COUNT(*) c FROM files WHERE user_id=?').get(u.id).c;
|
||
const noteLen = (db.prepare('SELECT content FROM notes WHERE user_id=?').get(u.id)?.content || '').length;
|
||
// iCals are stored in localStorage per user - not in DB, so we can't count them server-side
|
||
const icals = 0;
|
||
return { ...u, archiv, bestellung, todos, links, files, icals, noteLen };
|
||
});
|
||
res.json(stats);
|
||
});
|
||
|
||
// POST /api/admin/users – neuen Benutzer anlegen
|
||
router.post('/users', authenticate, requireAdmin, (req, res) => {
|
||
const { username, password, role = 'user' } = req.body;
|
||
if (!username?.trim() || username.trim().length < 3)
|
||
return res.status(400).json({ error: 'Benutzername mind. 3 Zeichen' });
|
||
if (!password || password.length < 8)
|
||
return res.status(400).json({ error: 'Passwort mind. 8 Zeichen' });
|
||
if (!['user','admin'].includes(role))
|
||
return res.status(400).json({ error: 'Ungültige Rolle' });
|
||
const exists = db.prepare('SELECT id FROM users WHERE username=?').get(username.trim());
|
||
if (exists) return res.status(400).json({ error: 'Benutzername bereits vergeben' });
|
||
const r = db.prepare('INSERT INTO users (username, password_hash, role) VALUES (?,?,?)')
|
||
.run(username.trim(), bcrypt.hashSync(password, 12), role);
|
||
res.json({ id: r.lastInsertRowid, username: username.trim(), role });
|
||
});
|
||
|
||
// DELETE /api/admin/users/:id – Benutzer löschen
|
||
router.delete('/users/:id', authenticate, requireAdmin, (req, res) => {
|
||
const id = parseInt(req.params.id);
|
||
if (id === req.user.id) return res.status(400).json({ error: 'Du kannst dich nicht selbst löschen' });
|
||
const user = db.prepare('SELECT * FROM users WHERE id=?').get(id);
|
||
if (!user) return res.status(404).json({ error: 'Benutzer nicht gefunden' });
|
||
// Sicherstellen dass mindestens ein Admin bleibt
|
||
if (user.role === 'admin') {
|
||
const adminCount = db.prepare("SELECT COUNT(*) c FROM users WHERE role='admin'").get().c;
|
||
if (adminCount <= 1) return res.status(400).json({ error: 'Mindestens ein Administrator muss verbleiben' });
|
||
}
|
||
// Cascade: alle Daten des Users löschen
|
||
db.prepare('DELETE FROM calculations WHERE user_id=?').run(id);
|
||
db.prepare('DELETE FROM orders WHERE user_id=?').run(id);
|
||
db.prepare('DELETE FROM todos WHERE user_id=?').run(id);
|
||
db.prepare('DELETE FROM quick_links WHERE user_id=?').run(id);
|
||
db.prepare('DELETE FROM notes WHERE user_id=?').run(id);
|
||
db.prepare('DELETE FROM users WHERE id=?').run(id);
|
||
res.json({ success: true });
|
||
});
|
||
|
||
// PUT /api/admin/users/:id/reset-password – Passwort zurücksetzen
|
||
router.put('/users/:id/reset-password', authenticate, requireAdmin, (req, res) => {
|
||
const { newPassword } = req.body;
|
||
if (!newPassword || newPassword.length < 8)
|
||
return res.status(400).json({ error: 'Mind. 8 Zeichen' });
|
||
const user = db.prepare('SELECT * FROM users WHERE id=?').get(req.params.id);
|
||
if (!user) return res.status(404).json({ error: 'Nicht gefunden' });
|
||
db.prepare('UPDATE users SET password_hash=? WHERE id=?').run(bcrypt.hashSync(newPassword, 12), req.params.id);
|
||
res.json({ success: true });
|
||
});
|
||
|
||
module.exports = router;
|