Add 'Spielplan' links to Navigation component; update index page to include 'Spielplan' section; enhance 'spielplaene' page with filtering, loading states, and error handling for improved user experience.
This commit is contained in:
@@ -16,7 +16,8 @@ export default defineEventHandler(async (event) => {
|
||||
const allowedFiles = [
|
||||
'vereinsmeisterschaften.csv',
|
||||
'mannschaften.csv',
|
||||
'termine.csv'
|
||||
'termine.csv',
|
||||
'spielplan.csv'
|
||||
]
|
||||
|
||||
if (!allowedFiles.includes(filename)) {
|
||||
|
||||
102
server/api/cms/upload-spielplan-pdf.post.js
Normal file
102
server/api/cms/upload-spielplan-pdf.post.js
Normal file
@@ -0,0 +1,102 @@
|
||||
import multer from 'multer'
|
||||
import fs from 'fs/promises'
|
||||
import path from 'path'
|
||||
|
||||
// Multer-Konfiguration für PDF-Uploads
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
const uploadPath = path.join(process.cwd(), 'public', 'documents', 'spielplaene')
|
||||
cb(null, uploadPath)
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
const type = req.body.type
|
||||
const filename = `spielplan_${type}.pdf`
|
||||
cb(null, filename)
|
||||
}
|
||||
})
|
||||
|
||||
const upload = multer({
|
||||
storage: storage,
|
||||
fileFilter: (req, file, cb) => {
|
||||
if (file.mimetype === 'application/pdf') {
|
||||
cb(null, true)
|
||||
} else {
|
||||
cb(new Error('Nur PDF-Dateien sind erlaubt'), false)
|
||||
}
|
||||
},
|
||||
limits: {
|
||||
fileSize: 10 * 1024 * 1024 // 10MB Limit
|
||||
}
|
||||
})
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
// Prüfe Authentifizierung
|
||||
const authHeader = getHeader(event, 'authorization')
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
throw createError({
|
||||
statusCode: 401,
|
||||
statusMessage: 'Nicht autorisiert'
|
||||
})
|
||||
}
|
||||
|
||||
// Multer-Middleware für multipart/form-data
|
||||
await new Promise((resolve, reject) => {
|
||||
upload.single('pdf')(event.node.req, event.node.res, (err) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const file = event.node.req.file
|
||||
const type = event.node.req.body.type
|
||||
|
||||
if (!file) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Keine Datei hochgeladen'
|
||||
})
|
||||
}
|
||||
|
||||
if (!type || !['gesamt', 'erwachsene', 'nachwuchs'].includes(type)) {
|
||||
// Lösche die hochgeladene Datei
|
||||
await fs.unlink(file.path)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Ungültiger Typ'
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `PDF für ${type} erfolgreich hochgeladen`,
|
||||
filename: file.filename,
|
||||
originalName: file.originalname
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim PDF-Upload:', error)
|
||||
|
||||
if (error.code === 'LIMIT_FILE_SIZE') {
|
||||
throw createError({
|
||||
statusCode: 413,
|
||||
statusMessage: 'Datei zu groß (max. 10MB)'
|
||||
})
|
||||
}
|
||||
|
||||
if (error.message === 'Nur PDF-Dateien sind erlaubt') {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Nur PDF-Dateien sind erlaubt'
|
||||
})
|
||||
}
|
||||
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Fehler beim Hochladen der PDF-Datei'
|
||||
})
|
||||
}
|
||||
})
|
||||
61
server/api/spielplan.get.js
Normal file
61
server/api/spielplan.get.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import fs from 'fs/promises'
|
||||
import path from 'path'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
const filePath = path.join(process.cwd(), 'public', 'data', 'spielplan.csv')
|
||||
|
||||
// Prüfe ob Datei existiert
|
||||
try {
|
||||
await fs.access(filePath)
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Spielplan-Datei nicht gefunden',
|
||||
data: []
|
||||
}
|
||||
}
|
||||
|
||||
// CSV-Datei lesen
|
||||
const csvContent = await fs.readFile(filePath, 'utf-8')
|
||||
const lines = csvContent.split('\n').filter(line => line.trim() !== '')
|
||||
|
||||
if (lines.length < 2) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Spielplan-Datei ist leer oder unvollständig',
|
||||
data: []
|
||||
}
|
||||
}
|
||||
|
||||
// Header-Zeile parsen
|
||||
const headers = lines[0].split(';').map(header => header.trim())
|
||||
|
||||
// Datenzeilen parsen
|
||||
const data = lines.slice(1).map(line => {
|
||||
const values = line.split(';').map(value => value.trim())
|
||||
const row = {}
|
||||
|
||||
headers.forEach((header, index) => {
|
||||
row[header] = values[index] || ''
|
||||
})
|
||||
|
||||
return row
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Spielplan erfolgreich geladen',
|
||||
data: data,
|
||||
headers: headers
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden des Spielplans:', error)
|
||||
return {
|
||||
success: false,
|
||||
message: 'Fehler beim Laden des Spielplans',
|
||||
data: []
|
||||
}
|
||||
}
|
||||
})
|
||||
114
server/api/spielplan/download/[filename].get.js
Normal file
114
server/api/spielplan/download/[filename].get.js
Normal file
@@ -0,0 +1,114 @@
|
||||
import fs from 'fs/promises'
|
||||
import path from 'path'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
const filename = getRouterParam(event, 'filename')
|
||||
|
||||
if (!filename) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Dateiname fehlt'
|
||||
})
|
||||
}
|
||||
|
||||
// Erlaubte Dateinamen für Sicherheit
|
||||
const allowedFiles = [
|
||||
'spielplan_gesamt.pdf',
|
||||
'spielplan_erwachsene.pdf',
|
||||
'spielplan_nachwuchs.pdf',
|
||||
'spielplan_erwachsene_1.pdf',
|
||||
'spielplan_erwachsene_2.pdf',
|
||||
'spielplan_erwachsene_3.pdf',
|
||||
'spielplan_erwachsene_4.pdf',
|
||||
'spielplan_erwachsene_5.pdf',
|
||||
'spielplan_jugendmannschaft.pdf'
|
||||
]
|
||||
|
||||
// Prüfe ob es eine dynamische Mannschafts-PDF ist
|
||||
const isDynamicMannschaft = filename.startsWith('spielplan_') && filename.endsWith('.pdf') &&
|
||||
!allowedFiles.includes(filename)
|
||||
|
||||
if (!allowedFiles.includes(filename) && !isDynamicMannschaft) {
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: 'Datei nicht erlaubt'
|
||||
})
|
||||
}
|
||||
|
||||
let filePath
|
||||
|
||||
if (isDynamicMannschaft) {
|
||||
// Für dynamische Mannschafts-PDFs: Verwende Gesamt-Spielplan als Fallback
|
||||
// Hier könnte später ein PDF-Generator implementiert werden
|
||||
filePath = path.join(process.cwd(), 'public', 'documents', 'spielplaene', 'spielplan_gesamt.pdf')
|
||||
} else {
|
||||
// Für vordefinierte PDFs
|
||||
filePath = path.join(process.cwd(), 'public', 'documents', 'spielplaene', filename)
|
||||
}
|
||||
|
||||
// Prüfe ob Datei existiert
|
||||
try {
|
||||
await fs.access(filePath)
|
||||
} catch (error) {
|
||||
// Fallback: Erstelle eine informative HTML-Seite
|
||||
const htmlContent = `
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>PDF nicht verfügbar</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 40px; background-color: #f5f5f5; }
|
||||
.container { background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
|
||||
.error { color: #d32f2f; font-size: 24px; margin-bottom: 20px; }
|
||||
.message { color: #333; font-size: 16px; line-height: 1.6; }
|
||||
.action { margin-top: 20px; padding: 15px; background: #e3f2fd; border-radius: 4px; }
|
||||
.link { color: #1976d2; text-decoration: none; font-weight: bold; }
|
||||
.link:hover { text-decoration: underline; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="error">📄 PDF-Datei nicht verfügbar</div>
|
||||
<div class="message">
|
||||
Die angeforderte PDF-Datei <strong>"${filename}"</strong> wurde noch nicht hochgeladen.
|
||||
</div>
|
||||
<div class="action">
|
||||
<strong>Was können Sie tun?</strong><br>
|
||||
• Laden Sie die PDF-Datei über das <a href="/cms/spielplaene" class="link">CMS</a> hoch<br>
|
||||
• Kontaktieren Sie den Administrator<br>
|
||||
• Versuchen Sie es später erneut
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
setHeader(event, 'Content-Type', 'text/html; charset=utf-8')
|
||||
return htmlContent
|
||||
}
|
||||
|
||||
// Datei lesen
|
||||
const fileBuffer = await fs.readFile(filePath)
|
||||
|
||||
// Content-Type setzen
|
||||
setHeader(event, 'Content-Type', 'application/pdf')
|
||||
setHeader(event, 'Content-Disposition', `attachment; filename="${filename}"`)
|
||||
setHeader(event, 'Content-Length', fileBuffer.length.toString())
|
||||
|
||||
return fileBuffer
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der PDF-Datei:', error)
|
||||
|
||||
if (error.statusCode) {
|
||||
throw error
|
||||
}
|
||||
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Fehler beim Laden der PDF-Datei'
|
||||
})
|
||||
}
|
||||
})
|
||||
352
server/api/spielplan/pdf.get.js
Normal file
352
server/api/spielplan/pdf.get.js
Normal file
@@ -0,0 +1,352 @@
|
||||
import fs from 'fs/promises'
|
||||
import path from 'path'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
const query = getQuery(event)
|
||||
const team = query.team
|
||||
|
||||
if (!team) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Team-Parameter fehlt'
|
||||
})
|
||||
}
|
||||
|
||||
// Lade Spielplandaten
|
||||
const csvPath = path.join(process.cwd(), 'public/data/spielplan.csv')
|
||||
let csvContent
|
||||
|
||||
try {
|
||||
csvContent = await fs.readFile(csvPath, 'utf-8')
|
||||
} catch (error) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: 'Spielplandaten nicht gefunden'
|
||||
})
|
||||
}
|
||||
|
||||
// Parse CSV
|
||||
const lines = csvContent.split('\n').filter(line => line.trim())
|
||||
if (lines.length < 2) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Keine Spielplandaten verfügbar'
|
||||
})
|
||||
}
|
||||
|
||||
const headers = lines[0].split(';')
|
||||
const dataRows = lines.slice(1).map(line => {
|
||||
const values = line.split(';')
|
||||
const row = {}
|
||||
headers.forEach((header, index) => {
|
||||
row[header] = values[index] || ''
|
||||
})
|
||||
return row
|
||||
})
|
||||
|
||||
// Filtere Daten basierend auf Team
|
||||
let filteredData = dataRows
|
||||
|
||||
if (team !== 'all') {
|
||||
filteredData = dataRows.filter(row => {
|
||||
const heimMannschaft = (row.HeimMannschaft || '').toLowerCase()
|
||||
const gastMannschaft = (row.GastMannschaft || '').toLowerCase()
|
||||
const heimAltersklasse = (row.HeimMannschaftAltersklasse || '').toLowerCase()
|
||||
const gastAltersklasse = (row.GastMannschaftAltersklasse || '').toLowerCase()
|
||||
|
||||
// Prüfe ob eine der Mannschaften Harheimer TC ist
|
||||
const isHarheimerHeim = heimMannschaft.includes('harheimer tc')
|
||||
const isHarheimerGast = gastMannschaft.includes('harheimer tc')
|
||||
|
||||
if (!isHarheimerHeim && !isHarheimerGast) {
|
||||
return false // Kein Harheimer TC Spiel
|
||||
}
|
||||
|
||||
// Mapping zwischen Team-Namen und CSV-Daten
|
||||
const mannschaftMapping = {
|
||||
'erwachsene': ['harheimer tc'], // Alle Erwachsenen-Mannschaften
|
||||
'nachwuchs': ['harheimer tc'], // Alle Jugend-Mannschaften
|
||||
'erwachsene_1': ['harheimer tc'], // Nur ohne römische Zahl
|
||||
'erwachsene_2': ['harheimer tc ii'],
|
||||
'erwachsene_3': ['harheimer tc iii'],
|
||||
'erwachsene_4': ['harheimer tc iv'],
|
||||
'erwachsene_5': ['harheimer tc v'],
|
||||
'jugendmannschaft': ['harheimer tc'] // Jugend hat keine römische Zahl
|
||||
}
|
||||
|
||||
const csvVariants = mannschaftMapping[team] || []
|
||||
|
||||
if (csvVariants.length === 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Prüfe Mannschafts-Zuordnung UND Altersklasse
|
||||
const mannschaftMatch = csvVariants.some(variant => {
|
||||
// Strikte Übereinstimmung: Prüfe exakte Mannschaftsnamen
|
||||
if (isHarheimerHeim) {
|
||||
// Für "harheimer tc" (Erwachsene 1): Nur wenn KEINE römische Zahl folgt
|
||||
if (variant === 'harheimer tc') {
|
||||
return heimMannschaft === 'harheimer tc' ||
|
||||
heimMannschaft.startsWith('harheimer tc ') &&
|
||||
!heimMannschaft.match(/harheimer tc\s+[ivx]+/i)
|
||||
}
|
||||
// Für andere Mannschaften: Exakte Übereinstimmung
|
||||
return heimMannschaft === variant || heimMannschaft.startsWith(variant + ' ')
|
||||
}
|
||||
if (isHarheimerGast) {
|
||||
// Für "harheimer tc" (Erwachsene 1): Nur wenn KEINE römische Zahl folgt
|
||||
if (variant === 'harheimer tc') {
|
||||
return gastMannschaft === 'harheimer tc' ||
|
||||
gastMannschaft.startsWith('harheimer tc ') &&
|
||||
!gastMannschaft.match(/harheimer tc\s+[ivx]+/i)
|
||||
}
|
||||
// Für andere Mannschaften: Exakte Übereinstimmung
|
||||
return gastMannschaft === variant || gastMannschaft.startsWith(variant + ' ')
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
if (!mannschaftMatch) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Zusätzliche Altersklassen-Prüfung für spezifische Mannschaften
|
||||
if (team.startsWith('erwachsene')) {
|
||||
// Erwachsenen-Mannschaften: MUSS Erwachsene sein, DARF NICHT Jugend sein
|
||||
const isErwachsenenHeim = isHarheimerHeim &&
|
||||
heimAltersklasse.includes('erwachsene') &&
|
||||
!heimAltersklasse.includes('jugend')
|
||||
const isErwachsenenGast = isHarheimerGast &&
|
||||
gastAltersklasse.includes('erwachsene') &&
|
||||
!gastAltersklasse.includes('jugend')
|
||||
|
||||
return isErwachsenenHeim || isErwachsenenGast
|
||||
} else if (team === 'jugendmannschaft' || team === 'nachwuchs') {
|
||||
// Jugend-Mannschaft: MUSS Jugend sein
|
||||
const isJugendHeim = isHarheimerHeim &&
|
||||
(heimAltersklasse.includes('jugend') || heimMannschaft.includes('jugend'))
|
||||
const isJugendGast = isHarheimerGast &&
|
||||
(gastAltersklasse.includes('jugend') || gastMannschaft.includes('jugend'))
|
||||
|
||||
return isJugendHeim || isJugendGast
|
||||
}
|
||||
|
||||
return true // Fallback für unbekannte Mannschaften
|
||||
})
|
||||
}
|
||||
|
||||
// Filtere nach Wettbewerb (Standard: Punktrunde)
|
||||
const wettbewerb = query.wettbewerb || 'punktrunde'
|
||||
if (wettbewerb !== 'alle') {
|
||||
filteredData = filteredData.filter(row => {
|
||||
const runde = (row.Runde || '').toLowerCase()
|
||||
|
||||
if (wettbewerb === 'punktrunde') {
|
||||
return runde === 'vr' ||
|
||||
runde.includes('vorrunde') ||
|
||||
runde === 'rr' ||
|
||||
runde.includes('rückrunde') ||
|
||||
runde.includes('verbandsrunde')
|
||||
} else if (wettbewerb === 'pokal') {
|
||||
return runde === 'pokal' || runde.includes('pokal')
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// Filtere nach aktueller Saison (2025/26)
|
||||
const currentSaisonStart = new Date(2025, 6, 1) // 01.07.2025
|
||||
const currentSaisonEnd = new Date(2026, 5, 30) // 30.06.2026
|
||||
|
||||
filteredData = filteredData.filter(row => {
|
||||
const termin = row.Termin
|
||||
if (!termin) return false
|
||||
|
||||
try {
|
||||
let spielDatum
|
||||
|
||||
if (termin.includes(' ')) {
|
||||
const datumTeil = termin.split(' ')[0]
|
||||
const [tag, monat, jahr] = datumTeil.split('.')
|
||||
spielDatum = new Date(jahr, monat - 1, tag)
|
||||
} else {
|
||||
spielDatum = new Date(termin)
|
||||
}
|
||||
|
||||
if (isNaN(spielDatum.getTime())) return false
|
||||
|
||||
return spielDatum >= currentSaisonStart && spielDatum <= currentSaisonEnd
|
||||
} catch (error) {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
// Generiere LaTeX-Code für PDF
|
||||
const teamName = team.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())
|
||||
const currentDate = new Date().toLocaleDateString('de-DE')
|
||||
|
||||
let latexContent = `
|
||||
\\documentclass[9pt,a4paper]{article}
|
||||
\\usepackage[utf8]{inputenc}
|
||||
\\usepackage[ngerman]{babel}
|
||||
\\usepackage{geometry}
|
||||
\\usepackage{array}
|
||||
\\usepackage{longtable}
|
||||
\\usepackage{helvet}
|
||||
\\renewcommand{\\familydefault}{\\sfdefault}
|
||||
|
||||
\\geometry{margin=2cm}
|
||||
|
||||
\\title{Spielplan ${teamName}}
|
||||
\\author{Harheimer TC}
|
||||
\\date{Saison 2025/26 - Stand: ${currentDate}}
|
||||
|
||||
\\begin{document}
|
||||
\\maketitle
|
||||
|
||||
\\section*{Spielplan ${teamName}}
|
||||
\\vspace{0.5cm}
|
||||
|
||||
${filteredData.length > 0 ? `
|
||||
\\renewcommand{\\arraystretch}{1.3}
|
||||
\\begin{longtable}{|p{1.8cm}|p{1.1cm}|p{6.5cm}|p{6.5cm}|}
|
||||
\\hline
|
||||
\\textbf{Datum} & \\textbf{Uhrzeit} & \\textbf{Heim} & \\textbf{Gast} \\\\
|
||||
\\hline
|
||||
\\endhead
|
||||
|
||||
${filteredData.map(row => {
|
||||
const termin = row.Termin || ''
|
||||
const datum = termin.includes(' ') ? termin.split(' ')[0] : termin
|
||||
const uhrzeit = termin.includes(' ') ? termin.split(' ')[1] : ''
|
||||
const heim = (row.HeimMannschaft || '').replace(/&/g, '\\&')
|
||||
const gast = (row.GastMannschaft || '').replace(/&/g, '\\&')
|
||||
const heimAltersklasse = (row.HeimMannschaftAltersklasse || '').toLowerCase()
|
||||
const gastAltersklasse = (row.GastMannschaftAltersklasse || '').toLowerCase()
|
||||
|
||||
// Prüfe ob es Jugend-Mannschaften sind
|
||||
const isHeimJugend = heimAltersklasse.includes('jugend') || heim.toLowerCase().includes('jugend')
|
||||
const isGastJugend = gastAltersklasse.includes('jugend') || gast.toLowerCase().includes('jugend')
|
||||
|
||||
// Füge "(J) " vor Jugendmannschaften hinzu (nur bei Gesamtspielplan)
|
||||
let heimFormatted = heim
|
||||
let gastFormatted = gast
|
||||
|
||||
if (team === 'all') {
|
||||
if (isHeimJugend) {
|
||||
heimFormatted = `(J) ${heim}`
|
||||
}
|
||||
if (isGastJugend) {
|
||||
gastFormatted = `(J) ${gast}`
|
||||
}
|
||||
}
|
||||
|
||||
const heimBold = heimFormatted.toLowerCase().includes('harheimer tc') ? `\\textbf{${heimFormatted}}` : heimFormatted
|
||||
const gastBold = gastFormatted.toLowerCase().includes('harheimer tc') ? `\\textbf{${gastFormatted}}` : gastFormatted
|
||||
|
||||
return `${datum} & ${uhrzeit} & ${heimBold} & ${gastBold} \\\\ \\hline`
|
||||
}).join('\n')}
|
||||
|
||||
\\end{longtable}
|
||||
` : `
|
||||
\\begin{center}
|
||||
\\textit{Keine Spiele für ${teamName} gefunden.}
|
||||
\\end{center}
|
||||
`}
|
||||
|
||||
\\vfill
|
||||
\\begin{center}
|
||||
\\small Generiert am ${currentDate} | Harheimer TC
|
||||
\\end{center}
|
||||
|
||||
\\end{document}`
|
||||
|
||||
// Schreibe LaTeX-Datei temporär
|
||||
const tempDir = path.join(process.cwd(), 'temp')
|
||||
try {
|
||||
await fs.mkdir(tempDir, { recursive: true })
|
||||
} catch (error) {
|
||||
// Verzeichnis existiert bereits
|
||||
}
|
||||
|
||||
const tempTexFile = path.join(tempDir, `spielplan_${team}_${Date.now()}.tex`)
|
||||
await fs.writeFile(tempTexFile, latexContent, 'utf-8')
|
||||
|
||||
// Kompiliere LaTeX zu PDF
|
||||
const { exec } = await import('child_process')
|
||||
const { promisify } = await import('util')
|
||||
const execAsync = promisify(exec)
|
||||
|
||||
try {
|
||||
const { stdout, stderr } = await execAsync(`pdflatex -interaction=nonstopmode -output-directory=${tempDir} "${tempTexFile}"`)
|
||||
|
||||
// PDF-Datei lesen
|
||||
const pdfFile = tempTexFile.replace('.tex', '.pdf')
|
||||
const pdfBuffer = await fs.readFile(pdfFile)
|
||||
|
||||
// Cleanup: Lösche temporäre Dateien
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
await fs.unlink(tempTexFile)
|
||||
await fs.unlink(pdfFile)
|
||||
await fs.unlink(tempTexFile.replace('.tex', '.log'))
|
||||
await fs.unlink(tempTexFile.replace('.tex', '.aux'))
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Löschen temporärer Dateien:', error)
|
||||
}
|
||||
}, 5000)
|
||||
|
||||
// Setze PDF-Headers
|
||||
setHeader(event, 'Content-Type', 'application/pdf')
|
||||
setHeader(event, 'Content-Disposition', `attachment; filename="spielplan_${team}.pdf"`)
|
||||
setHeader(event, 'Content-Length', pdfBuffer.length.toString())
|
||||
|
||||
return pdfBuffer
|
||||
|
||||
} catch (compileError) {
|
||||
console.error('LaTeX-Kompilierung fehlgeschlagen:', compileError)
|
||||
|
||||
// Fallback: HTML-Response mit Fehlermeldung
|
||||
const errorHtml = `
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>PDF-Generierung fehlgeschlagen</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 40px; background-color: #f5f5f5; }
|
||||
.container { background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
|
||||
.error { color: #d32f2f; font-size: 24px; margin-bottom: 20px; }
|
||||
.message { color: #333; font-size: 16px; line-height: 1.6; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="error">❌ PDF-Generierung fehlgeschlagen</div>
|
||||
<div class="message">
|
||||
Die PDF-Generierung für <strong>"${teamName}"</strong> ist fehlgeschlagen.<br>
|
||||
Bitte kontaktieren Sie den Administrator.
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
setHeader(event, 'Content-Type', 'text/html; charset=utf-8')
|
||||
return errorHtml
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Generieren des Spielplans:', error)
|
||||
|
||||
if (error.statusCode) {
|
||||
throw error
|
||||
}
|
||||
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Fehler beim Generieren des Spielplans'
|
||||
})
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user