Files
harheimertc/server/api/membership/generate-pdf.post.js

281 lines
8.5 KiB
JavaScript

/**
* Membership PDF Generation API
* Clean Code: Single Responsibility, Dependency Injection
*/
import fs from 'fs/promises'
import path from 'path'
import { encrypt } from '../../utils/encryption.js'
import { PDFGeneratorService } from '../../utils/pdf-generator-service.js'
import nodemailer from 'nodemailer'
/**
* Gets the correct data path for config files
* @param {string} filename - Config filename
* @returns {string} Full path to config file
*/
function getDataPath(filename) {
const isProduction = process.env.NODE_ENV === 'production'
if (isProduction) {
// In production, process.cwd() points to .output
return path.join(process.cwd(), '..', 'server', 'data', filename)
} else {
// In development, process.cwd() points to project root
return path.join(process.cwd(), 'server', 'data', filename)
}
}
/**
* Formats a date string to DD.MM.YYYY format with leading zeros
* @param {string} dateString - Date string (YYYY-MM-DD format)
* @returns {string} Formatted date string (DD.MM.YYYY)
*/
function formatDateWithLeadingZeros(dateString) {
if (!dateString) return ''
try {
const date = new Date(dateString)
const day = String(date.getDate()).padStart(2, '0')
const month = String(date.getMonth() + 1).padStart(2, '0')
const year = date.getFullYear()
return `${day}.${month}.${year}`
} catch (error) {
return dateString
}
}
/**
* Validates form data
* @param {Object} data - Form data
* @returns {Object} Validation result
*/
function validateFormData(data) {
const required = ['vorname', 'nachname', 'strasse', 'plz', 'ort', 'geburtsdatum', 'email', 'mitgliedschaftsart']
const missing = required.filter(field => !data[field] || data[field].trim() === '')
if (missing.length > 0) {
return { valid: false, error: `Fehlende Pflichtfelder: ${missing.join(', ')}` }
}
// Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(data.email)) {
return { valid: false, error: 'Ungültige E-Mail-Adresse' }
}
// Validate IBAN if provided
if (data.iban && data.iban.length < 15) {
return { valid: false, error: 'Ungültige IBAN' }
}
return { valid: true }
}
/**
* Processes form data and adds computed fields
* @param {Object} data - Raw form data
* @returns {Object} Processed form data
*/
function processFormData(data) {
const processed = { ...data }
// Add computed fields
processed.isVolljaehrig = new Date().getFullYear() - new Date(data.geburtsdatum).getFullYear() >= 18
processed.sign_datum = formatDateWithLeadingZeros(new Date().toISOString().split('T')[0])
processed.sepa_datum = formatDateWithLeadingZeros(new Date().toISOString().split('T')[0])
processed.page3_datum = formatDateWithLeadingZeros(new Date().toISOString().split('T')[0])
return processed
}
/**
* Sends membership application email
* @param {Object} data - Form data
* @param {string} pdfPath - Path to generated PDF
* @returns {Promise<Object>} Email result
*/
async function sendMembershipEmail(data, pdfPath) {
try {
// Load config
const configPath = getDataPath('config.json')
let config = {}
try {
const configData = await fs.readFile(configPath, 'utf8')
config = JSON.parse(configData)
} catch (error) {
console.error('Could not load config.json:', error.message)
}
// Get recipients
const isProduction = process.env.NODE_ENV === 'production'
const recipients = []
if (!isProduction) {
recipients.push('tsschulz@tsschulz.de')
} else {
if (config.vorsitzender && config.vorsitzender.email) {
recipients.push(config.vorsitzender.email)
}
if (config.schriftfuehrer && config.schriftfuehrer.email) {
recipients.push(config.schriftfuehrer.email)
}
if (!data.isVolljaehrig && config.trainer && config.trainer.email) {
recipients.push(config.trainer.email)
}
if (recipients.length === 0) {
recipients.push('tsschulz@tsschulz.de')
}
}
// Create transporter
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST || 'localhost',
port: parseInt(process.env.SMTP_PORT) || 587,
secure: process.env.SMTP_SECURE === 'true',
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS
}
})
// Email content
const subject = `Neuer Mitgliedschaftsantrag - ${data.vorname} ${data.nachname}`
const message = `Ein neuer Mitgliedschaftsantrag wurde eingereicht.
Antragsteller: ${data.vorname} ${data.nachname}
Mitgliedschaftsart: ${data.mitgliedschaftsart}
Volljährig: ${data.isVolljaehrig ? 'Ja' : 'Nein'}
Das ausgefüllte Formular ist als Anhang verfügbar.`
// Email options
const mailOptions = {
from: process.env.SMTP_FROM || 'noreply@harheimertc.de',
to: recipients.join(', '),
subject: subject,
text: message,
attachments: [
{
filename: path.basename(pdfPath),
path: pdfPath
}
]
}
// Send email
const info = await transporter.sendMail(mailOptions)
return {
success: true,
message: message,
recipients: recipients,
messageId: info.messageId
}
} catch (error) {
console.error('Email sending failed:', error.message)
return {
success: false,
message: `E-Mail-Versand fehlgeschlagen: ${error.message}`,
error: error.message
}
}
}
/**
* Main API handler
*/
export default defineEventHandler(async (event) => {
try {
const body = await readBody(event)
// Validate input
const validation = validateFormData(body)
if (!validation.valid) {
throw createError({
statusCode: 400,
statusMessage: validation.error
})
}
// Process form data
const data = processFormData(body)
// Create upload directory
const uploadDir = path.join(process.cwd(), 'public', 'uploads')
await fs.mkdir(uploadDir, { recursive: true })
// Create temp directory for LaTeX
const tempDir = path.join(process.cwd(), '.output', 'temp', 'latex')
await fs.mkdir(tempDir, { recursive: true })
// Initialize PDF generator service
const pdfService = new PDFGeneratorService()
// Generate PDF from template
let result = await pdfService.generateFromTemplate(data)
if (!result.success) {
throw createError({
statusCode: 500,
statusMessage: 'PDF-Generierung fehlgeschlagen'
})
}
// Save PDF file
const filePath = await pdfService.savePDF(result.pdfBuffer, result.filename, uploadDir)
// Encrypt and save form data
try {
const password = process.env.ENCRYPTION_PASSWORD || 'default-password'
const encryptedData = encrypt(JSON.stringify(data), password)
const dataFilePath = path.join(uploadDir, result.filename.replace('.pdf', '.data'))
await fs.writeFile(dataFilePath, encryptedData)
} catch (encryptError) {
console.error('Fehler beim Verschlüsseln der Daten:', encryptError.message)
// Continue without encryption for now
}
// Set download token cookie
const timestamp = Date.now()
const downloadToken = Buffer.from(`${result.filename.replace('.pdf', '')}:${timestamp}`).toString('base64')
setCookie(event, 'download_token', downloadToken, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 60 * 60 * 24 // 24 hours
})
// Send email notification
let emailResult = { success: false, message: 'E-Mail konnte nicht gesendet werden' }
try {
emailResult = await sendMembershipEmail(data, filePath)
} catch (emailError) {
console.error('E-Mail-Versand fehlgeschlagen:', emailError.message)
}
// Return success response
return {
success: true,
message: 'Beitrittsformular erfolgreich erstellt und E-Mail gesendet.',
downloadUrl: `/api/membership/download/${result.filename}`,
emailSuccess: emailResult.success,
emailMessage: emailResult.message,
usedTemplate: result.success
}
} catch (error) {
console.error('PDF generation error:', error)
// Return error response
return {
success: false,
error: true,
message: error.message || 'Unbekannter Fehler bei der PDF-Generierung',
url: getRequestURL(event).href,
statusCode: error.statusCode || 500,
statusMessage: error.statusMessage || 'Internal Server Error'
}
}
})