From 3b900747239079d77de1544f13eb8044bf18cd07 Mon Sep 17 00:00:00 2001 From: Ulas Date: Fri, 3 Apr 2026 22:05:22 +0200 Subject: [PATCH] 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. --- .env.example | 1 + server/auth.js | 21 +++++++++------- server/db.js | 7 ++++-- server/index.js | 17 ++++++++----- server/logger.js | 40 ++++++++++++++++++++++++++++++ server/routes/budget.js | 15 ++++++----- server/routes/calendar.js | 37 ++++++++++++++------------- server/routes/contacts.js | 15 ++++++----- server/routes/dashboard.js | 13 ++++++---- server/routes/meals.js | 23 +++++++++-------- server/routes/notes.js | 13 ++++++---- server/routes/shopping.js | 23 +++++++++-------- server/routes/tasks.js | 17 +++++++------ server/routes/weather.js | 9 ++++--- server/services/apple-calendar.js | 19 ++++++++------ server/services/google-calendar.js | 15 ++++++----- 16 files changed, 185 insertions(+), 100 deletions(-) create mode 100644 server/logger.js diff --git a/.env.example b/.env.example index 6842456..3130f57 100644 --- a/.env.example +++ b/.env.example @@ -4,6 +4,7 @@ # Server PORT=3000 NODE_ENV=production +# LOG_LEVEL=info # debug, info, warn, error (default: info) # Session SESSION_SECRET=REPLACE_WITH_A_LONG_RANDOM_STRING diff --git a/server/auth.js b/server/auth.js index 40a3435..b82da3c 100644 --- a/server/auth.js +++ b/server/auth.js @@ -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 }); } }); diff --git a/server/db.js b/server/db.js index d803e73..7e4cf2a 100644 --- a/server/db.js +++ b/server/db.js @@ -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) { diff --git a/server/index.js b/server/index.js index 44797b4..7a23030 100644 --- a/server/index.js +++ b/server/index.js @@ -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); }); diff --git a/server/logger.js b/server/logger.js new file mode 100644 index 0000000..5973d22 --- /dev/null +++ b/server/logger.js @@ -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 }; diff --git a/server/routes/budget.js b/server/routes/budget.js index e568d0d..e71d495 100644 --- a/server/routes/budget.js +++ b/server/routes/budget.js @@ -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 }); } }); diff --git a/server/routes/calendar.js b/server/routes/calendar.js index 835c3b2..a875d05 100644 --- a/server/routes/calendar.js +++ b/server/routes/calendar.js @@ -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 }); } }); diff --git a/server/routes/contacts.js b/server/routes/contacts.js index ab330a8..3e8547e 100644 --- a/server/routes/contacts.js +++ b/server/routes/contacts.js @@ -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 }); } }); diff --git a/server/routes/dashboard.js b/server/routes/dashboard.js index 8677058..834c38b 100644 --- a/server/routes/dashboard.js +++ b/server/routes/dashboard.js @@ -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 }); } }); diff --git a/server/routes/meals.js b/server/routes/meals.js index f1e9ef8..6d88ed4 100644 --- a/server/routes/meals.js +++ b/server/routes/meals.js @@ -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 }); } }); diff --git a/server/routes/notes.js b/server/routes/notes.js index 5e95f03..cceafb6 100644 --- a/server/routes/notes.js +++ b/server/routes/notes.js @@ -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 }); } }); diff --git a/server/routes/shopping.js b/server/routes/shopping.js index 3b2c671..aff0ffa 100644 --- a/server/routes/shopping.js +++ b/server/routes/shopping.js @@ -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 }); } }); diff --git a/server/routes/tasks.js b/server/routes/tasks.js index 412f318..1377c80 100644 --- a/server/routes/tasks.js +++ b/server/routes/tasks.js @@ -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 }); } }); diff --git a/server/routes/weather.js b/server/routes/weather.js index 5bdd08e..301412e 100644 --- a/server/routes/weather.js +++ b/server/routes/weather.js @@ -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 }); } }); diff --git a/server/services/apple-calendar.js b/server/services/apple-calendar.js index 042ad36..8bf5ae0 100644 --- a/server/services/apple-calendar.js +++ b/server/services/apple-calendar.js @@ -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 }; diff --git a/server/services/google-calendar.js b/server/services/google-calendar.js index a783580..ab005a7 100644 --- a/server/services/google-calendar.js +++ b/server/services/google-calendar.js @@ -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); } } }