diff --git a/package-lock.json b/package-lock.json index 18f1b29..d72ae13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "pdf-lib": "^1.17.1", "pdf-parse": "^2.4.5", "pinia": "^3.0.3", - "quill": "^2.0.3", + "quill": "^2.0.2", "sharp": "^0.34.5", "tinymce": "^8.3.1", "vue": "^3.5.22" @@ -6672,9 +6672,9 @@ } }, "node_modules/devalue": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.4.1.tgz", - "integrity": "sha512-YtoaOfsqjbZQKGIMRYDWKjUmSB4VJ/RElB+bXZawQAQYAo4xu08GKTMVlsZDTF6R2MbAgjcAQRPI5eIyRAT2OQ==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.2.tgz", + "integrity": "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==", "license": "MIT" }, "node_modules/dezalgo": { @@ -6696,9 +6696,9 @@ "license": "Apache-2.0" }, "node_modules/diff": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", - "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", + "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" @@ -7927,9 +7927,9 @@ } }, "node_modules/h3": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.4.tgz", - "integrity": "sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ==", + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.5.tgz", + "integrity": "sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==", "license": "MIT", "dependencies": { "cookie-es": "^1.2.2", @@ -7937,12 +7937,18 @@ "defu": "^6.1.4", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", - "node-mock-http": "^1.0.2", + "node-mock-http": "^1.0.4", "radix3": "^1.1.2", - "ufo": "^1.6.1", + "ufo": "^1.6.3", "uncrypto": "^0.1.3" } }, + "node_modules/h3/node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "license": "MIT" + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -9765,9 +9771,9 @@ } }, "node_modules/node-mock-http": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.3.tgz", - "integrity": "sha512-jN8dK25fsfnMrVsEhluUTPkBFY+6ybu7jSB1n+ri/vOGjJxU8J9CZhpSGkHXSkFjtUhbmoncG/YG9ta5Ludqog==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.4.tgz", + "integrity": "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==", "license": "MIT" }, "node_modules/node-releases": { @@ -11423,9 +11429,9 @@ "license": "MIT" }, "node_modules/quill": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/quill/-/quill-2.0.3.tgz", - "integrity": "sha512-xEYQBqfYx/sfb33VJiKnSJp8ehloavImQ2A6564GAbqG55PGw1dAWUn1MUbQB62t0azawUS2CZZhWCjO8gRvTw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/quill/-/quill-2.0.2.tgz", + "integrity": "sha512-QfazNrhMakEdRG57IoYFwffUIr04LWJxbS/ZkidRFXYCQt63c1gK6Z7IHUXMx/Vh25WgPBU42oBaNzQ0K1R/xw==", "license": "BSD-3-Clause", "dependencies": { "eventemitter3": "^5.0.1", @@ -12892,9 +12898,9 @@ } }, "node_modules/tar": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", - "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.3.tgz", + "integrity": "sha512-ENg5JUHUm2rDD7IvKNFGzyElLXNjachNLp6RaGf4+JOgxXHkqA+gq81ZAMCUmtMtqBsoU62lcp6S27g1LCYGGQ==", "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", @@ -13220,9 +13226,9 @@ } }, "node_modules/undici": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.16.0.tgz", - "integrity": "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.18.2.tgz", + "integrity": "sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw==", "license": "MIT", "engines": { "node": ">=20.18.1" diff --git a/package.json b/package.json index ae97dcc..58ccb2f 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "pdf-lib": "^1.17.1", "pdf-parse": "^2.4.5", "pinia": "^3.0.3", - "quill": "^2.0.3", + "quill": "^2.0.2", "sharp": "^0.34.5", "tinymce": "^8.3.1", "vue": "^3.5.22" diff --git a/pages/cms/mannschaften.vue b/pages/cms/mannschaften.vue index f5b1dff..41fbb25 100644 --- a/pages/cms/mannschaften.vue +++ b/pages/cms/mannschaften.vue @@ -336,7 +336,9 @@ const formData = ref({ const loadMannschaften = async () => { isLoading.value = true try { - const response = await fetch('/data/mannschaften.csv') + // Cache-Buster: Browser/CDN könnten CSV sonst aggressiv cachen + const url = `/data/mannschaften.csv?_t=${Date.now()}` + const response = await fetch(url, { cache: 'no-store' }) if (!response.ok) { throw new Error('Fehler beim Laden der Mannschaften') } @@ -466,7 +468,7 @@ const saveMannschaft = async () => { } } catch (error) { console.error('Fehler beim Speichern:', error) - errorMessage.value = error.data?.message || 'Fehler beim Speichern der Mannschaft.' + errorMessage.value = error?.data?.statusMessage || error?.statusMessage || error?.data?.message || 'Fehler beim Speichern der Mannschaft.' if (window.showErrorModal) { window.showErrorModal('Fehler', errorMessage.value) } diff --git a/server/api/cms/save-csv.post.js b/server/api/cms/save-csv.post.js index 0b9d98a..3bd9efc 100644 --- a/server/api/cms/save-csv.post.js +++ b/server/api/cms/save-csv.post.js @@ -45,33 +45,49 @@ export default defineEventHandler(async (event) => { }) } - // Handle both dev and production paths + // Wichtig: In Production werden statische Dateien aus `.output/public` ausgeliefert. + // Wenn PM2 `cwd` auf das Repo-Root setzt, ist `process.cwd()` NICHT `.output` – + // daher schreiben wir robust in alle sinnvollen Zielorte: + // - `.output/public/data/` (damit die laufende Instanz sofort die neuen Daten liefert) + // - `public/data/` (damit der nächste Build die Daten wieder übernimmt) + // // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal // filename is validated against allowlist above, path traversal prevented const cwd = process.cwd() - let filePath - - // 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 - filePath = path.join(cwd, '../public/data', filename) - } else { - // In development, working dir is project root - // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - filePath = path.join(cwd, 'public/data', filename) + const candidatePaths = [ + path.join(cwd, '.output/public/data', filename), + path.join(cwd, 'public/data', filename), + path.join(cwd, '../public/data', filename), // falls cwd z.B. `.output` oder `.output/server` ist + path.join(cwd, '../.output/public/data', filename) + ] + + const uniquePaths = [...new Set(candidatePaths)] + const writeResults = [] + const writeErrors = [] + + for (const targetPath of uniquePaths) { + try { + const dataDir = path.dirname(targetPath) + await fs.mkdir(dataDir, { recursive: true }) + await fs.writeFile(targetPath, content, 'utf8') + writeResults.push(targetPath) + } catch (e) { + writeErrors.push({ targetPath, error: e }) + } + } + + if (writeResults.length === 0) { + console.error('Konnte CSV-Datei in keinen Zielpfad schreiben:', writeErrors) + throw createError({ + statusCode: 500, + statusMessage: 'Fehler beim Speichern der Datei' + }) } - - const dataDir = path.dirname(filePath) - - // Sicherstellen, dass das Verzeichnis existiert - await fs.mkdir(dataDir, { recursive: true }) - - // Datei schreiben - await fs.writeFile(filePath, content, 'utf8') return { success: true, - message: 'Datei erfolgreich gespeichert' + message: 'Datei erfolgreich gespeichert', + writtenTo: writeResults } } catch (error) {