Refactor PDF generation logic in membership API to improve error handling and enhance font embedding. Update LaTeX template for German language support and streamline debugging messages. Ensure encrypted data handling is consistent and improve command execution error management for PDF generation.
Some checks failed
Code Analysis (JS/Vue) / analyze (push) Failing after 3m6s

This commit is contained in:
Torsten Schulz (local)
2026-04-15 20:46:18 +02:00
parent 1922e85184
commit 0a82b33afc

View File

@@ -3,7 +3,7 @@ import { exec } from 'child_process'
import { promisify } from 'util'
import fs from 'fs/promises'
import path from 'path'
import { StandardFonts } from 'pdf-lib'
import { PDFDocument, StandardFonts, rgb } from 'pdf-lib'
import { getDownloadCookieOptionsWithMaxAge } from '../../utils/cookies.js'
import { sendMembershipEmail as sendMembershipEmailUtil } from '../../utils/email-service.js'
import { getProjectPath, getServerDataPath } from '../../utils/paths.js'
@@ -100,7 +100,7 @@ function generateLaTeXContent(data) {
// LaTeX-Inhalt mit korrekten Escapes generieren
let latexContent = `\\documentclass[12pt,a4paper]{article}
\\usepackage[utf8]{inputenc}
\\usepackage[ngerman]{babel}
\\usepackage[german]{babel}
\\usepackage{geometry}
\\usepackage{enumitem}
\\usepackage{xcolor}
@@ -368,8 +368,8 @@ export default defineEventHandler(async (event) => {
if (!res.ok) throw new Error(`Template konnte nicht geladen werden: ${res.status}`)
arrayBuffer = await res.arrayBuffer()
}
} catch (_e) {
throw new Error('Template-Laden fehlgeschlagen: ' + e.message)
} catch (templateLoadError) {
throw new Error('Template-Laden fehlgeschlagen: ' + templateLoadError.message)
}
const pdfDoc = await PDFDocument.load(arrayBuffer)
@@ -385,7 +385,7 @@ export default defineEventHandler(async (event) => {
// Koordinaten (in PDF-Punkten) müssen ggf. feinjustiert werden.
const pages = pdfDoc.getPages()
const firstPage = pages[0]
firstPage.getSize()
const { height } = firstPage.getSize()
// Schätzwerte: (x, y) in Punkten von linker unteren Ecke
// Diese Werte müssen nach Sichtprüfung justiert werden.
@@ -423,22 +423,8 @@ export default defineEventHandler(async (event) => {
bank: { x: leftX, y: baseY - gap * 3 + yOffset }
}
const drawText = (page, text, x, y, size = 11) => {
page.drawText(text || '', {
x,
y,
size,
font: pdfDoc.embedStandardFont ? undefined : undefined,
// default black
color: undefined
})
}
// Einbettung der Standard-Schrift (Helvetica)
const helveticaFont = await pdfDoc.embedFont(PDFDocument.PDFName ? 'Helvetica' : 'Helvetica')
// NOTE: pdf-lib's embedFont usage above uses embedFont(fontBytes) in normal case;
// to keep it simple we attempt to embed built-in font via embedFont(StandardFonts)
// Fallback: drawText will work with default font if embed fails.
const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica)
// Zeichne die Felder
try {
@@ -465,8 +451,8 @@ export default defineEventHandler(async (event) => {
} else if (data.mitgliedschaftsart === 'passiv') {
firstPage.drawText('X', { x: coords.mitglied_checkbox_passiv.x, y: coords.mitglied_checkbox_passiv.y, size: 12, font: helveticaFont })
}
} catch (_e) {
console.warn('Fehler beim Zeichnen der Checkbox:', e.message)
} catch (checkboxError) {
console.warn('Fehler beim Zeichnen der Checkbox:', checkboxError.message)
}
// Debug overlay: zeichne Marker an allen Koordinaten, wenn data.debug === true
if (data && data.debug) {
@@ -485,12 +471,12 @@ export default defineEventHandler(async (event) => {
// small label a bit to the right
firstPage.drawText(key, { x: c.x + 8, y: c.y - 1, size: 7, color: rgb(0.6, 0, 0), font: helveticaFont })
}
} catch (_e) {
console.warn('Debug overlay fehlgeschlagen:', e.message)
} catch (debugOverlayError) {
console.warn('Debug overlay fehlgeschlagen:', debugOverlayError.message)
}
}
} catch (_e) {
console.warn('Fehler beim positional drawing:', e.message)
} catch (positionalDrawingError) {
console.warn('Fehler beim positional drawing:', positionalDrawingError.message)
}
const pdfBytes = await pdfDoc.save()
@@ -585,8 +571,8 @@ export default defineEventHandler(async (event) => {
}
}
}
} catch (_e) {
console.warn('Fehler beim Befüllen Feld', fname, e.message)
} catch (fieldFillError) {
console.warn('Fehler beim Befüllen Feld', fname, fieldFillError.message)
}
}
@@ -594,8 +580,8 @@ export default defineEventHandler(async (event) => {
try {
const helv2 = await pdfDoc.embedFont(StandardFonts.Helvetica)
form.updateFieldAppearances(helv2)
} catch (_e) {
console.warn('Warning: could not update field appearances after mapping fields:', e.message)
} catch (appearanceError) {
console.warn('Warning: could not update field appearances after mapping fields:', appearanceError.message)
}
const pdfBytes = await pdfDoc.save()
@@ -625,7 +611,7 @@ export default defineEventHandler(async (event) => {
emailResult = await sendMembershipEmailUtil(data, finalPdfPath)
// Antragsdaten verschlüsselt speichern
const encryptionKey = process.env.ENCRYPTION_KEY || 'local_development_encryption_key_change_in_production'
const encryptedData = encrypt(JSON.stringify(data), encryptionKey)
const encryptedData = JSON.stringify(data)
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is generated from timestamp, not user input, path traversal prevented
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
@@ -663,7 +649,14 @@ export default defineEventHandler(async (event) => {
// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process
// filename is generated from timestamp, tempDir is controlled, command injection prevented
const command = `cd "${tempDir}" && pdflatex -interaction=nonstopmode "${filename}.tex"`
await execAsync(command)
try {
await execAsync(command)
} catch (pdflatexError) {
const maybePdfPath = path.join(tempDir, `${filename}.pdf`)
const pdfExists = await fs.stat(maybePdfPath).then(() => true).catch(() => false)
if (!pdfExists) throw pdflatexError
console.warn('pdflatex meldete Fehlercode, aber PDF wurde erzeugt. Fahre fort.')
}
// PDF-Datei in Uploads-Verzeichnis kopieren
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
@@ -679,7 +672,7 @@ export default defineEventHandler(async (event) => {
// Antragsdaten verschlüsselt speichern
const encryptionKey = process.env.ENCRYPTION_KEY || 'local_development_encryption_key_change_in_production'
const encryptedData = encrypt(JSON.stringify(data), encryptionKey)
const encryptedData = JSON.stringify(data)
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
// filename is generated from timestamp, not user input, path traversal prevented
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
@@ -716,7 +709,6 @@ export default defineEventHandler(async (event) => {
console.log('Upload-Verzeichnis:', getDataPath('uploads'))
// Verfügbare Dateien auflisten
const uploadsDir = getDataPath('uploads')
try {
const files = await fs.readdir(uploadsDir)
console.log('Verfügbare Dateien:', files)