211 lines
9.0 KiB
JavaScript
211 lines
9.0 KiB
JavaScript
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;
|