const express = require('express'); const db = require('../../db'); const { authenticate } = require('../../middleware/auth'); const router = express.Router(); // ── Pushover Helper ─────────────────────────────────────────────────────────── async function sendPushover(userKey, appToken, title, message, opts = {}) { try { const params = { token: appToken, user: userKey, title, message }; if (opts.retry && opts.expire) { params.priority = 2; params.retry = opts.retry; params.expire = opts.expire; } await fetch('https://api.pushover.net/1/messages.json', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams(params), }); } catch {} } // ── Public Keys ─────────────────────────────────────────────────────────────── router.get('/keys/:userId', authenticate, (req, res) => { const row = db.prepare('SELECT public_key FROM user_keys WHERE user_id = ?').get(req.params.userId); res.json({ public_key: row?.public_key || null }); }); router.post('/keys', authenticate, (req, res) => { const { public_key } = req.body; if (!public_key) return res.status(400).json({ error: 'Kein Schlüssel' }); db.prepare(` INSERT INTO user_keys (user_id, public_key, updated_at) VALUES (?, ?, CURRENT_TIMESTAMP) ON CONFLICT(user_id) DO UPDATE SET public_key=excluded.public_key, updated_at=CURRENT_TIMESTAMP `).run(req.user.id, public_key); res.json({ ok: true }); }); // ── Users ───────────────────────────────────────────────────────────────────── // Presence: User ist gerade aktiv im Chat router.post('/presence', authenticate, (req, res) => { const { active } = req.body; if (active) { db.prepare("UPDATE users SET chat_active_at=datetime('now','localtime') WHERE id=?").run(req.user.id); } else { db.prepare("UPDATE users SET chat_active_at=NULL WHERE id=?").run(req.user.id); } res.json({ ok: true }); }); router.get('/users', authenticate, (req, res) => { const me = req.user.id; const isAdmin = req.user.role === 'admin'; // Admins sehen alle. Normale User sehen: // - keine hidden User // - wenn sie selbst hidden sind: nur Admins const meUser = db.prepare('SELECT hidden FROM users WHERE id=?').get(me); const isMeHidden = !!meUser?.hidden; let whereExtra = ''; if (!isAdmin && isMeHidden) { // Ich bin hidden → sehe nur Admins whereExtra = "AND u.role = 'admin'"; } else if (!isAdmin) { // Normaler User → sieht keine hidden User whereExtra = "AND (u.hidden = 0 OR u.hidden IS NULL)"; } // Admin → kein Filter const users = db.prepare(` SELECT u.id, u.username, uk.public_key, (SELECT COUNT(*) FROM messages WHERE sender_id = u.id AND recipient_id = ? AND read_by_recipient = 0) AS unread, (SELECT MAX(created_at) FROM messages WHERE (sender_id = u.id AND recipient_id = ?) OR (sender_id = ? AND recipient_id = u.id)) AS last_message_at FROM users u LEFT JOIN user_keys uk ON uk.user_id = u.id WHERE u.id != ? ${whereExtra} ORDER BY last_message_at DESC, u.username ASC `).all(me, me, me, me); res.json(users); }); // ── Unread Count ────────────────────────────────────────────────────────────── router.get('/unread', authenticate, (req, res) => { const row = db.prepare( 'SELECT COUNT(*) AS count FROM messages WHERE recipient_id = ? AND read_by_recipient = 0' ).get(req.user.id); res.json({ count: row.count }); }); // ── Messages ────────────────────────────────────────────────────────────────── router.get('/messages/:userId', authenticate, (req, res) => { const me = req.user.id; const other = parseInt(req.params.userId); db.prepare(` UPDATE messages SET read_by_recipient = 1 WHERE recipient_id = ? AND sender_id = ? AND read_by_recipient = 0 `).run(me, other); const msgs = db.prepare(` SELECT id, sender_id, encrypted_content, iv, created_at, (sender_id = ?) AS is_mine, read_by_recipient FROM messages WHERE (sender_id = ? AND recipient_id = ?) OR (recipient_id = ? AND sender_id = ?) ORDER BY created_at ASC `).all(me, me, other, me, other); res.json(msgs); }); router.post('/messages/:userId', authenticate, async (req, res) => { const me = req.user.id; const recipId = parseInt(req.params.userId); const { encrypted_content, iv } = req.body; if (!encrypted_content || !iv) return res.status(400).json({ error: 'Fehlende Felder' }); const recipient = db.prepare('SELECT id FROM users WHERE id = ?').get(recipId); if (!recipient) return res.status(404).json({ error: 'Benutzer nicht gefunden' }); const result = db.prepare( "INSERT INTO messages (sender_id, recipient_id, encrypted_content, iv, created_at) VALUES (?, ?, ?, ?, datetime('now', 'localtime'))" ).run(me, recipId, encrypted_content, iv); const senderName = db.prepare('SELECT username FROM users WHERE id = ?').get(me)?.username || 'Jemand'; const pushover = db.prepare('SELECT * FROM pushover_settings WHERE user_id = ?').get(recipId); if (pushover) { // Kein Push wenn Empfänger gerade im Chat ist (aktiv in letzten 45 Sekunden) const presence = db.prepare(` SELECT chat_active_at FROM users WHERE id=? AND chat_active_at > datetime('now','localtime','-45 seconds') `).get(recipId); if (!presence) { await sendPushover(pushover.user_key, pushover.app_token, 'DickenDock', `Neue Nachricht von ${senderName}`, { retry: pushover.retry, expire: pushover.expire }); } } res.json({ id: result.lastInsertRowid }); }); // DELETE einzelne Nachricht – hard delete, beide Seiten dürfen löschen router.delete('/messages/:messageId', authenticate, (req, res) => { const me = req.user.id; const mid = parseInt(req.params.messageId); db.prepare( 'DELETE FROM messages WHERE id = ? AND (sender_id = ? OR recipient_id = ?)' ).run(mid, me, me); res.json({ ok: true }); }); // DELETE gesamte Konversation – hard delete router.delete('/conversation/:userId', authenticate, (req, res) => { const me = req.user.id; const other = parseInt(req.params.userId); db.prepare(` DELETE FROM messages WHERE (sender_id = ? AND recipient_id = ?) OR (sender_id = ? AND recipient_id = ?) `).run(me, other, other, me); res.json({ ok: true }); }); // DELETE alle eigenen Nachrichten (für Schlüssel-Reset) router.delete('/my-messages', authenticate, (req, res) => { db.prepare('DELETE FROM messages WHERE sender_id = ? OR recipient_id = ?') .run(req.user.id, req.user.id); res.json({ ok: true }); }); // ── Pushover Settings ───────────────────────────────────────────────────────── router.get('/pushover', authenticate, (req, res) => { const row = db.prepare('SELECT user_key, app_token, retry, expire FROM pushover_settings WHERE user_id = ?').get(req.user.id); res.json(row || { user_key: '', app_token: '', retry: null, expire: null }); }); router.post('/pushover', authenticate, (req, res) => { const { user_key, app_token, retry, expire } = req.body; if (!user_key || !app_token) return res.status(400).json({ error: 'Beide Felder erforderlich' }); db.prepare(` INSERT INTO pushover_settings (user_id, user_key, app_token, retry, expire) VALUES (?, ?, ?, ?, ?) ON CONFLICT(user_id) DO UPDATE SET user_key=excluded.user_key, app_token=excluded.app_token, retry=excluded.retry, expire=excluded.expire `).run(req.user.id, user_key, app_token, retry || null, expire || null); res.json({ ok: true }); }); router.delete('/pushover', authenticate, (req, res) => { db.prepare('DELETE FROM pushover_settings WHERE user_id = ?').run(req.user.id); res.json({ ok: true }); }); router.post('/pushover/test', authenticate, async (req, res) => { const row = db.prepare('SELECT * FROM pushover_settings WHERE user_id = ?').get(req.user.id); if (!row) return res.status(400).json({ error: 'Keine Pushover-Einstellungen gespeichert' }); const useEmergency = req.body?.emergency === true && row.retry && row.expire; const msg = useEmergency ? `🚨 Emergency-Test! Wiederholt alle ${row.retry}s für max. ${row.expire}s. Bitte in Pushover quittieren.` : 'Pushover-Verbindung erfolgreich! 🎉'; await sendPushover(row.user_key, row.app_token, 'DickenDock', msg, useEmergency ? { retry: row.retry, expire: row.expire } : {}); res.json({ ok: true }); }); module.exports = router;