Dicken Dock v1.0.0
This commit is contained in:
30
backend/src/routes/admin.js
Normal file
30
backend/src/routes/admin.js
Normal file
@@ -0,0 +1,30 @@
|
||||
const express = require('express');
|
||||
const fs = require('fs');
|
||||
const multer = require('multer');
|
||||
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 });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
37
backend/src/routes/auth.js
Normal file
37
backend/src/routes/auth.js
Normal file
@@ -0,0 +1,37 @@
|
||||
const express = require('express');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const db = require('../db');
|
||||
const { authenticate } = require('../middleware/auth');
|
||||
|
||||
const router = express.Router();
|
||||
const SECRET = process.env.JWT_SECRET || 'dev-secret';
|
||||
|
||||
// POST /api/auth/login
|
||||
router.post('/login', (req, res) => {
|
||||
const { username, password } = req.body;
|
||||
const user = db.prepare('SELECT * FROM users WHERE username = ?').get(username);
|
||||
if (!user || !bcrypt.compareSync(password, user.password_hash))
|
||||
return res.status(401).json({ error: 'Benutzername oder Passwort falsch' });
|
||||
|
||||
const token = jwt.sign(
|
||||
{ id: user.id, username: user.username, role: user.role },
|
||||
SECRET, { expiresIn: '7d' }
|
||||
);
|
||||
res.json({ token, user: { id: user.id, username: user.username, role: user.role } });
|
||||
});
|
||||
|
||||
// POST /api/auth/change-password
|
||||
router.post('/change-password', authenticate, (req, res) => {
|
||||
const { currentPassword, newPassword } = req.body;
|
||||
const user = db.prepare('SELECT * FROM users WHERE id = ?').get(req.user.id);
|
||||
if (!bcrypt.compareSync(currentPassword, user.password_hash))
|
||||
return res.status(400).json({ error: 'Aktuelles Passwort falsch' });
|
||||
if (!newPassword || newPassword.length < 8)
|
||||
return res.status(400).json({ error: 'Mindestens 8 Zeichen erforderlich' });
|
||||
db.prepare('UPDATE users SET password_hash = ? WHERE id = ?')
|
||||
.run(bcrypt.hashSync(newPassword, 12), req.user.id);
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
70
backend/src/routes/dashboard.js
Normal file
70
backend/src/routes/dashboard.js
Normal file
@@ -0,0 +1,70 @@
|
||||
const express = require('express');
|
||||
const db = require('../db');
|
||||
const { authenticate } = require('../middleware/auth');
|
||||
const router = express.Router();
|
||||
const uid = req => req.user.id;
|
||||
|
||||
// ── Quick Links ───────────────────────────────────────────────────────────────
|
||||
router.get('/links', authenticate, (req, res) => {
|
||||
res.json(db.prepare('SELECT * FROM quick_links WHERE user_id=? ORDER BY sort_order,id').all(uid(req)));
|
||||
});
|
||||
router.post('/links', authenticate, (req, res) => {
|
||||
const { title, url, icon = '🔗' } = req.body;
|
||||
if (!title || !url) return res.status(400).json({ error: 'Titel und URL erforderlich' });
|
||||
const max = db.prepare('SELECT MAX(sort_order) m FROM quick_links WHERE user_id=?').get(uid(req));
|
||||
const r = db.prepare('INSERT INTO quick_links (user_id,title,url,icon,sort_order) VALUES (?,?,?,?,?)')
|
||||
.run(uid(req), title, url, icon, (max?.m ?? -1) + 1);
|
||||
res.json(db.prepare('SELECT * FROM quick_links WHERE id=?').get(r.lastInsertRowid));
|
||||
});
|
||||
router.put('/links/:id', authenticate, (req, res) => {
|
||||
const ex = db.prepare('SELECT * FROM quick_links WHERE id=? AND user_id=?').get(req.params.id, uid(req));
|
||||
if (!ex) return res.status(404).json({ error: 'Nicht gefunden' });
|
||||
const { title=ex.title, url=ex.url, icon=ex.icon } = req.body;
|
||||
db.prepare('UPDATE quick_links SET title=?,url=?,icon=? WHERE id=? AND user_id=?')
|
||||
.run(title, url, icon, req.params.id, uid(req));
|
||||
res.json(db.prepare('SELECT * FROM quick_links WHERE id=?').get(req.params.id));
|
||||
});
|
||||
router.delete('/links/:id', authenticate, (req, res) => {
|
||||
const r = db.prepare('DELETE FROM quick_links WHERE id=? AND user_id=?').run(req.params.id, uid(req));
|
||||
if (!r.changes) return res.status(404).json({ error: 'Nicht gefunden' });
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
// ── Todos ─────────────────────────────────────────────────────────────────────
|
||||
router.get('/todos', authenticate, (req, res) => {
|
||||
res.json(db.prepare('SELECT * FROM todos WHERE user_id=? ORDER BY done,sort_order,id').all(uid(req)));
|
||||
});
|
||||
router.post('/todos', authenticate, (req, res) => {
|
||||
const { text } = req.body;
|
||||
if (!text?.trim()) return res.status(400).json({ error: 'Text erforderlich' });
|
||||
const max = db.prepare('SELECT MAX(sort_order) m FROM todos WHERE user_id=?').get(uid(req));
|
||||
const r = db.prepare('INSERT INTO todos (user_id,text,sort_order) VALUES (?,?,?)')
|
||||
.run(uid(req), text.trim(), (max?.m ?? -1) + 1);
|
||||
res.json(db.prepare('SELECT * FROM todos WHERE id=?').get(r.lastInsertRowid));
|
||||
});
|
||||
router.put('/todos/:id', authenticate, (req, res) => {
|
||||
const ex = db.prepare('SELECT * FROM todos WHERE id=? AND user_id=?').get(req.params.id, uid(req));
|
||||
if (!ex) return res.status(404).json({ error: 'Nicht gefunden' });
|
||||
const text = req.body.text ?? ex.text;
|
||||
const done = req.body.done !== undefined ? (req.body.done ? 1 : 0) : ex.done;
|
||||
db.prepare('UPDATE todos SET text=?,done=? WHERE id=? AND user_id=?').run(text, done, req.params.id, uid(req));
|
||||
res.json(db.prepare('SELECT * FROM todos WHERE id=?').get(req.params.id));
|
||||
});
|
||||
router.delete('/todos/:id', authenticate, (req, res) => {
|
||||
const r = db.prepare('DELETE FROM todos WHERE id=? AND user_id=?').run(req.params.id, uid(req));
|
||||
if (!r.changes) return res.status(404).json({ error: 'Nicht gefunden' });
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
// ── Notizen ───────────────────────────────────────────────────────────────────
|
||||
router.get('/note', authenticate, (req, res) => {
|
||||
res.json(db.prepare('SELECT * FROM notes WHERE user_id=?').get(uid(req)) || { content: '', updated_at: null });
|
||||
});
|
||||
router.put('/note', authenticate, (req, res) => {
|
||||
db.prepare(`INSERT INTO notes (user_id,content,updated_at) VALUES (?,?,CURRENT_TIMESTAMP)
|
||||
ON CONFLICT(user_id) DO UPDATE SET content=excluded.content,updated_at=CURRENT_TIMESTAMP`)
|
||||
.run(uid(req), req.body.content ?? '');
|
||||
res.json({ success: true, updated_at: new Date().toISOString() });
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
40
backend/src/routes/system.js
Normal file
40
backend/src/routes/system.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const express = require('express');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { authenticate } = require('../middleware/auth');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
let VERSION = 'v1.0.0';
|
||||
try { VERSION = fs.readFileSync(path.join(__dirname, '../../version.txt'), 'utf8').trim(); } catch {}
|
||||
|
||||
const GITEA_URL = process.env.GITEA_URL;
|
||||
const GITEA_REPO = process.env.GITEA_REPO;
|
||||
|
||||
router.get('/version', authenticate, (_req, res) => res.json({ version: VERSION }));
|
||||
|
||||
router.get('/update-check', authenticate, async (_req, res) => {
|
||||
if (!GITEA_URL || !GITEA_REPO)
|
||||
return res.json({ hasUpdate: false, currentVersion: VERSION, configured: false });
|
||||
try {
|
||||
const r = await fetch(`${GITEA_URL}/api/v1/repos/${GITEA_REPO}/releases?limit=5`,
|
||||
{ signal: AbortSignal.timeout(5000) });
|
||||
const releases = await r.json();
|
||||
if (!Array.isArray(releases) || !releases.length)
|
||||
return res.json({ hasUpdate: false, currentVersion: VERSION, configured: true });
|
||||
res.json({
|
||||
hasUpdate: releases[0].tag_name !== VERSION,
|
||||
currentVersion: VERSION,
|
||||
latestVersion: releases[0].tag_name,
|
||||
configured: true,
|
||||
releases: releases.map(r => ({
|
||||
version: r.tag_name, name: r.name || r.tag_name,
|
||||
body: r.body || '(Keine Beschreibung)', publishedAt: r.published_at,
|
||||
})),
|
||||
});
|
||||
} catch (e) {
|
||||
res.json({ hasUpdate: false, currentVersion: VERSION, configured: true });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user