139 lines
4.3 KiB
JavaScript
139 lines
4.3 KiB
JavaScript
import fs from 'fs/promises'
|
|
import path from 'path'
|
|
import { getUserFromToken, hasAnyRole } from '../../utils/auth.js'
|
|
|
|
export default defineEventHandler(async (event) => {
|
|
try {
|
|
let token = getCookie(event, 'auth_token')
|
|
|
|
if (!token) {
|
|
const authHeader = getHeader(event, 'authorization')
|
|
if (authHeader && authHeader.startsWith('Bearer ')) {
|
|
token = authHeader.substring(7).trim()
|
|
}
|
|
}
|
|
|
|
const currentUser = token ? await getUserFromToken(token) : null
|
|
|
|
if (!currentUser) {
|
|
throw createError({
|
|
statusCode: 401,
|
|
statusMessage: 'Nicht authentifiziert'
|
|
})
|
|
}
|
|
|
|
if (!hasAnyRole(currentUser, 'admin', 'vorstand')) {
|
|
throw createError({
|
|
statusCode: 403,
|
|
statusMessage: 'Keine Berechtigung'
|
|
})
|
|
}
|
|
|
|
const { filename, content } = await readBody(event)
|
|
|
|
if (!filename || !content) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
statusMessage: 'Filename und Content sind erforderlich'
|
|
})
|
|
}
|
|
|
|
// Sicherheitsprüfung: Nur bestimmte Dateien erlauben
|
|
const allowedFiles = [
|
|
'vereinsmeisterschaften.csv',
|
|
'mannschaften.csv',
|
|
'termine.csv',
|
|
'spielplan.csv'
|
|
]
|
|
|
|
if (!allowedFiles.includes(filename)) {
|
|
throw createError({
|
|
statusCode: 403,
|
|
statusMessage: 'Datei nicht erlaubt'
|
|
})
|
|
}
|
|
|
|
// Neuer Ablauf (Option B): Schreibe CSVs ausschließlich in internes Datenverzeichnis,
|
|
// damit keine direkten Schreibzugriffe auf `public/` stattfinden.
|
|
// Später kann ein kontrollierter Deploy-/Sync-Prozess die Daten aus `server/data/public-data`
|
|
// in die öffentlich ausgelieferte `public/`-Location übernehmen.
|
|
const cwd = process.cwd()
|
|
|
|
const writeFileAtomicAndVerify = async (targetPath, data) => {
|
|
const dataDir = path.dirname(targetPath)
|
|
await fs.mkdir(dataDir, { recursive: true })
|
|
|
|
// Atomar schreiben: erst temp-Datei in *gleichem Verzeichnis*, dann rename.
|
|
// So vermeiden wir:
|
|
// - halb geschriebene Dateien (Reader sieht "Partial Transfer")
|
|
// - Erfolgsmeldungen, obwohl die Datei effektiv kaputt ist
|
|
const tmpPath = `${targetPath}.tmp-${process.pid}-${Date.now()}`
|
|
try {
|
|
await fs.writeFile(tmpPath, data, 'utf8')
|
|
await fs.rename(tmpPath, targetPath)
|
|
|
|
const expectedSize = Buffer.byteLength(data, 'utf8')
|
|
const st = await fs.stat(targetPath)
|
|
if (st.size !== expectedSize) {
|
|
throw new Error(`Size mismatch after write. expected=${expectedSize} actual=${st.size}`)
|
|
}
|
|
|
|
// Wenn beim Build pre-komprimierte Assets erzeugt wurden, können für CSVs
|
|
// noch alte `.gz`/`.br` Dateien liegen bleiben. Nach einem Update würden dann
|
|
// ggf. inkonsistente Inhalte ausgeliefert (Browser meldet Partial Transfer).
|
|
// Daher: nach erfolgreichem Schreiben alte Varianten entfernen.
|
|
for (const ext of ['.gz', '.br']) {
|
|
try { await fs.unlink(`${targetPath}${ext}`) } catch (_e3) {}
|
|
}
|
|
} catch (e) {
|
|
// best-effort cleanup
|
|
try { await fs.unlink(tmpPath) } catch (_e2) {}
|
|
throw e
|
|
}
|
|
}
|
|
|
|
// Ziel: internes Datenverzeichnis unter `server/data/public-data` (persistente, interne Quelle)
|
|
const internalPaths = [
|
|
path.join(cwd, 'server/data/public-data', filename),
|
|
path.join(cwd, '../server/data/public-data', filename)
|
|
]
|
|
|
|
const uniquePaths = [...new Set([...internalPaths])]
|
|
const writeResults = []
|
|
const writeErrors = []
|
|
|
|
for (const targetPath of uniquePaths) {
|
|
try {
|
|
await writeFileAtomicAndVerify(targetPath, content)
|
|
writeResults.push(targetPath)
|
|
} catch (e) {
|
|
writeErrors.push({ targetPath, error: e?.message || String(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'
|
|
})
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
message: 'Datei erfolgreich gespeichert',
|
|
writtenTo: writeResults
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Speichern der CSV-Datei:', error)
|
|
if (error.statusCode) {
|
|
throw error
|
|
}
|
|
throw createError({
|
|
statusCode: 500,
|
|
statusMessage: 'Fehler beim Speichern der Datei'
|
|
})
|
|
}
|
|
})
|