106 lines
2.9 KiB
JavaScript
106 lines
2.9 KiB
JavaScript
/**
|
|
* 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()
|
|
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) {
|
|
// 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
|
|
}
|
|
}
|