Whiteboard: Name beim Erstellen eingeben, Pushover bei neuen Berechtigungen
This commit is contained in:
@@ -125,7 +125,7 @@ router.get('/users-list', authenticate, (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ── Berechtigungen setzen ─────────────────────────────────────────────────────
|
// ── Berechtigungen setzen ─────────────────────────────────────────────────────
|
||||||
router.put('/:id/permissions', authenticate, (req, res) => {
|
router.put('/:id/permissions', authenticate, async (req, res) => {
|
||||||
const me = uid(req);
|
const me = uid(req);
|
||||||
const wb = db.prepare('SELECT * FROM whiteboards WHERE id=?').get(req.params.id);
|
const wb = db.prepare('SELECT * FROM whiteboards WHERE id=?').get(req.params.id);
|
||||||
if (!wb) return res.status(404).json({ error: 'Nicht gefunden' });
|
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}]
|
const { permissions } = req.body; // [{user_id, role: 'edit'|'view'|null}]
|
||||||
if (!Array.isArray(permissions)) return res.status(400).json({ error: 'permissions fehlt' });
|
if (!Array.isArray(permissions)) return res.status(400).json({ error: 'permissions fehlt' });
|
||||||
|
|
||||||
|
// 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 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 remove = db.prepare("DELETE FROM whiteboard_permissions WHERE whiteboard_id=? AND user_id=?");
|
||||||
const tx = db.transaction(() => {
|
db.transaction(() => {
|
||||||
for (const p of permissions) {
|
for (const p of permissions) {
|
||||||
if (!p.user_id) continue;
|
if (!p.user_id) continue;
|
||||||
if (p.role === null || p.role === 'none') remove.run(req.params.id, p.user_id);
|
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);
|
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(`
|
const updated = db.prepare(`
|
||||||
SELECT p.*, u.username FROM whiteboard_permissions p
|
SELECT p.*, u.username FROM whiteboard_permissions p
|
||||||
JOIN users u ON u.id=p.user_id WHERE p.whiteboard_id=?
|
JOIN users u ON u.id=p.user_id WHERE p.whiteboard_id=?
|
||||||
|
|||||||
@@ -186,6 +186,8 @@ export default function Whiteboard({ toast, mobile }) {
|
|||||||
const [boards, setBoards] = useState([]);
|
const [boards, setBoards] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [current, setCurrent] = useState(null); // { id, title, role }
|
const [current, setCurrent] = useState(null); // { id, title, role }
|
||||||
|
const [creating, setCreating] = useState(false);
|
||||||
|
const [newTitle, setNewTitle] = useState('');
|
||||||
|
|
||||||
// ── Canvas-State ──────────────────────────────────────────────────────────
|
// ── Canvas-State ──────────────────────────────────────────────────────────
|
||||||
const [elements, setElements] = useState([]);
|
const [elements, setElements] = useState([]);
|
||||||
@@ -516,12 +518,36 @@ export default function Whiteboard({ toast, mobile }) {
|
|||||||
WHITEBOARD
|
WHITEBOARD
|
||||||
</h2>
|
</h2>
|
||||||
<div style={{ flex:1 }}/>
|
<div style={{ flex:1 }}/>
|
||||||
<button onClick={async () => {
|
{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 {
|
try {
|
||||||
const wb = await api('/tools/whiteboard', { body: { title: 'Neues Whiteboard' } });
|
const wb = await api('/tools/whiteboard', { body: { title: newTitle.trim() } });
|
||||||
|
setCreating(false); setNewTitle('');
|
||||||
await openBoard(wb);
|
await openBoard(wb);
|
||||||
} catch(e) { toast(e.message, 'error'); }
|
} catch(err) { toast(err.message, 'error'); }
|
||||||
}} style={S.btn('#4ECDC4')}>+ Neu</button>
|
}
|
||||||
|
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>
|
</div>
|
||||||
|
|
||||||
{loading && <div style={{ color:'rgba(255,255,255,0.3)', fontFamily:'monospace', fontSize:12 }}>Lädt…</div>}
|
{loading && <div style={{ color:'rgba(255,255,255,0.3)', fontFamily:'monospace', fontSize:12 }}>Lädt…</div>}
|
||||||
|
|||||||
Reference in New Issue
Block a user