Dateien nach "backend/src/routes" hochladen
This commit is contained in:
@@ -1,29 +1,40 @@
|
||||
const express = require('express');
|
||||
const webpush = require('web-push');
|
||||
const https = require('https');
|
||||
const crypto = require('crypto');
|
||||
const db = require('../db');
|
||||
const { authenticate } = require('../middleware/auth');
|
||||
const router = express.Router();
|
||||
|
||||
// ── VAPID-Setup (einmalig, in DB gespeichert) ─────────────────────────────────
|
||||
function getVapidKeys() {
|
||||
// ── Minimales VAPID ohne externe Abhängigkeit ────────────────────────────────
|
||||
function getOrCreateVapidKeys() {
|
||||
const pub = db.prepare("SELECT value FROM admin_settings WHERE key='vapid_public'").get();
|
||||
const priv = db.prepare("SELECT value FROM admin_settings WHERE key='vapid_private'").get();
|
||||
if (pub && priv) return { publicKey: pub.value, privateKey: priv.value };
|
||||
const keys = webpush.generateVAPIDKeys();
|
||||
db.prepare("INSERT OR REPLACE INTO admin_settings (key,value) VALUES ('vapid_public',?)").run(keys.publicKey);
|
||||
db.prepare("INSERT OR REPLACE INTO admin_settings (key,value) VALUES ('vapid_private',?)").run(keys.privateKey);
|
||||
return keys;
|
||||
// Generate ECDH P-256 key pair for VAPID
|
||||
const ecdh = crypto.createECDH('prime256v1');
|
||||
ecdh.generateKeys();
|
||||
const publicKey = ecdh.getPublicKey('base64url');
|
||||
const privateKey = ecdh.getPrivateKey('base64url');
|
||||
db.prepare("INSERT OR REPLACE INTO admin_settings (key,value) VALUES ('vapid_public',?)").run(publicKey);
|
||||
db.prepare("INSERT OR REPLACE INTO admin_settings (key,value) VALUES ('vapid_private',?)").run(privateKey);
|
||||
return { publicKey, privateKey };
|
||||
}
|
||||
const vapid = getVapidKeys();
|
||||
webpush.setVapidDetails('mailto:admin@dickendock.local', vapid.publicKey, vapid.privateKey);
|
||||
|
||||
// GET /vapid-key – öffentlicher VAPID-Schlüssel für den Browser
|
||||
const vapid = getOrCreateVapidKeys();
|
||||
|
||||
// Simple push notification – uses fetch to push service (no web-push dep needed for basic use)
|
||||
// For full VAPID we'd need JWT signing – we skip actual push for now and rely on polling
|
||||
async function sendPushNotification(sub, payload) {
|
||||
// Placeholder: in production, use proper VAPID JWT. For now we skip push sending
|
||||
// The frontend polls every 10s anyway as fallback
|
||||
}
|
||||
|
||||
// GET /vapid-key
|
||||
router.get('/vapid-key', authenticate, (req, res) => {
|
||||
res.json({ publicKey: vapid.publicKey });
|
||||
});
|
||||
|
||||
// ── Öffentliche Schlüssel ─────────────────────────────────────────────────────
|
||||
// POST /keys – eigenen öffentlichen Schlüssel speichern
|
||||
// ── Schlüssel ─────────────────────────────────────────────────────────────────
|
||||
router.post('/keys', authenticate, (req, res) => {
|
||||
const { publicKey } = req.body;
|
||||
if (!publicKey) return res.status(400).json({ error: 'Kein Schlüssel' });
|
||||
@@ -31,19 +42,18 @@ router.post('/keys', authenticate, (req, res) => {
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
// GET /keys/:username – öffentlichen Schlüssel eines Users abrufen
|
||||
router.get('/keys/:username', authenticate, (req, res) => {
|
||||
const user = db.prepare('SELECT id FROM users WHERE username=?').get(req.params.username);
|
||||
if (!user) return res.status(404).json({ error: 'User nicht gefunden' });
|
||||
const key = db.prepare('SELECT public_key FROM user_keys WHERE user_id=?').get(user.id);
|
||||
if (!key) return res.status(404).json({ error: 'Noch kein Schlüssel hinterlegt' });
|
||||
if (!key) return res.status(404).json({ error: 'Noch kein Schlüssel' });
|
||||
res.json({ publicKey: key.public_key });
|
||||
});
|
||||
|
||||
// ── Push-Subscriptions ────────────────────────────────────────────────────────
|
||||
// ── Push Subscriptions ────────────────────────────────────────────────────────
|
||||
router.post('/subscribe', authenticate, (req, res) => {
|
||||
const { endpoint, keys } = req.body;
|
||||
if (!endpoint || !keys?.p256dh || !keys?.auth) return res.status(400).json({ error: 'Ungültige Subscription' });
|
||||
if (!endpoint || !keys?.p256dh || !keys?.auth) return res.status(400).json({ error: 'Ungültig' });
|
||||
db.prepare('INSERT OR REPLACE INTO push_subscriptions (user_id,endpoint,p256dh,auth) VALUES (?,?,?,?)')
|
||||
.run(req.user.id, endpoint, keys.p256dh, keys.auth);
|
||||
res.json({ success: true });
|
||||
@@ -55,7 +65,6 @@ router.delete('/subscribe', authenticate, (req, res) => {
|
||||
});
|
||||
|
||||
// ── Nachrichten ───────────────────────────────────────────────────────────────
|
||||
// GET /conversations – alle Gesprächspartner mit ungelesener Anzahl
|
||||
router.get('/conversations', authenticate, (req, res) => {
|
||||
const uid = req.user.id;
|
||||
const convs = db.prepare(`
|
||||
@@ -63,7 +72,7 @@ router.get('/conversations', authenticate, (req, res) => {
|
||||
CASE WHEN m.sender_id=? THEN m.recipient_id ELSE m.sender_id END AS partner_id,
|
||||
u.username AS partner_name,
|
||||
MAX(m.created_at) AS last_at,
|
||||
COUNT(CASE WHEN m.recipient_id=? AND m.deleted_by_recipient=0 THEN 1 END) AS unread
|
||||
SUM(CASE WHEN m.recipient_id=? AND m.deleted_by_recipient=0 THEN 1 ELSE 0 END) AS unread
|
||||
FROM messages m
|
||||
JOIN users u ON u.id=(CASE WHEN m.sender_id=? THEN m.recipient_id ELSE m.sender_id END)
|
||||
WHERE (m.sender_id=? AND m.deleted_by_sender=0)
|
||||
@@ -74,13 +83,13 @@ router.get('/conversations', authenticate, (req, res) => {
|
||||
res.json(convs);
|
||||
});
|
||||
|
||||
// GET /unread-count – Gesamtanzahl ungelesener Nachrichten
|
||||
router.get('/unread-count', authenticate, (req, res) => {
|
||||
const count = db.prepare('SELECT COUNT(*) c FROM messages WHERE recipient_id=? AND deleted_by_recipient=0').get(req.user.id).c;
|
||||
const count = db.prepare(
|
||||
'SELECT COUNT(*) c FROM messages WHERE recipient_id=? AND deleted_by_recipient=0'
|
||||
).get(req.user.id).c;
|
||||
res.json({ count });
|
||||
});
|
||||
|
||||
// GET /with/:username – Nachrichten mit einem User
|
||||
router.get('/with/:username', authenticate, (req, res) => {
|
||||
const uid = req.user.id;
|
||||
const partner = db.prepare('SELECT id FROM users WHERE username=?').get(req.params.username);
|
||||
@@ -97,41 +106,21 @@ router.get('/with/:username', authenticate, (req, res) => {
|
||||
res.json(msgs);
|
||||
});
|
||||
|
||||
// GET /users – alle User außer sich selbst (für neues Gespräch)
|
||||
router.get('/users', authenticate, (req, res) => {
|
||||
const users = db.prepare('SELECT id,username FROM users WHERE id!=? ORDER BY username').all(req.user.id);
|
||||
res.json(users);
|
||||
res.json(db.prepare('SELECT id,username FROM users WHERE id!=? ORDER BY username').all(req.user.id));
|
||||
});
|
||||
|
||||
// POST /send – verschlüsselte Nachricht senden
|
||||
router.post('/send', authenticate, async (req, res) => {
|
||||
router.post('/send', authenticate, (req, res) => {
|
||||
const { recipient_username, encrypted_content, iv } = req.body;
|
||||
if (!recipient_username || !encrypted_content || !iv)
|
||||
return res.status(400).json({ error: 'Fehlende Felder' });
|
||||
const recipient = db.prepare('SELECT id FROM users WHERE username=?').get(recipient_username);
|
||||
if (!recipient) return res.status(404).json({ error: 'Empfänger nicht gefunden' });
|
||||
|
||||
const r = db.prepare('INSERT INTO messages (sender_id,recipient_id,encrypted_content,iv) VALUES (?,?,?,?)')
|
||||
.run(req.user.id, recipient.id, encrypted_content, iv);
|
||||
|
||||
// Push-Benachrichtigung an Empfänger
|
||||
const subs = db.prepare('SELECT * FROM push_subscriptions WHERE user_id=?').all(recipient.id);
|
||||
const sender = db.prepare('SELECT username FROM users WHERE id=?').get(req.user.id);
|
||||
for (const sub of subs) {
|
||||
try {
|
||||
await webpush.sendNotification(
|
||||
{ endpoint: sub.endpoint, keys: { p256dh: sub.p256dh, auth: sub.auth } },
|
||||
JSON.stringify({ title: `Nachricht von ${sender.username}`, body: '🔒 Verschlüsselte Nachricht', icon: '/favicon.svg' })
|
||||
);
|
||||
} catch(e) {
|
||||
if (e.statusCode === 410) db.prepare('DELETE FROM push_subscriptions WHERE endpoint=?').run(sub.endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
res.json({ id: r.lastInsertRowid, success: true });
|
||||
});
|
||||
|
||||
// DELETE /:id – Nachricht für sich löschen
|
||||
router.delete('/:id', authenticate, (req, res) => {
|
||||
const uid = req.user.id;
|
||||
const msg = db.prepare('SELECT * FROM messages WHERE id=?').get(req.params.id);
|
||||
@@ -139,10 +128,11 @@ router.delete('/:id', authenticate, (req, res) => {
|
||||
if (msg.sender_id !== uid && msg.recipient_id !== uid)
|
||||
return res.status(403).json({ error: 'Kein Zugriff' });
|
||||
|
||||
if (msg.sender_id === uid) db.prepare('UPDATE messages SET deleted_by_sender=1 WHERE id=?').run(msg.id);
|
||||
else db.prepare('UPDATE messages SET deleted_by_recipient=1 WHERE id=?').run(msg.id);
|
||||
if (msg.sender_id === uid)
|
||||
db.prepare('UPDATE messages SET deleted_by_sender=1 WHERE id=?').run(msg.id);
|
||||
else
|
||||
db.prepare('UPDATE messages SET deleted_by_recipient=1 WHERE id=?').run(msg.id);
|
||||
|
||||
// Wenn beide gelöscht → komplett entfernen
|
||||
const updated = db.prepare('SELECT * FROM messages WHERE id=?').get(msg.id);
|
||||
if (updated?.deleted_by_sender && updated?.deleted_by_recipient)
|
||||
db.prepare('DELETE FROM messages WHERE id=?').run(msg.id);
|
||||
@@ -150,18 +140,15 @@ router.delete('/:id', authenticate, (req, res) => {
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
// DELETE /conversation/:username – ganzes Gespräch für sich löschen
|
||||
router.delete('/conversation/:username', authenticate, (req, res) => {
|
||||
const uid = req.user.id;
|
||||
const partner = db.prepare('SELECT id FROM users WHERE username=?').get(req.params.username);
|
||||
if (!partner) return res.status(404).json({ error: 'User nicht gefunden' });
|
||||
|
||||
db.prepare('UPDATE messages SET deleted_by_sender=1 WHERE sender_id=? AND recipient_id=?').run(uid, partner.id);
|
||||
db.prepare('UPDATE messages SET deleted_by_recipient=1 WHERE recipient_id=? AND sender_id=?').run(uid, partner.id);
|
||||
// Komplett löschen wenn beide weg
|
||||
db.prepare('DELETE FROM messages WHERE sender_id IN (?,?) AND recipient_id IN (?,?) AND deleted_by_sender=1 AND deleted_by_recipient=1')
|
||||
.run(uid, partner.id, uid, partner.id);
|
||||
|
||||
db.prepare(`DELETE FROM messages WHERE
|
||||
((sender_id=? AND recipient_id=?) OR (sender_id=? AND recipient_id=?))
|
||||
AND deleted_by_sender=1 AND deleted_by_recipient=1`).run(uid, partner.id, partner.id, uid);
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user