/** * Ablage der gesendeten AB-PDFs unter config.auftraege.uploadDir (wird bei jedem Aufruf gelesen): * //.pdf, Verzeichnis 0700, Datei 0600. Überschreibt nie (die gesendete Fassung ist * der Beleg). Kennungen und Pfade sind auf [A-Za-z0-9_-] begrenzt (kein Punkt außer vor „pdf“), damit kein „..“ und * kein fremder Pfad möglich ist. */ import { createHash } from 'node:crypto' import { mkdir, readFile, unlink, writeFile } from 'node:fs/promises' import path from 'node:path' import { config } from '../../config.js' import { AuftragError } from './fehler.js' const KENNUNG = /^[A-Za-z0-9_-]{1,64}$/ const PDF_PFAD = /^[A-Za-z0-9_-]{1,64}\/[A-Za-z0-9_-]{1,64}\.pdf$/ const nichtGefunden = () => new AuftragError(404, 'PDF nicht gefunden.') const gueltigeKennung = (wert) => typeof wert === 'string' && KENNUNG.test(wert) function pfadVon(pdfDatei) { if (typeof pdfDatei !== 'string' || !PDF_PFAD.test(pdfDatei)) throw nichtGefunden() return path.join(config.auftraege.uploadDir, pdfDatei) } export async function speicherePdf(kundenId, auftragId, buffer) { if (!gueltigeKennung(kundenId) || !gueltigeKennung(auftragId)) throw new TypeError('Ungültige Kunden- oder Auftrags-ID für die PDF-Ablage.') if (!Buffer.isBuffer(buffer) || buffer.length === 0) throw new TypeError('Das PDF muss ein nicht leerer Buffer sein.') const pdfDatei = `${kundenId}/${auftragId}.pdf` const datei = path.join(config.auftraege.uploadDir, pdfDatei) await mkdir(path.dirname(datei), { recursive: true, mode: 0o700 }) try { await writeFile(datei, buffer, { flag: 'wx', mode: 0o600 }) } catch (err) { if (err?.code === 'EEXIST') throw new AuftragError(409, 'Das PDF dieser Auftragsbestätigung ist schon gespeichert.') // eine angelegte Teildatei nicht liegen lassen await unlink(datei).catch(() => {}) throw err } return { pdfDatei, pdfSha256: createHash('sha256').update(buffer).digest('hex') } } export async function lesePdf(pdfDatei) { const datei = pfadVon(pdfDatei) try { return await readFile(datei) } catch (err) { if (err?.code === 'ENOENT') throw nichtGefunden() throw err } } /** Räumt ein abgelegtes PDF weg, wenn danach etwas scheitert (L3). Fehlt die Datei, ist das folgenlos. */ export async function entfernePdf(pdfDatei) { const datei = pfadVon(pdfDatei) try { await unlink(datei) } catch (err) { if (err?.code !== 'ENOENT') throw err } }