refactor(logging): replace console.* with structured logger across server

Add server/logger.js - zero-dependency, level-based logger that outputs
JSON in production and human-readable format in development. Controlled
via LOG_LEVEL env var (debug/info/warn/error, default: info).

Replaces all 100 console.log/warn/error calls in 14 server files.
This commit is contained in:
Ulas
2026-04-03 22:05:22 +02:00
parent 5b1e6915ac
commit 3b90074723
16 changed files with 185 additions and 100 deletions
+12 -9
View File
@@ -13,6 +13,9 @@ const rateLimit = require('express-rate-limit');
const db = require('./db');
const { generateToken, csrfMiddleware } = require('./middleware/csrf');
const { createLogger } = require('./logger');
const log = createLogger('Auth');
const router = express.Router();
// --------------------------------------------------------
@@ -96,7 +99,7 @@ if (!process.env.SESSION_SECRET) {
}
const { randomBytes } = require('node:crypto');
process.env.SESSION_SECRET = randomBytes(32).toString('hex');
console.warn('[Auth] SESSION_SECRET nicht gesetzt - zufaelliges Einmal-Secret generiert (Sessions ueberleben keinen Neustart).');
log.warn('SESSION_SECRET nicht gesetzt - zufaelliges Einmal-Secret generiert (Sessions ueberleben keinen Neustart).');
}
const sessionMiddleware = session({
@@ -187,7 +190,7 @@ router.post('/login', loginLimiter, async (req, res) => {
req.session.regenerate((err) => {
if (err) {
console.error('[Auth] Session-Regenerierung fehlgeschlagen:', err);
log.error('Session-Regenerierung fehlgeschlagen:', err);
return res.status(500).json({ error: 'Interner Serverfehler.', code: 500 });
}
@@ -214,7 +217,7 @@ router.post('/login', loginLimiter, async (req, res) => {
});
});
} catch (err) {
console.error('[Auth] Login-Fehler:', err);
log.error('Login-Fehler:', err);
res.status(500).json({ error: 'Interner Serverfehler.', code: 500 });
}
});
@@ -226,7 +229,7 @@ router.post('/login', loginLimiter, async (req, res) => {
router.post('/logout', requireAuth, csrfMiddleware, (req, res) => {
req.session.destroy((err) => {
if (err) {
console.error('[Auth] Logout-Fehler:', err);
log.error('Logout-Fehler:', err);
return res.status(500).json({ error: 'Logout fehlgeschlagen.', code: 500 });
}
res.clearCookie('oikos.sid');
@@ -251,7 +254,7 @@ router.get('/me', requireAuth, (req, res) => {
res.json({ user });
} catch (err) {
console.error('[Auth] /me Fehler:', err);
log.error('/me Fehler:', err);
res.status(500).json({ error: 'Interner Serverfehler.', code: 500 });
}
});
@@ -268,7 +271,7 @@ router.get('/users', requireAuth, requireAdmin, (req, res) => {
.all();
res.json({ data: users });
} catch (err) {
console.error('[Auth] Users-Fehler:', err);
log.error('Users-Fehler:', err);
res.status(500).json({ error: 'Interner Serverfehler.', code: 500 });
}
});
@@ -319,7 +322,7 @@ router.post('/users', requireAuth, requireAdmin, csrfMiddleware, async (req, res
if (err.message && err.message.includes('UNIQUE constraint')) {
return res.status(409).json({ error: 'Benutzername bereits vergeben.', code: 409 });
}
console.error('[Auth] User-Erstellen-Fehler:', err);
log.error('User-Erstellen-Fehler:', err);
res.status(500).json({ error: 'Interner Serverfehler.', code: 500 });
}
});
@@ -365,7 +368,7 @@ router.patch('/me/password', requireAuth, csrfMiddleware, async (req, res) => {
res.json({ ok: true });
} catch (err) {
console.error('[Auth] Passwort-Ändern-Fehler:', err);
log.error('Passwort-Aendern-Fehler:', err);
res.status(500).json({ error: 'Interner Serverfehler.', code: 500 });
}
});
@@ -402,7 +405,7 @@ router.delete('/users/:id', requireAuth, requireAdmin, csrfMiddleware, (req, res
res.json({ ok: true });
} catch (err) {
console.error('[Auth] User-Löschen-Fehler:', err);
log.error('User-Loeschen-Fehler:', err);
res.status(500).json({ error: 'Interner Serverfehler.', code: 500 });
}
});
+5 -2
View File
@@ -13,6 +13,9 @@
const Database = require('better-sqlite3');
const path = require('path');
const { createLogger } = require('./logger');
const log = createLogger('DB');
const DB_PATH = process.env.DB_PATH || path.join(__dirname, '..', 'oikos.db');
const DB_KEY = process.env.DB_ENCRYPTION_KEY;
@@ -50,7 +53,7 @@ function init() {
migrate();
console.log(`[DB] Verbunden: ${DB_PATH} | Schema v${currentVersion()}`);
log.info(`Verbunden: ${DB_PATH} | Schema v${currentVersion()}`);
return db;
}
@@ -321,7 +324,7 @@ function migrate() {
db.exec(migration.up);
db.prepare('INSERT INTO schema_migrations (version, description) VALUES (?, ?)')
.run(migration.version, migration.description);
console.log(`[DB] Migration ${migration.version} angewendet: ${migration.description}`);
log.info(`Migration ${migration.version} angewendet: ${migration.description}`);
});
for (const migration of pending) {
+11 -6
View File
@@ -11,6 +11,11 @@ const express = require('express');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const path = require('path');
const { createLogger } = require('./logger');
const log = createLogger('Server');
const logSync = createLogger('Sync');
const logOikos = createLogger('Oikos');
// --------------------------------------------------------
// Datenbank initialisieren (muss vor require('./auth') stehen,
@@ -189,7 +194,7 @@ app.get('*', spaLimiter, (req, res) => {
// Globaler Error-Handler
// --------------------------------------------------------
app.use((err, req, res, _next) => {
console.error('[Server] Unbehandelter Fehler:', err);
log.error('Unbehandelter Fehler:', err);
res.status(500).json({ error: 'Interner Serverfehler.', code: 500 });
});
@@ -202,12 +207,12 @@ const SYNC_INTERVAL_MS = (parseInt(process.env.SYNC_INTERVAL_MINUTES, 10) || 15)
async function runSync() {
const { connected: googleConnected } = googleCalendar.getStatus();
if (googleConnected) {
googleCalendar.sync().catch((e) => console.error('[Sync] Google Fehler:', e.message));
googleCalendar.sync().catch((e) => logSync.error('Google Fehler:', e.message));
}
const { configured: appleConfigured } = appleCalendar.getStatus();
if (appleConfigured) {
appleCalendar.sync().catch((e) => console.error('[Sync] Apple Fehler:', e.message));
appleCalendar.sync().catch((e) => logSync.error('Apple Fehler:', e.message));
}
}
@@ -215,14 +220,14 @@ async function runSync() {
// Server starten
// --------------------------------------------------------
app.listen(PORT, () => {
console.log(`[Oikos] Server läuft auf Port ${PORT}`);
console.log(`[Oikos] Umgebung: ${process.env.NODE_ENV || 'development'}`);
logOikos.info(`Server laeuft auf Port ${PORT}`);
logOikos.info(`Umgebung: ${process.env.NODE_ENV || 'development'}`);
// Erster Sync nach 10 Sekunden (warten bis DB vollständig initialisiert)
setTimeout(() => {
runSync();
setInterval(runSync, SYNC_INTERVAL_MS);
console.log(`[Sync] Auto-Sync alle ${SYNC_INTERVAL_MS / 60_000} Minuten aktiv.`);
logSync.info(`Auto-Sync alle ${SYNC_INTERVAL_MS / 60_000} Minuten aktiv.`);
}, 10_000);
});
+40
View File
@@ -0,0 +1,40 @@
/**
* Modul: Logger
* Zweck: Levelbasiertes strukturiertes Logging ohne externe Dependencies.
* Ausgabe als JSON in Production, lesbar in Development.
* Steuerung: LOG_LEVEL env var (debug, info, warn, error). Default: info.
*/
'use strict';
const LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
const currentLevel = LEVELS[process.env.LOG_LEVEL] ?? LEVELS.info;
const isProduction = process.env.NODE_ENV === 'production';
function emit(level, mod, msg, extra) {
if (LEVELS[level] < currentLevel) return;
if (isProduction) {
const entry = { ts: new Date().toISOString(), level, mod, msg };
if (extra !== undefined) entry.extra = extra;
process.stdout.write(JSON.stringify(entry) + '\n');
} else {
const prefix = `[${mod}]`;
if (extra !== undefined) {
console[level === 'debug' ? 'log' : level](prefix, msg, extra);
} else {
console[level === 'debug' ? 'log' : level](prefix, msg);
}
}
}
function createLogger(mod) {
return {
debug: (msg, extra) => emit('debug', mod, msg, extra),
info: (msg, extra) => emit('info', mod, msg, extra),
warn: (msg, extra) => emit('warn', mod, msg, extra),
error: (msg, extra) => emit('error', mod, msg, extra),
};
}
module.exports = { createLogger };
+9 -6
View File
@@ -6,6 +6,9 @@
'use strict';
const { createLogger } = require('../logger');
const log = createLogger('Budget');
const express = require('express');
const router = express.Router();
const db = require('../db');
@@ -117,7 +120,7 @@ router.get('/summary', (req, res) => {
},
});
} catch (err) {
console.error('[budget/GET /summary]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -167,7 +170,7 @@ router.get('/export', (req, res) => {
res.setHeader('Content-Disposition', `attachment; filename="budget-${month}.csv"`);
res.send('\uFEFF' + header + rows); // BOM für Excel
} catch (err) {
console.error('[budget/GET /export]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -221,7 +224,7 @@ router.get('/', (req, res) => {
const entries = db.get().prepare(sql).all(...params);
res.json({ data: entries });
} catch (err) {
console.error('[budget/GET /]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -259,7 +262,7 @@ router.post('/', (req, res) => {
res.status(201).json({ data: entry });
} catch (err) {
console.error('[budget/POST /]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -312,7 +315,7 @@ router.put('/:id', (req, res) => {
res.json({ data: updated });
} catch (err) {
console.error('[budget/PUT /:id]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -340,7 +343,7 @@ router.delete('/:id', (req, res) => {
res.status(204).end();
} catch (err) {
console.error('[budget/DELETE /:id]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
+20 -17
View File
@@ -7,6 +7,9 @@
'use strict';
const { createLogger } = require('../logger');
const log = createLogger('Calendar');
const express = require('express');
const router = express.Router();
const db = require('../db');
@@ -152,7 +155,7 @@ router.get('/', (req, res) => {
const events = expandRecurringEvents(rawEvents, from, to);
res.json({ data: events, from, to });
} catch (err) {
console.error('[calendar/GET /]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -190,7 +193,7 @@ router.get('/upcoming', (req, res) => {
res.json({ data: expanded });
} catch (err) {
console.error('[calendar/GET /upcoming]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -210,7 +213,7 @@ router.get('/google/auth', requireAdmin, (req, res) => {
if (!url) return res.status(503).json({ error: 'Google nicht konfiguriert.', code: 503 });
res.redirect(url);
} catch (err) {
console.error('[calendar/google/auth]', err);
log.error('', err);
res.status(503).json({ error: err.message, code: 503 });
}
});
@@ -228,7 +231,7 @@ router.get('/google/callback', async (req, res) => {
// OAuth CSRF-Schutz: state-Parameter validieren
if (!state || !req.session.googleOAuthState || state !== req.session.googleOAuthState) {
console.error('[calendar/google/callback] OAuth state mismatch');
log.error('OAuth state mismatch');
return res.redirect('/settings?sync_error=google');
}
delete req.session.googleOAuthState;
@@ -236,11 +239,11 @@ router.get('/google/callback', async (req, res) => {
await googleCalendar.handleCallback(code);
// Initialen Sync im Hintergrund starten (kein await - Redirect soll sofort erfolgen)
googleCalendar.sync().catch((e) => console.error('[Google] Initialer Sync fehlgeschlagen:', e.message));
googleCalendar.sync().catch((e) => log.error('Initialer Sync fehlgeschlagen:', e.message));
res.redirect('/settings?sync_ok=google');
} catch (err) {
console.error('[calendar/google/callback]', err);
log.error('', err);
res.redirect('/settings?sync_error=google');
}
});
@@ -256,7 +259,7 @@ router.post('/google/sync', requireAdmin, async (req, res) => {
const { lastSync } = googleCalendar.getStatus();
res.json({ ok: true, lastSync });
} catch (err) {
console.error('[calendar/google/sync]', err);
log.error('', err);
res.status(500).json({ error: err.message, code: 500 });
}
});
@@ -269,7 +272,7 @@ router.get('/google/status', (req, res) => {
try {
res.json(googleCalendar.getStatus());
} catch (err) {
console.error('[calendar/google/status]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -284,7 +287,7 @@ router.delete('/google/disconnect', requireAdmin, (req, res) => {
googleCalendar.disconnect();
res.json({ ok: true });
} catch (err) {
console.error('[calendar/google/disconnect]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -301,7 +304,7 @@ router.get('/apple/status', (req, res) => {
try {
res.json(appleCalendar.getStatus());
} catch (err) {
console.error('[calendar/apple/status]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -317,7 +320,7 @@ router.post('/apple/sync', requireAdmin, async (req, res) => {
const { lastSync } = appleCalendar.getStatus();
res.json({ ok: true, lastSync });
} catch (err) {
console.error('[calendar/apple/sync]', err);
log.error('', err);
res.status(500).json({ error: err.message, code: 500 });
}
});
@@ -348,7 +351,7 @@ router.post('/apple/connect', requireAdmin, async (req, res) => {
} catch (err) {
// Bei Fehler: gespeicherte Credentials wieder löschen
appleCalendar.clearCredentials();
console.error('[calendar/apple/connect]', err);
log.error('', err);
res.status(400).json({ error: err.message.replace('[Apple] ', ''), code: 400 });
}
});
@@ -363,7 +366,7 @@ router.delete('/apple/disconnect', requireAdmin, (req, res) => {
appleCalendar.clearCredentials();
res.status(204).end();
} catch (err) {
console.error('[calendar/apple/disconnect]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -390,7 +393,7 @@ router.get('/:id', (req, res) => {
if (!event) return res.status(404).json({ error: 'Termin nicht gefunden', code: 404 });
res.json({ data: event });
} catch (err) {
console.error('[calendar/GET /:id]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -448,7 +451,7 @@ router.post('/', (req, res) => {
res.status(201).json({ data: event });
} catch (err) {
console.error('[calendar/POST /]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -519,7 +522,7 @@ router.put('/:id', (req, res) => {
res.json({ data: updated });
} catch (err) {
console.error('[calendar/PUT /:id]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -537,7 +540,7 @@ router.delete('/:id', (req, res) => {
return res.status(404).json({ error: 'Termin nicht gefunden', code: 404 });
res.status(204).end();
} catch (err) {
console.error('[calendar/DELETE /:id]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
+9 -6
View File
@@ -6,6 +6,9 @@
'use strict';
const { createLogger } = require('../logger');
const log = createLogger('Contacts');
const express = require('express');
const router = express.Router();
const db = require('../db');
@@ -43,7 +46,7 @@ router.get('/', (req, res) => {
const contacts = db.get().prepare(sql).all(...params);
res.json({ data: contacts });
} catch (err) {
console.error('[contacts/GET /]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -74,7 +77,7 @@ router.post('/', (req, res) => {
const contact = db.get().prepare('SELECT * FROM contacts WHERE id = ?').get(result.lastInsertRowid);
res.status(201).json({ data: contact });
} catch (err) {
console.error('[contacts/POST /]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -123,7 +126,7 @@ router.put('/:id', (req, res) => {
const updated = db.get().prepare('SELECT * FROM contacts WHERE id = ?').get(id);
res.json({ data: updated });
} catch (err) {
console.error('[contacts/PUT /:id]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -141,7 +144,7 @@ router.delete('/:id', (req, res) => {
return res.status(404).json({ error: 'Kontakt nicht gefunden', code: 404 });
res.status(204).end();
} catch (err) {
console.error('[contacts/DELETE /:id]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -155,7 +158,7 @@ router.get('/meta', (_req, res) => {
try {
res.json({ data: { categories: VALID_CATEGORIES } });
} catch (err) {
console.error('[contacts/GET /meta]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -193,7 +196,7 @@ router.get('/:id/vcard', (req, res) => {
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
res.send(vcf);
} catch (err) {
console.error('[contacts/GET /:id/vcard]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
+8 -5
View File
@@ -6,6 +6,9 @@
'use strict';
const { createLogger } = require('../logger');
const log = createLogger('Dashboard');
const express = require('express');
const router = express.Router();
const db = require('../db');
@@ -48,7 +51,7 @@ router.get('/', (req, res) => {
LIMIT 5
`).all(now.toISOString());
} catch (err) {
console.error('[Dashboard] upcomingEvents-Fehler:', err.message);
log.error('upcomingEvents-Fehler:', err.message);
result.upcomingEvents = [];
}
@@ -74,7 +77,7 @@ router.get('/', (req, res) => {
LIMIT 5
`).all();
} catch (err) {
console.error('[Dashboard] urgentTasks-Fehler:', err.message);
log.error('urgentTasks-Fehler:', err.message);
result.urgentTasks = [];
}
@@ -92,7 +95,7 @@ router.get('/', (req, res) => {
END
`).all(todayStr);
} catch (err) {
console.error('[Dashboard] todayMeals-Fehler:', err.message);
log.error('todayMeals-Fehler:', err.message);
result.todayMeals = [];
}
@@ -106,7 +109,7 @@ router.get('/', (req, res) => {
LIMIT 3
`).all();
} catch (err) {
console.error('[Dashboard] pinnedNotes-Fehler:', err.message);
log.error('pinnedNotes-Fehler:', err.message);
result.pinnedNotes = [];
}
@@ -121,7 +124,7 @@ router.get('/', (req, res) => {
res.json(result);
} catch (err) {
console.error('[Dashboard] Kritischer Fehler:', err.message);
log.error('Kritischer Fehler:', err.message);
res.status(500).json({ error: 'Dashboard konnte nicht geladen werden.', code: 500 });
}
});
+13 -10
View File
@@ -6,6 +6,9 @@
'use strict';
const { createLogger } = require('../logger');
const log = createLogger('Meals');
const express = require('express');
const router = express.Router();
const db = require('../db');
@@ -64,7 +67,7 @@ router.get('/suggestions', (req, res) => {
res.json({ data: rows });
} catch (err) {
console.error('[meals/suggestions]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -131,7 +134,7 @@ router.get('/', (req, res) => {
res.json({ data: result, weekStart: from, weekEnd: to });
} catch (err) {
console.error('[meals/GET /]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -190,7 +193,7 @@ router.post('/', (req, res) => {
res.status(201).json({ data: { ...meal, ingredients: ings } });
} catch (err) {
console.error('[meals/POST /]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -242,7 +245,7 @@ router.put('/:id', (req, res) => {
res.json({ data: { ...updated, ingredients: ings } });
} catch (err) {
console.error('[meals/PUT /:id]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -260,7 +263,7 @@ router.delete('/:id', (req, res) => {
return res.status(404).json({ error: 'Mahlzeit nicht gefunden', code: 404 });
res.status(204).end();
} catch (err) {
console.error('[meals/DELETE /:id]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -295,7 +298,7 @@ router.post('/:id/ingredients', (req, res) => {
res.status(201).json({ data: ing });
} catch (err) {
console.error('[meals/POST /:id/ingredients]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -333,7 +336,7 @@ router.patch('/ingredients/:ingId', (req, res) => {
res.json({ data: updated });
} catch (err) {
console.error('[meals/PATCH /ingredients/:ingId]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -351,7 +354,7 @@ router.delete('/ingredients/:ingId', (req, res) => {
return res.status(404).json({ error: 'Zutat nicht gefunden', code: 404 });
res.status(204).end();
} catch (err) {
console.error('[meals/DELETE /ingredients/:ingId]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -407,7 +410,7 @@ router.post('/:id/to-shopping-list', (req, res) => {
res.json({ data: { transferred } });
} catch (err) {
console.error('[meals/POST /:id/to-shopping-list]', err);
log.error('POST /:id/to-shopping-list', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -463,7 +466,7 @@ router.post('/week-to-shopping-list', (req, res) => {
res.json({ data: { transferred } });
} catch (err) {
console.error('[meals/POST /week-to-shopping-list]', err);
log.error('POST /week-to-shopping-list', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
+8 -5
View File
@@ -6,6 +6,9 @@
'use strict';
const { createLogger } = require('../logger');
const log = createLogger('Notes');
const express = require('express');
const router = express.Router();
const db = require('../db');
@@ -26,7 +29,7 @@ router.get('/', (req, res) => {
`).all();
res.json({ data: notes });
} catch (err) {
console.error('[notes/GET /]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -59,7 +62,7 @@ router.post('/', (req, res) => {
res.status(201).json({ data: note });
} catch (err) {
console.error('[notes/POST /]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -106,7 +109,7 @@ router.put('/:id', (req, res) => {
res.json({ data: updated });
} catch (err) {
console.error('[notes/PUT /:id]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -126,7 +129,7 @@ router.patch('/:id/pin', (req, res) => {
db.get().prepare('UPDATE notes SET pinned = ? WHERE id = ?').run(newPinned, id);
res.json({ data: { id, pinned: newPinned } });
} catch (err) {
console.error('[notes/PATCH /:id/pin]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
@@ -144,7 +147,7 @@ router.delete('/:id', (req, res) => {
return res.status(404).json({ error: 'Notiz nicht gefunden', code: 404 });
res.status(204).end();
} catch (err) {
console.error('[notes/DELETE /:id]', err);
log.error('', err);
res.status(500).json({ error: 'Interner Fehler', code: 500 });
}
});
+13 -10
View File
@@ -9,6 +9,9 @@
'use strict';
const { createLogger } = require('../logger');
const log = createLogger('Shopping');
const express = require('express');
const router = express.Router();
const db = require('../db');
@@ -42,7 +45,7 @@ router.get('/suggestions', (req, res) => {
res.json({ data: rows.map((r) => r.name) });
} catch (err) {
console.error('[Shopping] suggestions Fehler:', err);
log.error('suggestions Fehler:', err);
res.status(500).json({ error: 'Interner Serverfehler.', code: 500 });
}
});
@@ -82,7 +85,7 @@ router.patch('/items/:itemId', (req, res) => {
.get(req.params.itemId);
res.json({ data: updated });
} catch (err) {
console.error('[Shopping] PATCH items/:id Fehler:', err);
log.error('PATCH items/:id Fehler:', err);
res.status(500).json({ error: 'Interner Serverfehler.', code: 500 });
}
});
@@ -101,7 +104,7 @@ router.delete('/items/:itemId', (req, res) => {
return res.status(404).json({ error: 'Artikel nicht gefunden.', code: 404 });
res.json({ ok: true });
} catch (err) {
console.error('[Shopping] DELETE items/:id Fehler:', err);
log.error('DELETE items/:id Fehler:', err);
res.status(500).json({ error: 'Interner Serverfehler.', code: 500 });
}
});
@@ -125,7 +128,7 @@ router.get('/', (req, res) => {
`).all();
res.json({ data: lists });
} catch (err) {
console.error('[Shopping] GET / Fehler:', err);
log.error('GET / Fehler:', err);
res.status(500).json({ error: 'Interner Serverfehler.', code: 500 });
}
});
@@ -150,7 +153,7 @@ router.post('/', (req, res) => {
.get(result.lastInsertRowid);
res.status(201).json({ data: list });
} catch (err) {
console.error('[Shopping] POST / Fehler:', err);
log.error('POST / Fehler:', err);
res.status(500).json({ error: 'Interner Serverfehler.', code: 500 });
}
});
@@ -177,7 +180,7 @@ router.put('/:listId', (req, res) => {
.get(req.params.listId);
res.json({ data: list });
} catch (err) {
console.error('[Shopping] PUT /:listId Fehler:', err);
log.error('PUT /:listId Fehler:', err);
res.status(500).json({ error: 'Interner Serverfehler.', code: 500 });
}
});
@@ -196,7 +199,7 @@ router.delete('/:listId', (req, res) => {
return res.status(404).json({ error: 'Liste nicht gefunden.', code: 404 });
res.json({ ok: true });
} catch (err) {
console.error('[Shopping] DELETE /:listId Fehler:', err);
log.error('DELETE /:listId Fehler:', err);
res.status(500).json({ error: 'Interner Serverfehler.', code: 500 });
}
});
@@ -227,7 +230,7 @@ router.get('/:listId/items', (req, res) => {
res.json({ data: items, list });
} catch (err) {
console.error('[Shopping] GET /:listId/items Fehler:', err);
log.error('GET /:listId/items Fehler:', err);
res.status(500).json({ error: 'Interner Serverfehler.', code: 500 });
}
});
@@ -261,7 +264,7 @@ router.post('/:listId/items', (req, res) => {
.get(result.lastInsertRowid);
res.status(201).json({ data: item });
} catch (err) {
console.error('[Shopping] POST /:listId/items Fehler:', err);
log.error('POST /:listId/items Fehler:', err);
res.status(500).json({ error: 'Interner Serverfehler.', code: 500 });
}
});
@@ -278,7 +281,7 @@ router.delete('/:listId/items/checked', (req, res) => {
`).run(req.params.listId);
res.json({ deleted: result.changes });
} catch (err) {
console.error('[Shopping] DELETE /:listId/items/checked Fehler:', err);
log.error('DELETE /:listId/items/checked Fehler:', err);
res.status(500).json({ error: 'Interner Serverfehler.', code: 500 });
}
});
+10 -7
View File
@@ -6,6 +6,9 @@
'use strict';
const { createLogger } = require('../logger');
const log = createLogger('Tasks');
const express = require('express');
const router = express.Router();
const db = require('../db');
@@ -101,7 +104,7 @@ router.get('/', (req, res) => {
res.json({ data: db.get().prepare(sql).all(...params) });
} catch (err) {
console.error('[Tasks] GET / Fehler:', err);
log.error('GET / Fehler:', err);
res.status(500).json({ error: 'Interner Serverfehler.', code: 500 });
}
});
@@ -125,7 +128,7 @@ router.get('/:id', (req, res) => {
task.subtasks = loadSubtasks(task.id);
res.json({ data: task });
} catch (err) {
console.error('[Tasks] GET /:id Fehler:', err);
log.error('GET /:id Fehler:', err);
res.status(500).json({ error: 'Interner Serverfehler.', code: 500 });
}
});
@@ -183,7 +186,7 @@ router.post('/', (req, res) => {
res.status(201).json({ data: task });
} catch (err) {
console.error('[Tasks] POST / Fehler:', err);
log.error('POST / Fehler:', err);
res.status(500).json({ error: 'Interner Serverfehler.', code: 500 });
}
});
@@ -235,7 +238,7 @@ router.put('/:id', (req, res) => {
res.json({ data: updated });
} catch (err) {
console.error('[Tasks] PUT /:id Fehler:', err);
log.error('PUT /:id Fehler:', err);
res.status(500).json({ error: 'Interner Serverfehler.', code: 500 });
}
});
@@ -279,7 +282,7 @@ router.patch('/:id/status', (req, res) => {
res.json({ data: { id: Number(req.params.id), status } });
} catch (err) {
console.error('[Tasks] PATCH /:id/status Fehler:', err);
log.error('PATCH /:id/status Fehler:', err);
res.status(500).json({ error: 'Interner Serverfehler.', code: 500 });
}
});
@@ -296,7 +299,7 @@ router.delete('/:id', (req, res) => {
return res.status(404).json({ error: 'Aufgabe nicht gefunden.', code: 404 });
res.json({ ok: true });
} catch (err) {
console.error('[Tasks] DELETE /:id Fehler:', err);
log.error('DELETE /:id Fehler:', err);
res.status(500).json({ error: 'Interner Serverfehler.', code: 500 });
}
});
@@ -313,7 +316,7 @@ router.get('/meta/options', (req, res) => {
).all();
res.json({ users, priorities: VALID_PRIORITIES, statuses: VALID_STATUSES, categories: VALID_CATEGORIES });
} catch (err) {
console.error('[Tasks] GET /meta/options Fehler:', err);
log.error('GET /meta/options Fehler:', err);
res.status(500).json({ error: 'Interner Serverfehler.', code: 500 });
}
});
+6 -3
View File
@@ -6,6 +6,9 @@
'use strict';
const { createLogger } = require('../logger');
const log = createLogger('Weather');
const express = require('express');
const router = express.Router();
@@ -43,7 +46,7 @@ router.get('/', async (req, res) => {
const currentUrl = `https://api.openweathermap.org/data/2.5/weather?q=${encodeURIComponent(city)}&appid=${apiKey}&units=${units}&lang=${lang}`;
const currentRes = await fetch(currentUrl, { signal: AbortSignal.timeout(8000) });
if (!currentRes.ok) {
console.warn(`[Weather] API Fehler: ${currentRes.status}`);
log.warn(`API Fehler: ${currentRes.status}`);
return res.json({ data: null });
}
const currentJson = await currentRes.json();
@@ -87,7 +90,7 @@ router.get('/', async (req, res) => {
cache = { data, ts: Date.now() };
res.json({ data });
} catch (err) {
console.warn('[Weather] Fehler:', err.message);
log.warn('Fehler:', err.message);
res.json({ data: null }); // Fallback: Widget ausblenden, kein Error-Screen
}
});
@@ -116,7 +119,7 @@ router.get('/icon/:code', async (req, res) => {
res.setHeader('Cache-Control', 'public, max-age=86400'); // 24 Stunden
upstream.body.pipe(res);
} catch (err) {
console.warn('[Weather] Icon-Proxy Fehler:', err.message);
log.warn('Icon-Proxy Fehler:', err.message);
res.status(502).json({ error: 'Icon-Proxy fehlgeschlagen.', code: 502 });
}
});
+11 -8
View File
@@ -14,6 +14,9 @@
'use strict';
const { createLogger } = require('../logger');
const log = createLogger('Apple');
const db = require('../db');
const APPLE_COLOR = '#FC3C44';
@@ -55,7 +58,7 @@ function getCredentials() {
function saveCredentials(url, username, password) {
// Warnung wenn DB-Verschluesselung nicht aktiv - Credentials liegen dann im Klartext
if (!process.env.DB_ENCRYPTION_KEY) {
console.warn('[Apple] WARNUNG: DB_ENCRYPTION_KEY nicht gesetzt - CalDAV-Credentials werden unverschluesselt gespeichert.');
log.warn('WARNUNG: DB_ENCRYPTION_KEY nicht gesetzt - CalDAV-Credentials werden unverschluesselt gespeichert.');
}
cfgSet('apple_caldav_url', url);
cfgSet('apple_username', username);
@@ -64,7 +67,7 @@ function saveCredentials(url, username, password) {
function clearCredentials() {
['apple_caldav_url', 'apple_username', 'apple_app_password', 'apple_last_sync'].forEach(cfgDel);
console.log('[Apple] Verbindung getrennt.');
log.info('Verbindung getrennt.');
}
// --------------------------------------------------------
@@ -304,14 +307,14 @@ async function sync() {
const calendars = await client.fetchCalendars();
if (!calendars.length) {
console.warn('[Apple] Keine Kalender gefunden.');
log.warn('Keine Kalender gefunden.');
return;
}
// created_by: ersten existierenden User verwenden (nicht hardcoded ID 1)
const owner = db.get().prepare('SELECT id FROM users ORDER BY id ASC LIMIT 1').get();
if (!owner) {
console.warn('[Apple] Kein User in der Datenbank - Sync übersprungen.');
log.warn('Kein User in der Datenbank - Sync übersprungen.');
return;
}
const createdBy = owner.id;
@@ -326,7 +329,7 @@ async function sync() {
try {
calObjects = await client.fetchCalendarObjects({ calendar: cal });
} catch (err) {
console.warn(`[Apple] Kalender "${cal.displayName || '(unbenannt)'}" nicht abrufbar: ${err.message}`);
log.warn(`Kalender "${cal.displayName || '(unbenannt)'}" nicht abrufbar: ${err.message}`);
continue;
}
@@ -365,7 +368,7 @@ async function sync() {
);
}
} catch (err) {
console.error(`[Apple] Upsert-Fehler für UID ${ev.uid}:`, err.message);
log.error(`Upsert-Fehler für UID ${ev.uid}:`, err.message);
}
}
}
@@ -396,12 +399,12 @@ async function sync() {
UPDATE calendar_events SET external_calendar_id = ?, external_source = 'apple' WHERE id = ?
`).run(uid, event.id);
} catch (err) {
console.error(`[Apple] Outbound-Fehler für Event ${event.id}:`, err.message);
log.error(`Outbound-Fehler für Event ${event.id}:`, err.message);
}
}
cfgSet('apple_last_sync', new Date().toISOString());
console.log(`[Apple] Sync abgeschlossen - ${totalObjects} Objekte aus ${syncCalendars.length} Kalendern inbound, ${localEvents.length} lokal → iCloud.`);
log.info(`Sync abgeschlossen - ${totalObjects} Objekte aus ${syncCalendars.length} Kalendern inbound, ${localEvents.length} lokal → iCloud.`);
}
module.exports = { sync, getStatus, saveCredentials, clearCredentials, testConnection };
+9 -6
View File
@@ -13,6 +13,9 @@
'use strict';
const { createLogger } = require('../logger');
const log = createLogger('Google');
const { google } = require('googleapis');
const crypto = require('crypto');
const db = require('../db');
@@ -127,7 +130,7 @@ async function handleCallback(code) {
cfgSet('google_refresh_token', tokens.refresh_token);
if (tokens.expiry_date) cfgSet('google_token_expiry', String(tokens.expiry_date));
console.log('[Google] OAuth erfolgreich - Tokens gespeichert.');
log.info('OAuth erfolgreich - Tokens gespeichert.');
}
/**
@@ -147,7 +150,7 @@ function getStatus() {
function disconnect() {
['google_access_token', 'google_refresh_token', 'google_token_expiry',
'google_sync_token', 'google_last_sync'].forEach(cfgDel);
console.log('[Google] Verbindung getrennt.');
log.info('Verbindung getrennt.');
}
/**
@@ -189,7 +192,7 @@ async function sync() {
} catch (err) {
if (err.code === 410) {
// syncToken abgelaufen → vollständiger Resync
console.warn('[Google] syncToken ungültig - vollständiger Resync.');
log.warn('syncToken ungültig - vollständiger Resync.');
cfgDel('google_sync_token');
syncToken = null;
continue;
@@ -225,12 +228,12 @@ async function sync() {
UPDATE calendar_events SET external_calendar_id = ?, external_source = 'google' WHERE id = ?
`).run(created.data.id, event.id);
} catch (err) {
console.error(`[Google] Outbound-Fehler für Event ${event.id}:`, err.message);
log.error(`Outbound-Fehler für Event ${event.id}:`, err.message);
}
}
cfgSet('google_last_sync', new Date().toISOString());
console.log(`[Google] Sync abgeschlossen - ${localEvents.length} lokal → Google, Inbound via syncToken.`);
log.info(`Sync abgeschlossen - ${localEvents.length} lokal → Google, Inbound via syncToken.`);
}
// --------------------------------------------------------
@@ -299,7 +302,7 @@ function upsertGoogleEvents(items) {
try {
insertOrUpdate(item);
} catch (err) {
console.error(`[Google] Upsert-Fehler für Event ${item.id}:`, err.message);
log.error(`Upsert-Fehler für Event ${item.id}:`, err.message);
}
}
}