diff --git a/package.json b/package.json index 9958cfb..0126ded 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "harheimertc-website", - "version": "1.4.2", + "version": "1.4.3", "description": "Moderne Webseite für den Harheimer Tischtennis Club", "private": true, "type": "module", diff --git a/server/api/spielplan.get.js b/server/api/spielplan.get.js index 63ed85d..39d685d 100644 --- a/server/api/spielplan.get.js +++ b/server/api/spielplan.get.js @@ -1,8 +1,18 @@ -import { listSpielplanSeasons, readSpielplanData } from '../utils/spielplan-data.js' +import { listSpielplanSeasons, readSpielplanData, validateSeasonSlug } from '../utils/spielplan-data.js' +import { error as loggerError } from '../utils/logger.js' export default defineEventHandler(async (event) => { try { const query = getQuery(event) + if (query.season && !validateSeasonSlug(query.season)) { + loggerError('Ungueltiger Saison-Slug angefragt', { season: query.season }) + return { + success: false, + message: 'Ungueltiger Saison-Slug', + data: [], + headers: [] + } + } const [spielplan, seasons] = await Promise.all([ readSpielplanData({ season: query.season }), listSpielplanSeasons() @@ -28,7 +38,7 @@ export default defineEventHandler(async (event) => { seasons } } catch (error) { - console.error('Fehler beim Laden des Spielplans:', error) + loggerError('Fehler beim Laden des Spielplans:', { error }) return { success: false, message: 'Fehler beim Laden des Spielplans', diff --git a/server/api/spielplan/pdf.get.js b/server/api/spielplan/pdf.get.js index 45f52f5..50974f6 100644 --- a/server/api/spielplan/pdf.get.js +++ b/server/api/spielplan/pdf.get.js @@ -1,11 +1,16 @@ import fs from 'fs/promises' import path from 'path' -import { readSpielplanData } from '../../utils/spielplan-data.js' +import { readSpielplanData, validateSeasonSlug } from '../../utils/spielplan-data.js' +import { info as loggerInfo, error as loggerError } from '../../utils/logger.js' export default defineEventHandler(async (event) => { try { const query = getQuery(event) const team = query.team + if (query.season && !validateSeasonSlug(query.season)) { + loggerError('Ungueltiger Saison-Slug angefragt (PDF)', { season: query.season }) + throw createError({ statusCode: 400, statusMessage: 'Ungueltiger Saison-Slug' }) + } if (!team) { throw createError({ @@ -139,7 +144,7 @@ export default defineEventHandler(async (event) => { const hallenMap = new Map() // Debug: Zeige verfügbare Spalten - console.log('Verfügbare Spalten in gefilterten Daten:', Object.keys(filteredData[0] || {})) + loggerInfo('Verfügbare Spalten in gefilterten Daten', { columns: Object.keys(filteredData[0] || {}) }) filteredData.forEach((row, index) => { // Suche Halle-Spalten mit verschiedenen möglichen Namen @@ -150,10 +155,9 @@ export default defineEventHandler(async (event) => { const heimMannschaft = row.HeimMannschaft || '' // Debug: Zeige Halle-Daten für erste paar Zeilen - if (index < 3) { - // nosemgrep: javascript.lang.security.audit.unsafe-formatstring.unsafe-formatstring - console.log(`Zeile ${index}: HalleName="${halleName}", HalleStrasse="${halleStrasse}", HallePLZ="${hallePLZ}", HalleOrt="${halleOrt}", HeimMannschaft="${heimMannschaft}"`) - } + if (index < 3) { + loggerInfo('Spielplan-Zeile Halle-Daten', { index, halleName, halleStrasse, hallePLZ, halleOrt, heimMannschaft }) + } if (halleName && halleStrasse && hallePLZ && halleOrt) { const halleKey = `${halleName}|${halleStrasse}|${hallePLZ}|${halleOrt}` @@ -173,7 +177,7 @@ export default defineEventHandler(async (event) => { }) const hallenListe = Array.from(hallenMap.values()) - console.log('Gefundene Hallen:', hallenListe.length, hallenListe) + loggerInfo('Gefundene Hallen', { count: hallenListe.length, hallen: hallenListe }) // Generiere LaTeX-Code für PDF const teamName = team.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()) @@ -323,7 +327,7 @@ ${hallenListe.map(halle => { await fs.unlink(tempTexFile.replace('.tex', '.log')) await fs.unlink(tempTexFile.replace('.tex', '.aux')) } catch (_error) { - console.error('Fehler beim Löschen temporärer Dateien:', error) + loggerError('Fehler beim Löschen temporärer Dateien:', { error: _error }) } }, 5000) @@ -343,7 +347,7 @@ ${hallenListe.map(halle => { return pdfBuffer } catch (compileError) { - console.error('LaTeX-Kompilierung fehlgeschlagen:', compileError) + loggerError('LaTeX-Kompilierung fehlgeschlagen:', { error: compileError }) // Fallback: HTML-Response mit Fehlermeldung const errorHtml = ` @@ -375,7 +379,7 @@ ${hallenListe.map(halle => { } } catch (error) { - console.error('Fehler beim Generieren des Spielplans:', error) + loggerError('Fehler beim Generieren des Spielplans:', { error }) if (error.statusCode) { throw error diff --git a/server/api/spielplan/seasons.get.js b/server/api/spielplan/seasons.get.js index d10b9a4..503a428 100644 --- a/server/api/spielplan/seasons.get.js +++ b/server/api/spielplan/seasons.get.js @@ -1,4 +1,5 @@ import { getCurrentSeasonSlug, listSpielplanSeasons } from '../../utils/spielplan-data.js' +import { error as loggerError } from '../../utils/logger.js' export default defineEventHandler(async () => { try { @@ -13,7 +14,7 @@ export default defineEventHandler(async () => { defaultSeason } } catch (error) { - console.error('Fehler beim Laden der Spielplan-Saisons:', error) + loggerError('Fehler beim Laden der Spielplan-Saisons:', { error }) return { success: false, seasons: [], diff --git a/server/plugins/spielplan-import-scheduler.js b/server/plugins/spielplan-import-scheduler.js index bd75813..02f4944 100644 --- a/server/plugins/spielplan-import-scheduler.js +++ b/server/plugins/spielplan-import-scheduler.js @@ -1,4 +1,5 @@ import { importSpielplan } from '../utils/spielplan-import.js' +import { info as loggerInfo, error as loggerError } from '../utils/logger.js' const TIME_ZONE = 'Europe/Berlin' const RUN_HOUR = 7 @@ -68,9 +69,9 @@ async function runImport(reason) { running = true try { const result = await importSpielplan() - console.log(`[spielplan-import] ${reason}: ${result.matchCount} Spiele importiert (${result.source.season.dateStart} bis ${result.source.season.dateEnd})`) + loggerInfo(`[spielplan-import] ${reason}: ${result.matchCount} Spiele importiert`, { range: `${result.source.season.dateStart} - ${result.source.season.dateEnd}` }) } catch (error) { - console.error('[spielplan-import] Import fehlgeschlagen:', error) + loggerError('[spielplan-import] Import fehlgeschlagen:', { error }) } finally { running = false } @@ -86,12 +87,12 @@ function scheduleNext() { }, delay) timer.unref?.() - console.log(`[spielplan-import] Naechster Lauf: ${runAt.toISOString()} (${TIME_ZONE} ${String(RUN_HOUR).padStart(2, '0')}:${String(RUN_MINUTE).padStart(2, '0')})`) + loggerInfo('[spielplan-import] Naechster Lauf', { runAt: runAt.toISOString(), tz: TIME_ZONE, time: `${String(RUN_HOUR).padStart(2, '0')}:${String(RUN_MINUTE).padStart(2, '0')}` }) } export default defineNitroPlugin((nitroApp) => { if (process.env.SPIELPLAN_IMPORT_DISABLED === 'true') { - console.log('[spielplan-import] Scheduler deaktiviert') + loggerInfo('[spielplan-import] Scheduler deaktiviert') return } diff --git a/server/utils/logger.js b/server/utils/logger.js new file mode 100644 index 0000000..d2dd446 --- /dev/null +++ b/server/utils/logger.js @@ -0,0 +1,27 @@ +export function _formatMessage(level, message, context) { + const time = new Date().toISOString() + try { + const ctx = context ? ` ${JSON.stringify(context)}` : '' + return `${time} [${level}] ${message}${ctx}` + } catch (_e) { + return `${time} [${level}] ${message}` + } +} + +export function info(message, context) { + console.log(_formatMessage('info', message, context)) +} + +export function warn(message, context) { + console.warn(_formatMessage('warn', message, context)) +} + +export function error(message, context) { + console.error(_formatMessage('error', message, context)) +} + +export default { + info, + warn, + error +} diff --git a/server/utils/spielplan-data.js b/server/utils/spielplan-data.js index 42ec826..c680ba8 100644 --- a/server/utils/spielplan-data.js +++ b/server/utils/spielplan-data.js @@ -1,6 +1,7 @@ import { promises as fs } from 'fs' import path from 'path' import { getProjectPath, getServerDataPath } from './paths.js' +import { error as loggerError, info as loggerInfo } from './logger.js' const SPIELPLAN_HEADERS = [ 'Termin', @@ -76,10 +77,10 @@ function seasonSlugToLabel(slug) { } function logReadError(message, filePath, error) { - console.error(message, { filePath, error }) + loggerError(message, { filePath, error }) } -function requireSeasonSlug(seasonSlug) { +export function requireSeasonSlug(seasonSlug) { const value = String(seasonSlug || '') if (!SEASON_SLUG_PATTERN.test(value)) { throw new Error(`Ungueltiger Spielplan-Saison-Slug: ${value}`) @@ -87,6 +88,15 @@ function requireSeasonSlug(seasonSlug) { return value } +export function validateSeasonSlug(seasonSlug) { + try { + requireSeasonSlug(seasonSlug) + return true + } catch (_e) { + return false + } +} + function normalizeSpielplanCsvPath(csvPath) { const value = String(csvPath || '') if (value.includes('\0') || path.basename(value) !== 'spielplan.csv') {