feat: zentrales Pushover-Logging + neuer Admin-Tab Logs
This commit is contained in:
@@ -636,4 +636,18 @@ db.exec(`
|
||||
)
|
||||
`);
|
||||
|
||||
// Log aller verschickten Pushover-Nachrichten (für Admin-Übersicht "Logs")
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS push_log (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
||||
title TEXT NOT NULL DEFAULT '',
|
||||
message TEXT NOT NULL DEFAULT '',
|
||||
priority INTEGER NOT NULL DEFAULT 0,
|
||||
source TEXT NOT NULL DEFAULT '',
|
||||
success INTEGER NOT NULL DEFAULT 1,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
|
||||
module.exports = db;
|
||||
|
||||
@@ -3,6 +3,7 @@ const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
require('./db');
|
||||
const { logPush } = require('./pushLog');
|
||||
|
||||
const app = express();
|
||||
// Hinter Nginx Proxy Manager (SSL-Terminierung) — sonst meldet req.protocol
|
||||
@@ -32,22 +33,25 @@ app.use((req, res, next) => {
|
||||
if (wasInactive && user?.role !== 'admin') {
|
||||
const username = db.prepare('SELECT username FROM users WHERE id=?').get(p.id)?.username || 'Jemand';
|
||||
const admins = db.prepare(`
|
||||
SELECT p.user_key, p.app_token FROM pushover_settings p
|
||||
SELECT p.user_id, p.user_key, p.app_token FROM pushover_settings p
|
||||
JOIN users u ON u.id = p.user_id
|
||||
WHERE u.role = 'admin' AND p.user_key IS NOT NULL AND p.app_token IS NOT NULL
|
||||
`).all();
|
||||
for (const admin of admins) {
|
||||
const title = '👤 DickenDock';
|
||||
const message = `${username} ist gerade online gegangen.`;
|
||||
fetch('https://api.pushover.net/1/messages.json', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
token: admin.app_token,
|
||||
user: admin.user_key,
|
||||
title: '👤 DickenDock',
|
||||
message: `${username} ist gerade online gegangen.`,
|
||||
title,
|
||||
message,
|
||||
priority: -1,
|
||||
}),
|
||||
}).catch(() => {});
|
||||
logPush({ userId: admin.user_id, title, message, priority: -1, source: 'presence' });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -280,11 +284,14 @@ mqttClient.setMediaAckAllHandler(() => {
|
||||
mqttClient.publishMediaAnfragen();
|
||||
});
|
||||
|
||||
async function sendPushoverMsg(userKey, appToken, message, opts = {}) {
|
||||
async function sendPushoverMsg(userKey, appToken, message, opts = {}, userId = null) {
|
||||
const title = 'DickenDock Erinnerung';
|
||||
let priority = 0;
|
||||
try {
|
||||
const params = { token: appToken, user: userKey, title: 'DickenDock Erinnerung', message };
|
||||
const params = { token: appToken, user: userKey, title, message };
|
||||
if (opts.retry && opts.expire) {
|
||||
params.priority = 2;
|
||||
priority = 2;
|
||||
params.retry = opts.retry;
|
||||
params.expire = opts.expire;
|
||||
}
|
||||
@@ -293,7 +300,11 @@ async function sendPushoverMsg(userKey, appToken, message, opts = {}) {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams(params),
|
||||
});
|
||||
} catch(e) { console.error('Pushover Fehler:', e.message); }
|
||||
logPush({ userId, title, message, priority, source: 'dashboard-erinnerung' });
|
||||
} catch(e) {
|
||||
console.error('Pushover Fehler:', e.message);
|
||||
logPush({ userId, title, message, priority, source: 'dashboard-erinnerung', success: false });
|
||||
}
|
||||
}
|
||||
|
||||
// Beim Start auf die nächste volle Minute synchronisieren
|
||||
@@ -319,7 +330,7 @@ async function runScheduler() {
|
||||
`).all(now);
|
||||
for (const row of due) {
|
||||
await sendPushoverMsg(row.user_key, row.app_token, row.message,
|
||||
{ retry: row.retry, expire: row.expire });
|
||||
{ retry: row.retry, expire: row.expire }, row.user_id);
|
||||
db.prepare('UPDATE push_schedules SET sent=1 WHERE id=?').run(row.id);
|
||||
console.log(`✓ Push gesendet an user ${row.user_id}: "${row.message}" (fällig: ${row.scheduled_at})`);
|
||||
}
|
||||
|
||||
17
backend/src/pushLog.js
Normal file
17
backend/src/pushLog.js
Normal file
@@ -0,0 +1,17 @@
|
||||
const db = require('./db');
|
||||
|
||||
// Wird von jeder Stelle im Code aufgerufen, die eine Pushover-Nachricht
|
||||
// verschickt — unabhängig vom Versand selbst, damit ein Fehler im Logging
|
||||
// nie den eigentlichen Push verhindert.
|
||||
function logPush({ userId = null, title = '', message = '', priority = 0, source = '', success = true }) {
|
||||
try {
|
||||
db.prepare(`
|
||||
INSERT INTO push_log (user_id, title, message, priority, source, success, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, datetime('now','localtime'))
|
||||
`).run(userId, title, message, priority ?? 0, source, success ? 1 : 0);
|
||||
} catch (e) {
|
||||
console.error('Push-Log Fehler:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { logPush };
|
||||
@@ -3,6 +3,7 @@ const fs = require('fs');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const multer = require('multer');
|
||||
const db = require('../db');
|
||||
const { logPush } = require('../pushLog');
|
||||
const { authenticate, requireAdmin } = require('../middleware/auth');
|
||||
|
||||
const router = express.Router();
|
||||
@@ -191,16 +192,25 @@ router.post('/users/:id/test-push', authenticate, requireAdmin, async (req, res)
|
||||
const poCfg = db.prepare('SELECT user_key, app_token FROM pushover_settings WHERE user_id=?').get(user.id);
|
||||
if (!poCfg?.app_token || !poCfg?.user_key) return res.status(400).json({ error: 'Kein Pushover eingerichtet' });
|
||||
const { message = 'Wat is mit meinen Fische?' } = req.body;
|
||||
const title = '📣 DockStation';
|
||||
try {
|
||||
const r = await 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: '📣 DockStation', message, priority: 0 }),
|
||||
body: JSON.stringify({ token: poCfg.app_token, user: poCfg.user_key, title, message, priority: 0 }),
|
||||
});
|
||||
const d = await r.json();
|
||||
if (d.status === 1) res.json({ ok: true });
|
||||
else res.status(500).json({ error: d.errors?.join(', ') || 'Pushover-Fehler' });
|
||||
} catch(e) { res.status(500).json({ error: e.message }); }
|
||||
if (d.status === 1) {
|
||||
logPush({ userId: user.id, title, message, priority: 0, source: 'admin-test' });
|
||||
res.json({ ok: true });
|
||||
} else {
|
||||
logPush({ userId: user.id, title, message, priority: 0, source: 'admin-test', success: false });
|
||||
res.status(500).json({ error: d.errors?.join(', ') || 'Pushover-Fehler' });
|
||||
}
|
||||
} catch(e) {
|
||||
logPush({ userId: user.id, title, message, priority: 0, source: 'admin-test', success: false });
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
// ── WebDAV / Synology NAS Einstellungen ───────────────────────────────────
|
||||
@@ -500,4 +510,18 @@ router.get('/disk-analysis', authenticate, requireAdmin, (req, res) => {
|
||||
res.json({ locations: result, tmpFiles, nodeModulesMb: fmt(nodeSize), bigFiles: bigFiles.slice(0,20) });
|
||||
});
|
||||
|
||||
// GET /push-logs – Übersicht aller verschickten Pushover-Nachrichten (Admin)
|
||||
router.get('/push-logs', authenticate, requireAdmin, (req, res) => {
|
||||
const limit = Math.min(parseInt(req.query.limit) || 200, 1000);
|
||||
const logs = db.prepare(`
|
||||
SELECT pl.id, pl.user_id, u.username, pl.title, pl.message, pl.priority,
|
||||
pl.source, pl.success, pl.created_at
|
||||
FROM push_log pl
|
||||
LEFT JOIN users u ON u.id = pl.user_id
|
||||
ORDER BY pl.id DESC
|
||||
LIMIT ?
|
||||
`).all(limit);
|
||||
res.json(logs);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const express = require('express');
|
||||
const db = require('../../db');
|
||||
const { logPush } = require('../../pushLog');
|
||||
const { authenticate } = require('../../middleware/auth');
|
||||
const router = express.Router();
|
||||
|
||||
@@ -207,6 +208,7 @@ function sendPush(userId, title, message) {
|
||||
method:'POST', headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ token:cfg.app_token, user:cfg.user_key, title, message, priority:0 }),
|
||||
}).catch(() => {});
|
||||
logPush({ userId, title, message, priority: 0, source: 'gebietseroberung' });
|
||||
}
|
||||
|
||||
// Spiel laden + Fog anwenden + echte Scores beifügen
|
||||
|
||||
@@ -2,6 +2,7 @@ const express = require('express');
|
||||
const https = require('https');
|
||||
const db = require('../../db');
|
||||
const mqttClient = require('../../mqtt');
|
||||
const { logPush } = require('../../pushLog');
|
||||
const { authenticate } = require('../../middleware/auth');
|
||||
const router = express.Router();
|
||||
|
||||
@@ -430,14 +431,15 @@ function formatOfferMessage(offers) {
|
||||
async function sendKoepiPushover(offers, onlyUserId = null) {
|
||||
const recipients = onlyUserId
|
||||
? db.prepare(`
|
||||
SELECT user_key, app_token FROM pushover_settings
|
||||
SELECT user_id, user_key, app_token FROM pushover_settings
|
||||
WHERE user_id = ? AND user_key IS NOT NULL AND app_token IS NOT NULL
|
||||
`).all(onlyUserId)
|
||||
: db.prepare(`
|
||||
SELECT user_key, app_token FROM pushover_settings
|
||||
SELECT user_id, user_key, app_token FROM pushover_settings
|
||||
WHERE user_key IS NOT NULL AND app_token IS NOT NULL
|
||||
`).all();
|
||||
const message = formatOfferMessage(offers);
|
||||
const title = '🍺 KöPi Angebote';
|
||||
for (const r of recipients) {
|
||||
try {
|
||||
await fetch('https://api.pushover.net/1/messages.json', {
|
||||
@@ -446,12 +448,16 @@ async function sendKoepiPushover(offers, onlyUserId = null) {
|
||||
body: new URLSearchParams({
|
||||
token: r.app_token,
|
||||
user: r.user_key,
|
||||
title: '🍺 KöPi Angebote',
|
||||
title,
|
||||
message,
|
||||
priority: 0,
|
||||
}),
|
||||
});
|
||||
} catch (e) { console.error('KöPi Pushover Fehler:', e.message); }
|
||||
logPush({ userId: r.user_id, title, message, priority: 0, source: 'koepi' });
|
||||
} catch (e) {
|
||||
console.error('KöPi Pushover Fehler:', e.message);
|
||||
logPush({ userId: r.user_id, title, message, priority: 0, source: 'koepi', success: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const db = require('../../db');
|
||||
const { logPush } = require('../../pushLog');
|
||||
const { authenticate, requireAdmin } = require('../../middleware/auth');
|
||||
|
||||
const TMDB_BASE = 'https://api.themoviedb.org/3';
|
||||
@@ -898,17 +899,20 @@ function ackFavorite(id) {
|
||||
try {
|
||||
const poCfg = db.prepare('SELECT user_key, app_token FROM pushover_settings WHERE user_id=?').get(fav.user_id);
|
||||
if (poCfg?.app_token && poCfg?.user_key) {
|
||||
const title = '✅ Favorit bestätigt';
|
||||
const message = `"${fav.title}" wurde von einem Admin bestätigt.`;
|
||||
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: '✅ Favorit bestätigt',
|
||||
message: `"${fav.title}" wurde von einem Admin bestätigt.`,
|
||||
title,
|
||||
message,
|
||||
priority: 0,
|
||||
}),
|
||||
}).catch(() => {});
|
||||
logPush({ userId: fav.user_id, title, message, priority: 0, source: 'media-favorit' });
|
||||
}
|
||||
} catch {}
|
||||
return fav;
|
||||
@@ -923,6 +927,7 @@ function ackAllFavorites() {
|
||||
const poCfg = db.prepare('SELECT user_key, app_token FROM pushover_settings WHERE user_id=?').get(userId);
|
||||
if (!poCfg?.app_token || !poCfg?.user_key) continue;
|
||||
const userFavs = toAck.filter(f => f.user_id === userId);
|
||||
const title = '✅ Favoriten bestätigt';
|
||||
const message = userFavs.length === 1
|
||||
? `"${userFavs[0].title}" wurde von einem Admin bestätigt.`
|
||||
: `${userFavs.length} deiner Favoriten wurden von einem Admin bestätigt.`;
|
||||
@@ -932,11 +937,12 @@ function ackAllFavorites() {
|
||||
body: JSON.stringify({
|
||||
token: poCfg.app_token,
|
||||
user: poCfg.user_key,
|
||||
title: '✅ Favoriten bestätigt',
|
||||
title,
|
||||
message,
|
||||
priority: 0,
|
||||
}),
|
||||
}).catch(() => {});
|
||||
logPush({ userId, title, message, priority: 0, source: 'media-favorit' });
|
||||
}
|
||||
} catch {}
|
||||
return toAck.length;
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
const express = require('express');
|
||||
const db = require('../../db');
|
||||
const { logPush } = require('../../pushLog');
|
||||
const { authenticate } = require('../../middleware/auth');
|
||||
const router = express.Router();
|
||||
|
||||
// ── Pushover Helper ───────────────────────────────────────────────────────────
|
||||
async function sendPushover(userKey, appToken, title, message, opts = {}) {
|
||||
async function sendPushover(userKey, appToken, title, message, opts = {}, userId = null) {
|
||||
let priority = 0;
|
||||
try {
|
||||
const params = { token: appToken, user: userKey, title, message };
|
||||
if (opts.retry && opts.expire) {
|
||||
params.priority = 2;
|
||||
priority = 2;
|
||||
params.retry = opts.retry;
|
||||
params.expire = opts.expire;
|
||||
}
|
||||
@@ -17,7 +20,10 @@ async function sendPushover(userKey, appToken, title, message, opts = {}) {
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams(params),
|
||||
});
|
||||
} catch {}
|
||||
logPush({ userId, title, message, priority, source: 'nachrichten' });
|
||||
} catch {
|
||||
logPush({ userId, title, message, priority, source: 'nachrichten', success: false });
|
||||
}
|
||||
}
|
||||
|
||||
// ── Public Keys ───────────────────────────────────────────────────────────────
|
||||
@@ -137,7 +143,7 @@ router.post('/messages/:userId', authenticate, async (req, res) => {
|
||||
`).get(recipId);
|
||||
if (!presence) {
|
||||
await sendPushover(pushover.user_key, pushover.app_token, 'DickenDock', `Neue Nachricht von ${senderName}`,
|
||||
{ retry: pushover.retry, expire: pushover.expire });
|
||||
{ retry: pushover.retry, expire: pushover.expire }, recipId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,7 +209,7 @@ router.post('/pushover/test', authenticate, async (req, res) => {
|
||||
? `🚨 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 } : {});
|
||||
useEmergency ? { retry: row.retry, expire: row.expire } : {}, req.user.id);
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const express = require('express');
|
||||
const db = require('../../db');
|
||||
const { logPush } = require('../../pushLog');
|
||||
const { authenticate } = require('../../middleware/auth');
|
||||
const router = express.Router();
|
||||
|
||||
@@ -103,6 +104,7 @@ function sendPush(userId, title, message) {
|
||||
method:'POST', headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify({ token:cfg.app_token, user:cfg.user_key, title, message, priority:0 }),
|
||||
}).catch(() => {});
|
||||
logPush({ userId, title, message, priority: 0, source: 'schocken' });
|
||||
}
|
||||
|
||||
const uid = req => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const express = require('express');
|
||||
const db = require('../../db');
|
||||
const { logPush } = require('../../pushLog');
|
||||
const { authenticate, requireAdmin } = require('../../middleware/auth');
|
||||
const router = express.Router();
|
||||
|
||||
@@ -174,17 +175,19 @@ router.post('/:id/save', authenticate, async (req, res) => {
|
||||
for (const userId of notify) {
|
||||
const poCfg = db.prepare('SELECT user_key, app_token FROM pushover_settings WHERE user_id=?').get(userId);
|
||||
if (!poCfg?.app_token || !poCfg?.user_key) continue;
|
||||
const title = '🖊 Whiteboard aktualisiert';
|
||||
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 aktualisiert',
|
||||
title,
|
||||
message,
|
||||
priority: 0,
|
||||
}),
|
||||
}).catch(() => {});
|
||||
logPush({ userId, title, message, priority: 0, source: 'whiteboard-save' });
|
||||
}
|
||||
} catch {}
|
||||
});
|
||||
@@ -227,17 +230,20 @@ router.put('/:id/permissions', authenticate, async (req, res) => {
|
||||
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';
|
||||
const title = '🖊 Whiteboard geteilt';
|
||||
const message = `${owner.username} hat das Whiteboard "${wb.title}" mit dir geteilt (${roleLabel}).`;
|
||||
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}).`,
|
||||
title,
|
||||
message,
|
||||
priority: 0,
|
||||
}),
|
||||
}).catch(() => {});
|
||||
logPush({ userId: p.user_id, title, message, priority: 0, source: 'whiteboard-share' });
|
||||
} catch {}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user