diff --git a/server/api/cms/save-csv.post.js b/server/api/cms/save-csv.post.js index 0d91c23..2fe9c29 100644 --- a/server/api/cms/save-csv.post.js +++ b/server/api/cms/save-csv.post.js @@ -4,7 +4,15 @@ import { getUserFromToken, hasAnyRole } from '../../utils/auth.js' export default defineEventHandler(async (event) => { try { - const token = getCookie(event, 'auth_token') + 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) { @@ -49,16 +57,7 @@ export default defineEventHandler(async (event) => { // 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 pathExists = async (p) => { - try { - await fs.access(p) - return true - } catch { - return false - } - } + const cwd = process.cwd() const writeFileAtomicAndVerify = async (targetPath, data) => { const dataDir = path.dirname(targetPath) @@ -99,33 +98,19 @@ export default defineEventHandler(async (event) => { path.join(cwd, '../server/data/public-data', filename) ] - // Behalte legacy `.output` write nur als optionalen, nicht-standardisierten Pfad - // (wird NICHT automatisch gefordert). Hauptsächlich schreiben wir intern. const uniquePaths = [...new Set([...internalPaths])] const writeResults = [] const writeErrors = [] - let wrotePreferred = false for (const targetPath of uniquePaths) { try { await writeFileAtomicAndVerify(targetPath, content) writeResults.push(targetPath) - if (preferredPaths.includes(targetPath)) wrotePreferred = true } catch (e) { writeErrors.push({ targetPath, error: e?.message || String(e) }) } } - // Wenn wir ein `.output/public` gefunden haben, MUSS auch dorthin geschrieben worden sein. - // Sonst melden wir nicht "Erfolg", weil die laufende Instanz dann weiterhin alte/defekte Daten ausliefert. - if (preferredPaths.length > 0 && !wrotePreferred) { - console.error('CSV wurde NICHT in .output/public geschrieben. Errors:', writeErrors) - throw createError({ - statusCode: 500, - statusMessage: 'CSV konnte nicht in das ausgelieferte Verzeichnis geschrieben werden' - }) - } - if (writeResults.length === 0) { console.error('Konnte CSV-Datei in keinen Zielpfad schreiben:', writeErrors) throw createError({ diff --git a/tests/cms-files-endpoints.spec.ts b/tests/cms-files-endpoints.spec.ts index 52acee9..a71e591 100644 --- a/tests/cms-files-endpoints.spec.ts +++ b/tests/cms-files-endpoints.spec.ts @@ -37,7 +37,38 @@ vi.mock('child_process', () => ({ })) vi.mock('util', () => ({ - promisify: () => () => Promise.resolve({ stdout: 'PDF Inhalt', stderr: '' }) + promisify: () => () => Promise.resolve({ + stdout: `§ 1 Name und Sitz +Der Verein führt den Namen Harheimer TC. + +§ 2 Zweck +Der Verein verfolgt ausschließlich und unmittelbar gemeinnützige Zwecke. + +§ 3 Mitgliedschaft +(1) Mitglied kann jede natürliche Person werden. +(2) Über die Aufnahme entscheidet der Vorstand. + +§ 4 Beiträge +Die Mitglieder zahlen Beiträge nach Maßgabe der Beitragsordnung. + +§ 5 Vorstand +Der Vorstand besteht aus dem Vorsitzenden, dem Schriftführer und dem Kassenwart. + +§ 6 Schlussbestimmungen +Diese Satzung tritt mit Beschluss der Mitgliederversammlung in Kraft. + +Zusätzlicher Satzungstext zur Plausibilitätsprüfung. +Zusätzlicher Satzungstext zur Plausibilitätsprüfung. +Zusätzlicher Satzungstext zur Plausibilitätsprüfung. +Zusätzlicher Satzungstext zur Plausibilitätsprüfung. +Zusätzlicher Satzungstext zur Plausibilitätsprüfung. +`, + stderr: '' + }) +})) + +vi.mock('../server/utils/upload-validation.js', () => ({ + assertPdfMagicHeader: vi.fn().mockResolvedValue(undefined) })) import saveCsvHandler from '../server/api/cms/save-csv.post.js' @@ -67,11 +98,26 @@ describe('CMS File Endpoints', () => { mockSuccessReadBody({ filename: 'mannschaften.csv', content: 'data' }) vi.spyOn(fs, 'mkdir').mockResolvedValue(undefined) vi.spyOn(fs, 'writeFile').mockResolvedValue(undefined) + vi.spyOn(fs, 'rename').mockResolvedValue(undefined) + vi.spyOn(fs, 'stat').mockResolvedValue({ size: Buffer.byteLength('data', 'utf8') } as any) const response = await saveCsvHandler(event) expect(response.success).toBe(true) expect(fs.writeFile).toHaveBeenCalled() }) + + it('erlaubt vorstand beim CSV-Speichern', async () => { + const event = createEvent({ cookies: { auth_token: 'token' } }) + mockSuccessReadBody({ filename: 'spielplan.csv', content: 'kopf;wert' }) + vi.spyOn(fs, 'mkdir').mockResolvedValue(undefined) + vi.spyOn(fs, 'writeFile').mockResolvedValue(undefined) + vi.spyOn(fs, 'rename').mockResolvedValue(undefined) + vi.spyOn(fs, 'stat').mockResolvedValue({ size: Buffer.byteLength('kopf;wert', 'utf8') } as any) + getUserFromToken.mockResolvedValue({ id: 'vorstand', role: 'vorstand' }) + + const response = await saveCsvHandler(event) + expect(response.success).toBe(true) + }) }) describe('POST /api/cms/upload-spielplan-pdf', () => {