import { promises as fs } from 'fs' import path from 'path' import { randomUUID } from 'crypto' // Handle both dev and production paths // filename is always a hardcoded constant (e.g., 'termine.csv'), never user input const getDataPath = (filename) => { const cwd = process.cwd() // In production (.output/server), working dir is .output if (cwd.endsWith('.output')) { // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal return path.join(cwd, '../public/data', filename) } // In development, working dir is project root // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal return path.join(cwd, 'public/data', filename) } const TERMINE_FILE = getDataPath('termine.csv') // Parse CSV to array of objects export async function readTermine() { try { const data = await fs.readFile(TERMINE_FILE, 'utf-8') const lines = data.split('\n').filter(line => line.trim() !== '') if (lines.length < 2) return [] // Parse CSV with quote handling const termine = [] for (let i = 1; i < lines.length; i++) { const values = [] let current = '' let inQuotes = false for (let j = 0; j < lines[i].length; j++) { const char = lines[i][j] if (char === '"') { inQuotes = !inQuotes } else if (char === ',' && !inQuotes) { values.push(current.trim()) current = '' } else { current += char } } values.push(current.trim()) if (values.length >= 4) { termine.push({ id: randomUUID(), // Generate ID on-the-fly for editing datum: values[0], uhrzeit: values[1] || '', titel: values[2] || '', beschreibung: values[3] || '', kategorie: values[4] || 'Sonstiges' }) } } return termine } catch (error) { if (error.code === 'ENOENT') { return [] } console.error('Fehler beim Lesen der Termine:', error) return [] } } // Write array of objects to CSV export async function writeTermine(termine) { try { let csv = '"datum","uhrzeit","titel","beschreibung","kategorie"\n' for (const termin of termine) { const datum = termin.datum || '' const uhrzeit = termin.uhrzeit || '' const titel = termin.titel || '' const beschreibung = termin.beschreibung || '' const kategorie = termin.kategorie || '' // Escape quotes in values const escapedDatum = datum.replace(/"/g, '""') const escapedUhrzeit = uhrzeit.replace(/"/g, '""') const escapedTitel = titel.replace(/"/g, '""') const escapedBeschreibung = beschreibung.replace(/"/g, '""') const escapedKategorie = kategorie.replace(/"/g, '""') csv += `"${escapedDatum}","${escapedUhrzeit}","${escapedTitel}","${escapedBeschreibung}","${escapedKategorie}"\n` } await fs.writeFile(TERMINE_FILE, csv, 'utf-8') return true } catch (error) { console.error('Fehler beim Schreiben der Termine:', error) return false } } // Add or update termin export async function saveTermin(terminData) { const termine = await readTermine() // Always add as new (CSV doesn't have persistent IDs) const newTermin = { datum: terminData.datum, uhrzeit: terminData.uhrzeit || '', titel: terminData.titel, beschreibung: terminData.beschreibung || '', kategorie: terminData.kategorie || 'Sonstiges' } termine.push(newTermin) // Sort by date termine.sort((a, b) => new Date(a.datum) - new Date(b.datum)) await writeTermine(termine) return true } // Delete termin by matching all fields (since we don't have persistent IDs) export async function deleteTermin(terminData) { let termine = await readTermine() termine = termine.filter(t => !(t.datum === terminData.datum && (t.uhrzeit || '') === (terminData.uhrzeit || '') && t.titel === terminData.titel && t.beschreibung === terminData.beschreibung && t.kategorie === terminData.kategorie) ) await writeTermine(termine) return true }