Refactor membership PDF generation logic to improve maintainability and validation; remove deprecated form filling methods and enhance email notification process. Update membership page styles for better layout and user experience.

This commit is contained in:
Torsten Schulz (local)
2025-10-23 15:04:45 +02:00
parent 28a2d05ab5
commit 95ea3a26bc
11 changed files with 862 additions and 800 deletions

View File

@@ -0,0 +1,105 @@
/**
* 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<PDFGenerationResult>} 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<string>} 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()
const name = `${data.nachname || 'Unbekannt'}_${data.vorname || 'Unbekannt'}`
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<string>} File path
*/
async savePDF(pdfBuffer, filename, uploadDir) {
const filePath = path.join(uploadDir, filename)
await fs.writeFile(filePath, pdfBuffer)
return filePath
}
}