Some checks failed
Code Analysis (JS/Vue) / analyze (push) Failing after 49s
This commit removes the PDF text extraction logic and replaces it with a fallback mechanism that retains existing content or provides a neutral message. The configuration update now only sets the PDF path without automatically generating HTML content, improving clarity and maintaining the integrity of the existing data.
179 lines
5.4 KiB
JavaScript
179 lines
5.4 KiB
JavaScript
import multer from 'multer'
|
|
import fs from 'fs/promises'
|
|
import path from 'path'
|
|
import { exec } from 'child_process'
|
|
import { promisify } from 'util'
|
|
import { getUserFromToken, hasAnyRole } from '../../utils/auth.js'
|
|
import { assertPdfMagicHeader } from '../../utils/upload-validation.js'
|
|
|
|
const execAsync = promisify(exec)
|
|
|
|
// Handle both dev and production paths
|
|
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
|
|
// filename is always a hardcoded constant ('satzung.json'), never user input
|
|
const getDataPath = (filename) => {
|
|
const cwd = process.cwd()
|
|
|
|
// In production (.output/server), working dir is .output
|
|
if (cwd.endsWith('.output')) {
|
|
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
|
|
return path.join(cwd, '../server/data', filename)
|
|
}
|
|
|
|
// In development, working dir is project root
|
|
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
|
|
return path.join(cwd, 'server/data', filename)
|
|
}
|
|
|
|
// Multer-Konfiguration für PDF-Uploads
|
|
const storage = multer.diskStorage({
|
|
destination: (req, file, cb) => {
|
|
cb(null, 'public/documents/')
|
|
},
|
|
filename: (req, file, cb) => {
|
|
cb(null, 'satzung.pdf')
|
|
}
|
|
})
|
|
|
|
const upload = multer({
|
|
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) => {
|
|
if (event.method !== 'POST') {
|
|
throw createError({
|
|
statusCode: 405,
|
|
statusMessage: 'Method Not Allowed'
|
|
})
|
|
}
|
|
|
|
let token = getCookie(event, 'auth_token')
|
|
const currentUser = token ? await getUserFromToken(token) : null
|
|
|
|
if (!currentUser) {
|
|
throw createError({
|
|
statusCode: 401,
|
|
statusMessage: 'Nicht authentifiziert'
|
|
})
|
|
}
|
|
|
|
if (!hasAnyRole(currentUser, 'admin', 'vorstand')) {
|
|
throw createError({
|
|
statusCode: 403,
|
|
statusMessage: 'Keine Berechtigung'
|
|
})
|
|
}
|
|
|
|
try {
|
|
await fs.mkdir(path.join(process.cwd(), 'public', 'documents'), { recursive: true })
|
|
|
|
// Multer-Middleware für File-Upload
|
|
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
|
|
if (!file) {
|
|
throw createError({
|
|
statusCode: 400,
|
|
statusMessage: 'Keine PDF-Datei hochgeladen'
|
|
})
|
|
}
|
|
|
|
// Zusätzliche Validierung: Magic-Bytes prüfen (mimetype kann gespooft sein)
|
|
await assertPdfMagicHeader(file.path)
|
|
|
|
// Config aktualisieren: Nur PDF-Pfad setzen, HTML-Inhalt nicht automatisch neu generieren
|
|
const configPath = getDataPath('config.json')
|
|
const configData = JSON.parse(await fs.readFile(configPath, 'utf-8'))
|
|
|
|
if (!configData.seiten) {
|
|
configData.seiten = {}
|
|
}
|
|
|
|
const previousContent = configData.seiten.satzung?.content || ''
|
|
|
|
configData.seiten.satzung = {
|
|
pdfUrl: '/documents/satzung.pdf',
|
|
// Entweder bestehenden Text behalten oder einen neutralen Hinweis setzen
|
|
content: previousContent || '<p>Die gültige Satzung des Harheimer Tischtennis-Club 1954 e. V. steht als PDF zum Download bereit. Die PDF-Fassung ist rechtlich verbindlich.</p>'
|
|
}
|
|
|
|
await fs.writeFile(configPath, JSON.stringify(configData, null, 2), 'utf-8')
|
|
|
|
return {
|
|
success: true,
|
|
message: 'Satzung erfolgreich hochgeladen und verarbeitet',
|
|
pdfUrl: '/documents/satzung.pdf'
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('PDF Upload Error:', error)
|
|
if (error.statusCode) {
|
|
throw error
|
|
}
|
|
throw createError({
|
|
statusCode: 500,
|
|
statusMessage: error.message || 'Fehler beim Verarbeiten der PDF-Datei'
|
|
})
|
|
}
|
|
})
|
|
|
|
// PDF-Text zu HTML konvertieren
|
|
function convertTextToHtml(text) {
|
|
// Text bereinigen und strukturieren
|
|
let html = text
|
|
.replace(/\r\n/g, '\n') // Windows-Zeilenumbrüche normalisieren
|
|
.replace(/\r/g, '\n') // Mac-Zeilenumbrüche normalisieren
|
|
.replace(/\n\s*\n/g, '\n\n') // Mehrfache Zeilenumbrüche reduzieren
|
|
.trim()
|
|
|
|
// Überschriften erkennen und formatieren
|
|
html = html.replace(/^(Vereinssatzung|Satzung)$/gm, '<h1>$1</h1>')
|
|
html = html.replace(/^(§\s*\d+[^§\n]*)$/gm, '<h2>$1</h2>')
|
|
|
|
// Absätze erstellen
|
|
html = html.split('\n\n').map(paragraph => {
|
|
paragraph = paragraph.trim()
|
|
if (!paragraph) return ''
|
|
|
|
// Überschriften nicht als Paragraphen behandeln
|
|
if (paragraph.match(/^<h[1-6]>/) || paragraph.match(/^§\s*\d+/)) {
|
|
return paragraph
|
|
}
|
|
|
|
// Listen erkennen
|
|
if (paragraph.includes('•') || paragraph.includes('-') || paragraph.match(/^\d+\./)) {
|
|
const listItems = paragraph.split(/\n/).map(item => {
|
|
item = item.trim()
|
|
if (item.match(/^[•-]\s/) || item.match(/^\d+\.\s/)) {
|
|
return `<li>${item.replace(/^[•-]\s/, '').replace(/^\d+\.\s/, '')}</li>`
|
|
}
|
|
return `<li>${item}</li>`
|
|
}).join('')
|
|
return `<ul>${listItems}</ul>`
|
|
}
|
|
|
|
// Normale Absätze
|
|
return `<p>${paragraph.replace(/\n/g, '<br>')}</p>`
|
|
}).join('\n')
|
|
|
|
// Mehrfache Zeilenumbrüche entfernen
|
|
html = html.replace(/\n{3,}/g, '\n\n')
|
|
|
|
return html
|
|
}
|