diff --git a/backend/src/db.js b/backend/src/db.js index 30ade24..24083a6 100644 --- a/backend/src/db.js +++ b/backend/src/db.js @@ -322,6 +322,19 @@ if (poCols.length && !poCols.includes('retry')) if (poCols.length && !poCols.includes('expire')) db.exec("ALTER TABLE pushover_settings ADD COLUMN expire INTEGER DEFAULT NULL"); +// ── Changelog ───────────────────────────────────────────────────────────────── +if (!db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='changelog'").get()) { + db.exec(` + CREATE TABLE changelog ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + version TEXT NOT NULL, + title TEXT NOT NULL, + body TEXT NOT NULL DEFAULT '', + created_at DATETIME DEFAULT NULL + ) + `); +} + // ── Push-Zeitplaner ─────────────────────────────────────────────────────────── if (!db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='push_schedules'").get()) { db.exec(` diff --git a/backend/src/routes/dashboard.js b/backend/src/routes/dashboard.js index e089694..59d40ff 100644 --- a/backend/src/routes/dashboard.js +++ b/backend/src/routes/dashboard.js @@ -1,6 +1,6 @@ const express = require('express'); const db = require('../db'); -const { authenticate } = require('../middleware/auth'); +const { authenticate, requireAdmin } = require('../middleware/auth'); const router = express.Router(); const uid = req => req.user.id; @@ -206,6 +206,24 @@ router.post('/board/:id/promote', authenticate, (req, res) => { res.json(updated); }); +// ── Changelog ───────────────────────────────────────────────────────────────── +router.get('/changelog', authenticate, (req, res) => { + res.json(db.prepare('SELECT * FROM changelog ORDER BY created_at DESC').all()); +}); + +router.post('/changelog', authenticate, requireAdmin, (req, res) => { + const { version, title, body } = req.body; + if (!version?.trim() || !title?.trim()) return res.status(400).json({ error: 'Version und Titel erforderlich' }); + const r = db.prepare(`INSERT INTO changelog (version, title, body, created_at) VALUES (?,?,?,datetime('now','localtime'))`) + .run(version.trim(), title.trim(), body?.trim() || ''); + res.json(db.prepare('SELECT * FROM changelog WHERE id=?').get(r.lastInsertRowid)); +}); + +router.delete('/changelog/:id', authenticate, requireAdmin, (req, res) => { + db.prepare('DELETE FROM changelog WHERE id=?').run(req.params.id); + res.json({ ok: true }); +}); + // ── Push-Zeitplaner ─────────────────────────────────────────────────────────── router.get('/push-schedules', authenticate, (req, res) => { const rows = db.prepare(` diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index ac07756..6ec98a4 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1171,13 +1171,127 @@ function PushScheduler({ toast }) { ); } +// ── Changelog Modal ─────────────────────────────────────────────────────────── +function ChangelogModal({ user, toast, onClose }) { + const isAdmin = user?.role === 'admin'; + const [entries, setEntries] = useState([]); + const [form, setForm] = useState({ version:'', title:'', body:'' }); + const [busy, setBusy] = useState(false); + const isMobile = window.innerWidth < 768; + + useEffect(()=>{ + api('/dashboard/changelog').then(setEntries).catch(()=>{}); + },[]); + + const add = async () => { + if (!form.version.trim() || !form.title.trim()) { toast('Version und Titel erforderlich','error'); return; } + setBusy(true); + try { + const r = await api('/dashboard/changelog',{body:form}); + setEntries(p=>[r,...p]); + setForm({version:'',title:'',body:''}); + toast('Eintrag hinzugefügt ✓'); + } catch(e){ toast(e.message,'error'); } + setBusy(false); + }; + + const del = async id => { + try { await api(`/dashboard/changelog/${id}`,{method:'DELETE'}); setEntries(p=>p.filter(e=>e.id!==id)); } + catch(e){ toast(e.message,'error'); } + }; + + const fmtDate = s => { if (!s) return ''; const d = new Date(s.replace(' ','T')); return isNaN(d)?'':d.toLocaleDateString('de-DE',{day:'2-digit',month:'2-digit',year:'numeric'}); }; + + return ( +