From 71c9061e33e39204b18e8eb9d22b56c574aeaef1 Mon Sep 17 00:00:00 2001 From: Dicken Date: Wed, 27 May 2026 19:02:31 +0200 Subject: [PATCH] Dateien nach "backend/src/tools/nachrichten" hochladen --- backend/src/tools/nachrichten/routes.js | 171 ++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 backend/src/tools/nachrichten/routes.js diff --git a/backend/src/tools/nachrichten/routes.js b/backend/src/tools/nachrichten/routes.js new file mode 100644 index 0000000..e22947c --- /dev/null +++ b/backend/src/tools/nachrichten/routes.js @@ -0,0 +1,171 @@ +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) { + try { + await fetch('https://api.pushover.net/1/messages.json', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: new URLSearchParams({ token: appToken, user: userKey, title, message }), + }); + } 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 ───────────────────────────────────────────────────────────────────── +router.get('/users', authenticate, (req, res) => { + const me = req.user.id; + 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 AND deleted_by_recipient = 0) AS unread, + (SELECT MAX(created_at) FROM messages + WHERE ((sender_id = u.id AND recipient_id = ? AND deleted_by_recipient = 0) + OR (sender_id = ? AND recipient_id = u.id AND deleted_by_sender = 0))) AS last_message_at + FROM users u + LEFT JOIN user_keys uk ON uk.user_id = u.id + WHERE u.id != ? + 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 AND deleted_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); + + // Mark incoming as read + 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 m.id, m.sender_id, m.encrypted_content, m.iv, m.created_at, + (m.sender_id = ?) AS is_mine + FROM messages m + WHERE (m.sender_id = ? AND m.recipient_id = ? AND m.deleted_by_sender = 0) + OR (m.recipient_id = ? AND m.sender_id = ? AND m.deleted_by_recipient = 0) + ORDER BY m.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) + VALUES (?, ?, ?, ?) + `).run(me, recipId, encrypted_content, iv); + + // Pushover notification + 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) { + await sendPushover(pushover.user_key, pushover.app_token, 'DickenDock', `Neue Nachricht von ${senderName}`); + } + + res.json({ id: result.lastInsertRowid }); +}); + +// DELETE single message (own side only) +router.delete('/messages/:messageId', authenticate, (req, res) => { + const me = req.user.id; + const mid = parseInt(req.params.messageId); + const msg = db.prepare('SELECT * FROM messages WHERE id = ?').get(mid); + if (!msg) return res.status(404).json({ error: 'Nicht gefunden' }); + if (msg.sender_id !== me && msg.recipient_id !== me) + return res.status(403).json({ error: 'Kein Zugriff' }); + + if (msg.sender_id === me) { + db.prepare('UPDATE messages SET deleted_by_sender = 1 WHERE id = ?').run(mid); + } else { + db.prepare('UPDATE messages SET deleted_by_recipient = 1 WHERE id = ?').run(mid); + } + // Physical delete when both sides have deleted + db.prepare('DELETE FROM messages WHERE id = ? AND deleted_by_sender = 1 AND deleted_by_recipient = 1').run(mid); + res.json({ ok: true }); +}); + +// DELETE entire conversation (own side) +router.delete('/conversation/:userId', authenticate, (req, res) => { + const me = req.user.id; + const other = parseInt(req.params.userId); + + db.prepare('UPDATE messages SET deleted_by_sender = 1 WHERE sender_id = ? AND recipient_id = ?').run(me, other); + db.prepare('UPDATE messages SET deleted_by_recipient = 1 WHERE recipient_id = ? AND sender_id = ?').run(me, other); + // Physical cleanup where both deleted + db.prepare(` + DELETE FROM messages + WHERE deleted_by_sender = 1 AND deleted_by_recipient = 1 + AND ((sender_id = ? AND recipient_id = ?) OR (sender_id = ? AND recipient_id = ?)) + `).run(me, other, other, me); + res.json({ ok: true }); +}); + +// ── Pushover Settings ───────────────────────────────────────────────────────── +router.get('/pushover', authenticate, (req, res) => { + const row = db.prepare('SELECT user_key, app_token FROM pushover_settings WHERE user_id = ?').get(req.user.id); + res.json(row || { user_key: '', app_token: '' }); +}); + +router.post('/pushover', authenticate, (req, res) => { + const { user_key, app_token } = 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) + VALUES (?, ?, ?) + ON CONFLICT(user_id) DO UPDATE SET user_key=excluded.user_key, app_token=excluded.app_token + `).run(req.user.id, user_key, app_token); + 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' }); + await sendPushover(row.user_key, row.app_token, 'DickenDock', 'Pushover-Verbindung erfolgreich! 🎉'); + res.json({ ok: true }); +}); + +module.exports = router;