/** * PDF Generator Service - Main service for PDF generation * Clean Code: Facade Pattern, Single Responsibility */ import { PDFDocument } from 'pdf-lib' import fs from 'fs/promises' import path from 'path' import { fillPdfForm } from './pdf-form-filler.js' /** * PDF Generation Result */ export class PDFGenerationResult { constructor(success, pdfBuffer, filename, error = null) { this.success = success this.pdfBuffer = pdfBuffer this.filename = filename this.error = error } } /** * PDF Generator Service */ export class PDFGeneratorService { constructor() { this.templatePath = path.join(process.cwd(), 'server', 'templates', 'mitgliedschaft-fillable.pdf') this.fallbackTemplatePath = path.join(process.cwd(), 'server', 'templates', 'Aufnahmeantrag 2025.pdf') } /** * Generates PDF from template with form data * @param {Object} data - Form data * @returns {Promise} Generation result */ async generateFromTemplate(data) { try { const templatePath = await this.getTemplatePath() const templateBytes = await fs.readFile(templatePath) const pdfDoc = await PDFDocument.load(templateBytes) const form = pdfDoc.getForm() // Fill form fields await fillPdfForm(pdfDoc, form, data) // Don't flatten form to keep fields editable // form.flatten() makes fields non-editable // Generate filename const filename = this.generateFilename(data) // Save PDF const pdfBytes = await pdfDoc.save() return new PDFGenerationResult(true, Buffer.from(pdfBytes), filename) } catch (_error) { console.error('Template PDF generation failed:', error.message) return new PDFGenerationResult(false, null, null, error.message) } } /** * Gets the appropriate template path * @returns {Promise} Template path */ async getTemplatePath() { try { await fs.access(this.templatePath) return this.templatePath } catch (_error) { try { await fs.access(this.fallbackTemplatePath) return this.fallbackTemplatePath } catch (_fallbackError) { throw new Error('No PDF template found') } } } /** * Generates filename for PDF * @param {Object} data - Form data * @returns {string} Filename */ generateFilename(data) { const timestamp = Date.now() return `beitrittserklärung_${timestamp}.pdf` } /** * Saves PDF to file system * @param {Buffer} pdfBuffer - PDF buffer * @param {string} filename - Filename * @param {string} uploadDir - Upload directory * @returns {Promise} File path */ async savePDF(pdfBuffer, filename, uploadDir) { // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal const filePath = path.join(uploadDir, filename) await fs.writeFile(filePath, pdfBuffer) return filePath } }