Whiteboard: Name beim Erstellen eingeben, Pushover bei neuen Berechtigungen

This commit is contained in:
2026-06-25 20:50:16 +02:00
parent e96946e246
commit c5407afd48
2 changed files with 63 additions and 12 deletions

View File

@@ -125,7 +125,7 @@ router.get('/users-list', authenticate, (req, res) => {
});
// ── Berechtigungen setzen ─────────────────────────────────────────────────────
router.put('/:id/permissions', authenticate, (req, res) => {
router.put('/:id/permissions', authenticate, async (req, res) => {
const me = uid(req);
const wb = db.prepare('SELECT * FROM whiteboards WHERE id=?').get(req.params.id);
if (!wb) return res.status(404).json({ error: 'Nicht gefunden' });
@@ -133,16 +133,41 @@ router.put('/:id/permissions', authenticate, (req, res) => {
const { permissions } = req.body; // [{user_id, role: 'edit'|'view'|null}]
if (!Array.isArray(permissions)) return res.status(400).json({ error: 'permissions fehlt' });
const upsert = db.prepare("INSERT INTO whiteboard_permissions (whiteboard_id,user_id,role) VALUES (?,?,?) ON CONFLICT(whiteboard_id,user_id) DO UPDATE SET role=excluded.role");
const remove = db.prepare("DELETE FROM whiteboard_permissions WHERE whiteboard_id=? AND user_id=?");
const tx = db.transaction(() => {
// Vorherigen Stand merken um neue User zu erkennen
const before = db.prepare('SELECT user_id FROM whiteboard_permissions WHERE whiteboard_id=?').all(req.params.id).map(r => r.user_id);
const upsert = db.prepare("INSERT INTO whiteboard_permissions (whiteboard_id,user_id,role) VALUES (?,?,?) ON CONFLICT(whiteboard_id,user_id) DO UPDATE SET role=excluded.role");
const remove = db.prepare("DELETE FROM whiteboard_permissions WHERE whiteboard_id=? AND user_id=?");
db.transaction(() => {
for (const p of permissions) {
if (!p.user_id) continue;
if (p.role === null || p.role === 'none') remove.run(req.params.id, p.user_id);
else upsert.run(req.params.id, p.user_id, p.role);
}
});
tx();
})();
// Pushover an neu hinzugekommene User (fire & forget)
const owner = db.prepare('SELECT username FROM users WHERE id=?').get(me);
const newlyAdded = permissions.filter(p => p.role && p.role !== 'none' && !before.includes(p.user_id));
for (const p of newlyAdded) {
try {
const poCfg = db.prepare('SELECT user_key, app_token FROM pushover_settings WHERE user_id=?').get(p.user_id);
if (!poCfg?.app_token || !poCfg?.user_key) continue;
const roleLabel = p.role === 'edit' ? 'bearbeiten' : 'ansehen';
fetch('https://api.pushover.net/1/messages.json', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token: poCfg.app_token,
user: poCfg.user_key,
title: '🖊 Whiteboard geteilt',
message: `${owner.username} hat das Whiteboard "${wb.title}" mit dir geteilt (${roleLabel}).`,
priority: 0,
}),
}).catch(() => {});
} catch {}
}
const updated = db.prepare(`
SELECT p.*, u.username FROM whiteboard_permissions p
JOIN users u ON u.id=p.user_id WHERE p.whiteboard_id=?

View File

@@ -186,6 +186,8 @@ export default function Whiteboard({ toast, mobile }) {
const [boards, setBoards] = useState([]);
const [loading, setLoading] = useState(true);
const [current, setCurrent] = useState(null); // { id, title, role }
const [creating, setCreating] = useState(false);
const [newTitle, setNewTitle] = useState('');
// ── Canvas-State ──────────────────────────────────────────────────────────
const [elements, setElements] = useState([]);
@@ -516,12 +518,36 @@ export default function Whiteboard({ toast, mobile }) {
WHITEBOARD
</h2>
<div style={{ flex:1 }}/>
<button onClick={async () => {
try {
const wb = await api('/tools/whiteboard', { body: { title: 'Neues Whiteboard' } });
await openBoard(wb);
} catch(e) { toast(e.message, 'error'); }
}} style={S.btn('#4ECDC4')}>+ Neu</button>
{creating ? (
<div style={{ display:'flex', gap:6 }}>
<input autoFocus value={newTitle} onChange={e=>setNewTitle(e.target.value)}
onKeyDown={async e => {
if (e.key === 'Enter') {
if (!newTitle.trim()) return;
try {
const wb = await api('/tools/whiteboard', { body: { title: newTitle.trim() } });
setCreating(false); setNewTitle('');
await openBoard(wb);
} catch(err) { toast(err.message, 'error'); }
}
if (e.key === 'Escape') { setCreating(false); setNewTitle(''); }
}}
placeholder="Name des Whiteboards"
style={{ ...S.inp, width: mobile ? 160 : 220, fontSize:12 }}
/>
<button onClick={async () => {
if (!newTitle.trim()) return;
try {
const wb = await api('/tools/whiteboard', { body: { title: newTitle.trim() } });
setCreating(false); setNewTitle('');
await openBoard(wb);
} catch(err) { toast(err.message, 'error'); }
}} style={S.btn('#4ECDC4', true)}>Erstellen</button>
<button onClick={() => { setCreating(false); setNewTitle(''); }} style={S.btn('#888888', true)}></button>
</div>
) : (
<button onClick={() => setCreating(true)} style={S.btn('#4ECDC4')}>+ Neu</button>
)}
</div>
{loading && <div style={{ color:'rgba(255,255,255,0.3)', fontFamily:'monospace', fontSize:12 }}>Lädt</div>}