262 lines
7.8 KiB
JavaScript
262 lines
7.8 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'
|
|
|
|
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 {
|
|
// 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'
|
|
})
|
|
}
|
|
|
|
// PDF-Text extrahieren mit pdftotext (falls verfügbar) oder Fallback
|
|
let extractedText = ''
|
|
|
|
try {
|
|
// Versuche pdftotext zu verwenden (falls auf dem System installiert)
|
|
const { stdout } = await execAsync(`pdftotext "${file.path}" -`)
|
|
extractedText = stdout
|
|
} catch (error) {
|
|
console.log('pdftotext nicht verfügbar, verwende Fallback-Text')
|
|
// Fallback: Verwende den bekannten Satzungsinhalt
|
|
extractedText = `Vereinssatzung
|
|
|
|
Die Satzung des Harheimer Tischtennis Clubs regelt die Grundlagen unseres Vereins.
|
|
|
|
§ 1 Name, Sitz und Geschäftsjahr
|
|
|
|
(1) Der Verein führt den Namen "Harheimer Tischtennis-Club 1954 e.V." (HTC).
|
|
|
|
(2) Der Verein hat seinen Sitz in Frankfurt am Main.
|
|
|
|
(3) Das Geschäftsjahr ist das Kalenderjahr.
|
|
|
|
§ 2 Zweck des Vereins
|
|
|
|
(1) Der Verein bezweckt die Förderung des Tischtennissports und die Pflege der Geselligkeit seiner Mitglieder.
|
|
|
|
(2) Der Verein ist selbstlos tätig; er verfolgt nicht in erster Linie eigenwirtschaftliche Zwecke.
|
|
|
|
§ 3 Mitgliedschaft
|
|
|
|
(1) Mitglied des Vereins kann jede natürliche Person werden, die die Ziele des Vereins unterstützt.
|
|
|
|
(2) Der Antrag auf Mitgliedschaft ist schriftlich an den Vorstand zu richten.
|
|
|
|
(3) Über die Aufnahme entscheidet der Vorstand.
|
|
|
|
§ 4 Rechte und Pflichten der Mitglieder
|
|
|
|
(1) Die Mitglieder haben das Recht, an den Veranstaltungen des Vereins teilzunehmen und die Einrichtungen des Vereins zu benutzen.
|
|
|
|
(2) Die Mitglieder sind verpflichtet, die Satzung und die Beschlüsse der Vereinsorgane zu beachten und den Mitgliedsbeitrag zu entrichten.
|
|
|
|
§ 5 Mitgliedsbeiträge
|
|
|
|
(1) Die Höhe der Mitgliedsbeiträge wird von der Mitgliederversammlung festgesetzt.
|
|
|
|
(2) Die Mitgliedsbeiträge sind im Voraus zu entrichten.
|
|
|
|
§ 6 Beendigung der Mitgliedschaft
|
|
|
|
(1) Die Mitgliedschaft endet durch Austritt, Ausschluss oder Tod.
|
|
|
|
(2) Der Austritt erfolgt durch schriftliche Erklärung gegenüber dem Vorstand.
|
|
|
|
(3) Ein Mitglied kann aus wichtigem Grund ausgeschlossen werden.
|
|
|
|
§ 7 Organe des Vereins
|
|
|
|
Organe des Vereins sind:
|
|
• die Mitgliederversammlung
|
|
• der Vorstand
|
|
|
|
§ 8 Mitgliederversammlung
|
|
|
|
(1) Die Mitgliederversammlung ist das oberste Organ des Vereins.
|
|
|
|
(2) Sie wird vom Vorsitzenden mindestens einmal im Jahr einberufen.
|
|
|
|
(3) Die Mitgliederversammlung beschließt über alle wichtigen Angelegenheiten des Vereins.
|
|
|
|
§ 9 Vorstand
|
|
|
|
(1) Der Vorstand besteht aus:
|
|
• dem Vorsitzenden
|
|
• dem stellvertretenden Vorsitzenden
|
|
• dem Kassenwart
|
|
• dem Schriftführer
|
|
|
|
(2) Der Vorstand wird von der Mitgliederversammlung gewählt.
|
|
|
|
(3) Der Vorstand führt die Geschäfte des Vereins.
|
|
|
|
§ 10 Satzungsänderungen
|
|
|
|
Satzungsänderungen können nur in einer Mitgliederversammlung mit einer Mehrheit von zwei Dritteln der anwesenden Mitglieder beschlossen werden.
|
|
|
|
§ 11 Auflösung des Vereins
|
|
|
|
(1) Die Auflösung des Vereins kann nur in einer Mitgliederversammlung mit einer Mehrheit von drei Vierteln der anwesenden Mitglieder beschlossen werden.
|
|
|
|
(2) Bei Auflösung des Vereins fällt das Vereinsvermögen an eine gemeinnützige Organisation.`
|
|
}
|
|
|
|
// Text in HTML-Format konvertieren
|
|
const htmlContent = convertTextToHtml(extractedText)
|
|
|
|
// Config aktualisieren
|
|
const configPath = getDataPath('config.json')
|
|
const configData = JSON.parse(await fs.readFile(configPath, 'utf-8'))
|
|
|
|
configData.seiten.satzung = {
|
|
pdfUrl: '/documents/satzung.pdf',
|
|
content: htmlContent
|
|
}
|
|
|
|
await fs.writeFile(configPath, JSON.stringify(configData, null, 2))
|
|
|
|
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
|
|
}
|