const express = require('express'); const https = require('https'); const fs = require('fs'); const path = require('path'); const os = require('os'); const { createReport } = require('docx-templates'); const cors = require('cors'); const AdmZip = require('adm-zip'); // Für DOCX-Analyse // Alternative Template Engine für echte dynamische Tabellen const Docxtemplater = require('docxtemplater'); const PizZip = require('pizzip'); const app = express(); const PORT = process.env.PORT || 3000; const HTTPS_PORT = process.env.HTTPS_PORT || 3443; const USE_HTTPS = process.env.USE_HTTPS === 'true' || false; // SSL-Zertifikate laden (falls vorhanden) let sslOptions = null; try { const sslKeyPath = path.join(__dirname, 'ssl', 'key.pem'); const sslCertPath = path.join(__dirname, 'ssl', 'cert.pem'); if (fs.existsSync(sslKeyPath) && fs.existsSync(sslCertPath)) { sslOptions = { key: fs.readFileSync(sslKeyPath), cert: fs.readFileSync(sslCertPath) }; console.log('🔒 SSL-Zertifikate gefunden und geladen'); } } catch (error) { console.log('⚠️ SSL-Zertifikate nicht gefunden - läuft nur über HTTP'); } // Helper-Funktion für Base URL function getBaseUrl(req) { const protocol = req.secure || req.headers['x-forwarded-proto'] === 'https' ? 'https' : 'http'; const host = req.get('host'); // Falls kein Host-Header, versuche lokale IP zu ermitteln if (!host) { const port = req.secure ? HTTPS_PORT : PORT; return `${protocol}://localhost:${port}`; } return `${protocol}://${host}`; } // Funktion um verfügbare Netzwerk-Interfaces zu ermitteln function getNetworkInterfaces() { const os = require('os'); const interfaces = os.networkInterfaces(); const addresses = []; for (const name in interfaces) { for (const iface of interfaces[name]) { if (iface.family === 'IPv4' && !iface.internal) { addresses.push(iface.address); } } } return addresses; } // ======================================== // RANDOM DATA GENERATION FUNCTIONS // ======================================== // Zufällige deutsche Namen, Firmen, Städte etc. const randomData = { names: ['Max Mustermann', 'Anna Schmidt', 'Peter Weber', 'Julia Müller', 'Thomas Wagner', 'Sarah Becker', 'Michael Fischer', 'Lisa Hofmann', 'Stefan Meyer', 'Nicole Schulz'], firstNames: ['Max', 'Anna', 'Peter', 'Julia', 'Thomas', 'Sarah', 'Michael', 'Lisa', 'Stefan', 'Nicole', 'David', 'Maria', 'Christian', 'Sandra', 'Daniel'], lastNames: ['Mustermann', 'Schmidt', 'Weber', 'Müller', 'Wagner', 'Becker', 'Fischer', 'Hofmann', 'Meyer', 'Schulz', 'Klein', 'Wolf', 'Neumann', 'Richter'], companies: ['Tech Solutions GmbH', 'Innovative Systems AG', 'Digital Services Ltd', 'Future Concepts GmbH', 'Smart Business Solutions', 'Advanced Technologies', 'Professional Services GmbH', 'Global Partners AG'], cities: ['Berlin', 'München', 'Hamburg', 'Köln', 'Frankfurt', 'Stuttgart', 'Düsseldorf', 'Dortmund', 'Essen', 'Leipzig', 'Bremen', 'Dresden', 'Hannover'], streets: ['Hauptstraße', 'Bahnhofstraße', 'Kirchstraße', 'Gartenstraße', 'Schulstraße', 'Marktplatz', 'Am Markt', 'Lindenstraße', 'Bergstraße', 'Dorfstraße'], emails: ['max.mustermann@beispiel.de', 'info@firma.com', 'kontakt@unternehmen.de', 'service@company.com', 'support@business.de'], products: ['Premium Service', 'Standard Paket', 'Professional Edition', 'Business Solution', 'Enterprise Package', 'Basic Version', 'Advanced Tools', 'Complete Suite'] }; // Funktion zur Generierung zufälliger Daten basierend auf Tag-Namen function generateRandomValue(tagName) { const lowerTag = tagName.toLowerCase(); // Namen if (lowerTag.includes('name') && !lowerTag.includes('firma') && !lowerTag.includes('company')) { return randomChoice(randomData.names); } if (lowerTag.includes('vorname') || lowerTag.includes('firstname')) { return randomChoice(randomData.firstNames); } if (lowerTag.includes('nachname') || lowerTag.includes('lastname')) { return randomChoice(randomData.lastNames); } // Firmen/Unternehmen if (lowerTag.includes('firma') || lowerTag.includes('company') || lowerTag.includes('unternehmen')) { return randomChoice(randomData.companies); } // Orte/Adressen if (lowerTag.includes('stadt') || lowerTag.includes('city') || lowerTag.includes('ort')) { return randomChoice(randomData.cities); } if (lowerTag.includes('straße') || lowerTag.includes('street') || lowerTag.includes('adresse')) { return `${randomChoice(randomData.streets)} ${Math.floor(Math.random() * 200) + 1}`; } if (lowerTag.includes('plz') || lowerTag.includes('postcode') || lowerTag.includes('zip')) { return String(Math.floor(Math.random() * 90000) + 10000); } // Kontakt if (lowerTag.includes('email') || lowerTag.includes('mail')) { return randomChoice(randomData.emails); } if (lowerTag.includes('telefon') || lowerTag.includes('phone') || lowerTag.includes('tel')) { return `+49 ${Math.floor(Math.random() * 900) + 100} ${Math.floor(Math.random() * 9000000) + 1000000}`; } // Datum if (lowerTag.includes('datum') || lowerTag.includes('date')) { const date = new Date(); date.setDate(date.getDate() + Math.floor(Math.random() * 365) - 180); return date.toLocaleDateString('de-DE'); } // Zahlen/Preise if (lowerTag.includes('preis') || lowerTag.includes('price') || lowerTag.includes('betrag') || lowerTag.includes('amount')) { return `${(Math.random() * 9000 + 1000).toFixed(2)} €`; } if (lowerTag.includes('nummer') || lowerTag.includes('number') || lowerTag.includes('nr')) { return String(Math.floor(Math.random() * 90000) + 10000); } if (lowerTag.includes('menge') || lowerTag.includes('anzahl') || lowerTag.includes('quantity')) { return String(Math.floor(Math.random() * 50) + 1); } // Produkte/Services if (lowerTag.includes('produkt') || lowerTag.includes('product') || lowerTag.includes('service') || lowerTag.includes('artikel')) { return randomChoice(randomData.products); } // Beschreibungen if (lowerTag.includes('beschreibung') || lowerTag.includes('description') || lowerTag.includes('text')) { return 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'; } // Default für unbekannte Tags return `Beispiel ${tagName}`; } // Hilfsfunktion für zufällige Auswahl function randomChoice(array) { return array[Math.floor(Math.random() * array.length)]; } // Funktion zur Extraktion von Template-Tags aus DOCX function extractTemplateTagsFromDocx(filePath) { try { const zip = new AdmZip(filePath); const docXml = zip.readAsText('word/document.xml'); // Suche nach ++tag++ Pattern const tagPattern = /\+\+([^+]+)\+\+/g; const tags = new Set(); let match; while ((match = tagPattern.exec(docXml)) !== null) { tags.add(match[1]); } return Array.from(tags); } catch (error) { console.error('Fehler beim Extrahieren der Tags:', error); return []; } } // Funktion zur Generierung von Tabellendaten function generateTableData(tableName, rowCount = 3) { const tableData = []; for (let i = 0; i < rowCount; i++) { if (tableName.toLowerCase().includes('produkt') || tableName.toLowerCase().includes('artikel')) { tableData.push({ pos: i + 1, artikel: randomChoice(randomData.products), beschreibung: 'Hochwertige Qualität und zuverlässige Leistung', menge: Math.floor(Math.random() * 10) + 1, preis: (Math.random() * 500 + 50).toFixed(2) + ' €', gesamt: (Math.random() * 1000 + 100).toFixed(2) + ' €' }); } else if (tableName.toLowerCase().includes('person') || tableName.toLowerCase().includes('mitarbeiter')) { tableData.push({ nr: i + 1, name: randomChoice(randomData.names), position: randomChoice(['Manager', 'Developer', 'Designer', 'Analyst', 'Consultant']), abteilung: randomChoice(['IT', 'Marketing', 'Vertrieb', 'Personal', 'Finanzen']), email: randomChoice(randomData.emails) }); } else if (tableName.toLowerCase().includes('position') || tableName.toLowerCase().includes('rechnung')) { // Rechnungsposten tableData.push({ nr: i + 1, artikel: randomChoice(randomData.products), menge: Math.floor(Math.random() * 10) + 1, einzelpreis: (Math.random() * 500 + 50).toFixed(2) + ' €', gesamtpreis: (Math.random() * 1000 + 100).toFixed(2) + ' €' }); } else if (tableName.toLowerCase().includes('leistung') || tableName.toLowerCase().includes('angebot')) { // Angebots-/Leistungsposten tableData.push({ nr: i + 1, titel: randomChoice(randomData.products), beschreibung: 'Professionelle Dienstleistung mit hoher Qualität und termingerechter Umsetzung', aufwand: `${Math.floor(Math.random() * 40) + 5} Stunden`, preis: (Math.random() * 2000 + 500).toFixed(2) + ' €' }); } else if (tableName.toLowerCase().includes('aufgaben') || tableName.toLowerCase().includes('task')) { // Aufgaben/Tasks tableData.push({ nr: i + 1, titel: randomChoice(['Website Design', 'API Development', 'Testing', 'Documentation', 'Code Review']), status: randomChoice(['In Bearbeitung', 'Abgeschlossen', 'Geplant', 'Blockiert']), deadline: new Date(Date.now() + Math.random() * 30 * 24 * 60 * 60 * 1000).toLocaleDateString('de-DE') }); } else { // Standard Tabelle tableData.push({ nr: i + 1, bezeichnung: `Eintrag ${i + 1}`, wert: (Math.random() * 1000).toFixed(2), status: randomChoice(['Aktiv', 'Inaktiv', 'Pending', 'Abgeschlossen']) }); } } return tableData; } app.use(cors()); app.use(express.json()); app.use(express.static('public')); // Template-Ordner erstellen falls nicht vorhanden const templatesDir = path.join(__dirname, 'templates'); const outputDir = path.join(__dirname, 'output'); if (!fs.existsSync(templatesDir)) { fs.mkdirSync(templatesDir, { recursive: true }); } if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } // Einfache WebDAV-ähnliche Datei-Routen (ohne Authentifizierung) app.use('/webdav/output', express.static(outputDir, { setHeaders: (res, path) => { res.set('DAV', '1'); res.set('Allow', 'GET, PUT, DELETE, PROPFIND, MKCOL'); res.set('MS-Author-Via', 'DAV'); } })); app.use('/webdav/templates', express.static(templatesDir, { setHeaders: (res, path) => { res.set('DAV', '1'); res.set('Allow', 'GET, PUT, DELETE, PROPFIND, MKCOL'); res.set('MS-Author-Via', 'DAV'); } })); // WebDAV PUT Support für Template-Upload app.put('/webdav/templates/:filename', express.raw({limit: '50mb', type: '*/*'}), (req, res) => { try { const filename = req.params.filename; const filePath = path.join(templatesDir, filename); // Nur DOCX-Dateien erlauben if (!filename.endsWith('.docx')) { return res.status(400).send('Nur DOCX-Dateien sind erlaubt'); } fs.writeFileSync(filePath, req.body); console.log(`📄 Template hochgeladen: ${filename}`); res.status(201).send('Template erfolgreich hochgeladen'); } catch (error) { console.error('Template-Upload Fehler:', error); res.status(500).send('Fehler beim Hochladen des Templates'); } }); // WebDAV PUT Support für Output-Dateien (falls bearbeitet) app.put('/webdav/output/:filename', express.raw({limit: '50mb', type: '*/*'}), (req, res) => { try { const filename = req.params.filename; const filePath = path.join(outputDir, filename); fs.writeFileSync(filePath, req.body); console.log(`📄 Output-Datei aktualisiert: ${filename}`); res.status(200).send('Datei erfolgreich aktualisiert'); } catch (error) { console.error('Datei-Update Fehler:', error); res.status(500).send('Fehler beim Aktualisieren der Datei'); } }); // WebDAV DELETE Support für Templates app.delete('/webdav/templates/:filename', (req, res) => { try { const filename = req.params.filename; const filePath = path.join(templatesDir, filename); if (!fs.existsSync(filePath)) { return res.status(404).send('Template nicht gefunden'); } fs.unlinkSync(filePath); console.log(`🗑️ Template gelöscht: ${filename}`); res.status(204).send(); } catch (error) { console.error('Template-Lösch Fehler:', error); res.status(500).send('Fehler beim Löschen des Templates'); } }); // WebDAV DELETE Support für Output-Dateien app.delete('/webdav/output/:filename', (req, res) => { try { const filename = req.params.filename; const filePath = path.join(outputDir, filename); if (!fs.existsSync(filePath)) { return res.status(404).send('Datei nicht gefunden'); } fs.unlinkSync(filePath); console.log(`🗑️ Output-Datei gelöscht: ${filename}`); res.status(204).send(); } catch (error) { console.error('Datei-Lösch Fehler:', error); res.status(500).send('Fehler beim Löschen der Datei'); } }); // WebDAV PROPFIND für Templates-Listing app.use('/webdav/templates', (req, res, next) => { if (req.method === 'PROPFIND') { try { const files = fs.readdirSync(templatesDir); const xmlResponse = ` ${files.map(file => { const filePath = path.join(templatesDir, file); const stats = fs.statSync(filePath); return ` /webdav/templates/${file} ${file} ${stats.size} ${stats.mtime.toUTCString()} application/vnd.openxmlformats-officedocument.wordprocessingml.document HTTP/1.1 200 OK `; }).join('')} `; res.set('Content-Type', 'application/xml; charset=utf-8'); res.status(207).send(xmlResponse); } catch (error) { res.status(500).send('Server Error'); } } else { next(); } }); app.use('/webdav/output', (req, res, next) => { if (req.method === 'PROPFIND') { try { const files = fs.readdirSync(outputDir); const xmlResponse = ` ${files.map(file => { const filePath = path.join(outputDir, file); const stats = fs.statSync(filePath); return ` /webdav/output/${file} ${file} ${stats.size} ${stats.mtime.toUTCString()} HTTP/1.1 200 OK `; }).join('')} `; res.set('Content-Type', 'application/xml; charset=utf-8'); res.status(207).send(xmlResponse); } catch (error) { res.status(500).send('Server Error'); } } else { next(); } }); // Route: DOCX mit Daten befüllen app.post('/generate-document', async (req, res) => { try { const { templateName, data } = req.body; if (!templateName || !data) { return res.status(400).json({ error: 'Template-Name und Daten sind erforderlich' }); } const templatePath = path.join(templatesDir, templateName); if (!fs.existsSync(templatePath)) { return res.status(404).json({ error: 'Template nicht gefunden' }); } // Template laden const template = fs.readFileSync(templatePath); // Dokument mit Daten befüllen const buffer = await createReport({ template, data: data, cmdDelimiter: ['++', '++'], // Andere Syntax verwenden }); // Eindeutigen Dateinamen erstellen const timestamp = Date.now(); const outputFileName = `document_${timestamp}.docx`; const outputPath = path.join(outputDir, outputFileName); // Befülltes Dokument speichern fs.writeFileSync(outputPath, buffer); // Download-Link zurückgeben res.json({ success: true, downloadUrl: `/download/${outputFileName}`, webdavUrl: `/webdav/output/${outputFileName}`, msWordUrl: `ms-word:ofe|u|${getBaseUrl(req)}/webdav/output/${outputFileName}`, message: 'Dokument erfolgreich erstellt' }); } catch (error) { console.error('Fehler beim Erstellen des Dokuments:', error); res.status(500).json({ error: 'Fehler beim Erstellen des Dokuments', details: error.message }); } }); // Route: Template mit automatischen Test-Daten befüllen app.post('/test-template/:templateName', async (req, res) => { try { const templateName = req.params.templateName; const { rowCount = 3 } = req.body; // Anzahl Tabellenzeilen if (!templateName.endsWith('.docx')) { return res.status(400).json({ error: 'Nur DOCX-Templates werden unterstützt' }); } const templatePath = path.join(templatesDir, templateName); if (!fs.existsSync(templatePath)) { return res.status(404).json({ error: 'Template nicht gefunden' }); } // Template-Tags extrahieren const templateTags = extractTemplateTagsFromDocx(templatePath); console.log(`📋 Gefundene Template-Tags in ${templateName}:`, templateTags); // Automatische Test-Daten generieren const testData = {}; const tableNames = []; templateTags.forEach(tag => { if (tag.includes('[') && tag.includes(']')) { // Array/Tabellen-Tag gefunden (z.B. items[0].product) const arrayMatch = tag.match(/([^[]+)\[/); if (arrayMatch) { const arrayName = arrayMatch[1]; if (!tableNames.includes(arrayName)) { tableNames.push(arrayName); } } } else if (tag.startsWith('$') || tag.includes('.')) { // Andere Tabellen-Syntax (z.B. $tabelle oder tabelle.artikel) const tableName = tag.split('.')[0].replace('$', ''); if (!tableNames.includes(tableName)) { tableNames.push(tableName); } } else { // Einfacher Platzhalter testData[tag] = generateRandomValue(tag); } }); // Tabellendaten generieren für erkannte Arrays tableNames.forEach(tableName => { if (tableName === 'items') { // Spezielle Behandlung für items Array testData[tableName] = []; for (let i = 0; i < rowCount; i++) { testData[tableName].push({ product: randomChoice(randomData.products), quantity: Math.floor(Math.random() * 10) + 1, price: (Math.random() * 500 + 50).toFixed(2) + ' €' }); } } else if (tableName === 'mitarbeiterRows') { testData[tableName] = generateTableData('mitarbeiter', rowCount); } else if (tableName === 'aufgabenRows') { testData[tableName] = generateTableData('aufgaben', rowCount); } else { // Für Arrays - erweitere auf bis zu 10 Elemente const tableData = generateTableData(tableName, rowCount); // Erstelle ein Array mit 10 Elementen const expandedArray = Array.from({length: 10}, (_, index) => { if (index < rowCount && tableData[index]) { // Echte Daten für die gewünschte Anzahl return tableData[index]; } else { // Leere/unsichtbare Daten für den Rest return { nr: '', name: '', position: '', email: '', abteilung: '', titel: '', status: '', deadline: '', artikel: '', menge: '', preis: '', einzelpreis: '', gesamtpreis: '', beschreibung: '', wert: '', aufwand: '' }; } }); testData[tableName] = expandedArray; } }); console.log(`🎲 Generierte Test-Daten:`, testData); // Template laden const template = fs.readFileSync(templatePath); // Dokument mit Test-Daten befüllen const buffer = await createReport({ template, data: testData, cmdDelimiter: ['++', '++'], }); // Eindeutigen Dateinamen erstellen const timestamp = Date.now(); const outputFileName = `test_${templateName.replace('.docx', '')}_${timestamp}.docx`; const outputPath = path.join(outputDir, outputFileName); // Befülltes Test-Dokument speichern fs.writeFileSync(outputPath, buffer); // Response mit Details über gefundene Tags und generierte Daten res.json({ success: true, templateName, foundTags: templateTags, tableNames, generatedData: testData, downloadUrl: `/download/${outputFileName}`, webdavUrl: `/webdav/output/${outputFileName}`, msWordUrl: `ms-word:ofe|u|${getBaseUrl(req)}/webdav/output/${outputFileName}`, message: `Test-Dokument erfolgreich erstellt mit ${templateTags.length} Tags und ${tableNames.length} Tabellen` }); } catch (error) { console.error('Fehler beim Erstellen des Test-Dokuments:', error); res.status(500).json({ error: 'Fehler beim Erstellen des Test-Dokuments', details: error.message }); } }); // Route: One-Click Demo - Analyse + Demo-Dokument in einem Schritt app.post('/demo-template/:templateName', async (req, res) => { try { const templateName = req.params.templateName; const { rowCount = 3 } = req.body; if (!templateName.endsWith('.docx')) { return res.status(400).json({ error: 'Nur DOCX-Templates werden unterstützt' }); } const templatePath = path.join(templatesDir, templateName); if (!fs.existsSync(templatePath)) { return res.status(404).json({ error: 'Template nicht gefunden' }); } // SCHRITT 1: Template analysieren const templateTags = extractTemplateTagsFromDocx(templatePath); console.log(`🔍 Demo-Analyse für ${templateName}:`, templateTags); // Tags kategorisieren const simpleTags = []; const tableTags = []; const tableNames = new Set(); templateTags.forEach(tag => { if (tag.includes('[') && tag.includes(']')) { tableTags.push(tag); const arrayMatch = tag.match(/([^[]+)\[/); if (arrayMatch) { tableNames.add(arrayMatch[1]); } } else if (tag.startsWith('$') || tag.includes('.')) { tableTags.push(tag); const tableName = tag.split('.')[0].replace('$', ''); tableNames.add(tableName); } else { simpleTags.push(tag); } }); // SCHRITT 2: Demo-Daten generieren const demoData = {}; // Einfache Tags befüllen simpleTags.forEach(tag => { demoData[tag] = generateRandomValue(tag); }); // Tabellen-Daten generieren Array.from(tableNames).forEach(tableName => { if (tableName === 'items') { demoData[tableName] = []; for (let i = 0; i < rowCount; i++) { demoData[tableName].push({ product: randomChoice(randomData.products), quantity: Math.floor(Math.random() * 10) + 1, price: (Math.random() * 500 + 50).toFixed(2) + ' €' }); } } else if (tableName === 'mitarbeiterRows') { demoData[tableName] = generateTableData('mitarbeiter', rowCount); } else if (tableName === 'aufgabenRows') { demoData[tableName] = generateTableData('aufgaben', rowCount); } else { // Für Arrays wie 'mitarbeiter' - erweitere auf bis zu 10 Elemente const tableData = generateTableData(tableName, rowCount); // Erstelle ein Array mit 10 Elementen const expandedArray = Array.from({length: 10}, (_, index) => { if (index < rowCount && tableData[index]) { // Echte Daten für die gewünschte Anzahl return tableData[index]; } else { // Leere/unsichtbare Daten für den Rest return { nr: '', name: '', position: '', email: '', abteilung: '', titel: '', status: '', deadline: '', artikel: '', menge: '', preis: '', einzelpreis: '', gesamtpreis: '', beschreibung: '', wert: '', aufwand: '' }; } }); demoData[tableName] = expandedArray; } }); console.log(`🎲 Generierte Demo-Daten:`, demoData); // SCHRITT 3: Demo-Dokument erstellen const template = fs.readFileSync(templatePath); const buffer = await createReport({ template, data: demoData, cmdDelimiter: ['++', '++'], }); const timestamp = Date.now(); const outputFileName = `demo_${templateName.replace('.docx', '')}_${timestamp}.docx`; const outputPath = path.join(outputDir, outputFileName); fs.writeFileSync(outputPath, buffer); // SCHRITT 4: Detaillierte Analyse-Response const analysisResult = { // Template-Info templateInfo: { name: templateName, size: fs.statSync(templatePath).size, path: templatePath, totalTags: templateTags.length }, // Tag-Analyse tagAnalysis: { foundTags: templateTags, simpleTags, tableTags, tableNames: Array.from(tableNames), tagBreakdown: { simple: simpleTags.length, tables: tableNames.size, total: templateTags.length } }, // Generierte Demo-Daten demoData, // Download-Links demoDocument: { filename: outputFileName, downloadUrl: `/download/${outputFileName}`, webdavUrl: `/webdav/output/${outputFileName}`, msWordUrl: `ms-word:ofe|u|${getBaseUrl(req)}/webdav/output/${outputFileName}` }, // Erfolgs-Status success: true, message: `✅ Demo erstellt: ${templateTags.length} Tags analysiert, ${tableNames.size} Tabellen befüllt, ${rowCount} Zeilen pro Tabelle`, // Zeitstempel created: new Date().toISOString() }; res.json(analysisResult); } catch (error) { console.error('Fehler beim Erstellen der Demo:', error); res.status(500).json({ error: 'Fehler beim Erstellen der Demo', details: error.message }); } }); // Route: Template-Tags analysieren (ohne Dokument zu erstellen) app.get('/analyze-template/:templateName', (req, res) => { try { const templateName = req.params.templateName; if (!templateName.endsWith('.docx')) { return res.status(400).json({ error: 'Nur DOCX-Templates werden unterstützt' }); } const templatePath = path.join(templatesDir, templateName); if (!fs.existsSync(templatePath)) { return res.status(404).json({ error: 'Template nicht gefunden' }); } // Template-Tags extrahieren const templateTags = extractTemplateTagsFromDocx(templatePath); // Tags kategorisieren const simpleTags = []; const tableTags = []; const tableNames = new Set(); templateTags.forEach(tag => { if (tag.includes('[') && tag.includes(']')) { // Array/Tabellen-Tag gefunden (z.B. items[0].product) tableTags.push(tag); const arrayMatch = tag.match(/([^[]+)\[/); if (arrayMatch) { tableNames.add(arrayMatch[1]); } } else if (tag.startsWith('$') || tag.includes('.')) { // Andere Tabellen-Syntax tableTags.push(tag); const tableName = tag.split('.')[0].replace('$', ''); tableNames.add(tableName); } else { simpleTags.push(tag); } }); // Beispiel-Daten generieren (ohne Dokument zu erstellen) const exampleData = {}; simpleTags.forEach(tag => { exampleData[tag] = generateRandomValue(tag); }); Array.from(tableNames).forEach(tableName => { if (tableName === 'items') { // Spezielle Behandlung für items Array exampleData[tableName] = [ { product: randomChoice(randomData.products), quantity: Math.floor(Math.random() * 10) + 1, price: (Math.random() * 500 + 50).toFixed(2) + ' €' }, { product: randomChoice(randomData.products), quantity: Math.floor(Math.random() * 10) + 1, price: (Math.random() * 500 + 50).toFixed(2) + ' €' } ]; } else { exampleData[tableName] = generateTableData(tableName, 2); // Nur 2 Beispielzeilen } }); res.json({ templateName, totalTags: templateTags.length, simpleTags, tableTags, tableNames: Array.from(tableNames), exampleData, usage: { testTemplate: `POST /test-template/${templateName}`, generateDocument: `POST /generate-document`, webdavEdit: `ms-word:ofe|u|${getBaseUrl(req)}/webdav/templates/${templateName}` } }); } catch (error) { console.error('Fehler beim Analysieren des Templates:', error); res.status(500).json({ error: 'Fehler beim Analysieren des Templates', details: error.message }); } }); // Route: Dokument herunterladen app.get('/download/:filename', (req, res) => { const filename = req.params.filename; const filePath = path.join(outputDir, filename); if (!fs.existsSync(filePath)) { return res.status(404).json({ error: 'Datei nicht gefunden' }); } res.download(filePath, filename, (err) => { if (err) { console.error('Download-Fehler:', err); res.status(500).json({ error: 'Download-Fehler' }); } }); }); // Route: Verfügbare Templates auflisten (erweitert) app.get('/templates', (req, res) => { try { const templates = fs.readdirSync(templatesDir) .filter(file => file.endsWith('.docx')) .map(file => { const filePath = path.join(templatesDir, file); const stats = fs.statSync(filePath); const baseUrl = getBaseUrl(req); return { name: file, size: stats.size, created: stats.birthtime, modified: stats.mtime, path: `/templates/${file}`, webdavUrl: `/webdav/templates/${file}`, msWordUrl: `ms-word:ofe|u|${baseUrl}/webdav/templates/${file}`, // Neue Analyse- und Test-Links analyzeUrl: `/analyze-template/${file}`, testUrl: `/test-template/${file}`, demoUrl: `/demo-template/${file}`, actions: { analyze: { method: 'GET', url: `/analyze-template/${file}`, description: 'Template analysieren und alle Tags anzeigen' }, test: { method: 'POST', url: `/test-template/${file}`, description: 'Test-Dokument mit Zufallsdaten erstellen', body: { rowCount: 3 } }, demo: { method: 'POST', url: `/demo-template/${file}`, description: 'One-Click Demo: Analyse + Demo-Dokument erstellen', body: { rowCount: 3 } }, generate: { method: 'POST', url: '/generate-document', description: 'Dokument mit eigenen Daten erstellen', body: { templateName: file, data: {} } } } }; }); res.json({ templates, count: templates.length, webdavAccess: `/webdav/templates/`, uploadInfo: { method: 'PUT', url: `/webdav/templates/dateiname.docx`, contentType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }, templateActions: { analyze: { description: 'Template analysieren - alle Tags und Beispieldaten anzeigen', method: 'GET', url: '/analyze-template/{templateName}', example: '/analyze-template/simple-template.docx' }, test: { description: 'Test-Dokument mit Zufallsdaten erstellen', method: 'POST', url: '/test-template/{templateName}', body: { rowCount: 3 }, example: '/test-template/simple-template.docx' }, demo: { description: '🚀 One-Click Demo: Analyse + Demo-Dokument in einem Schritt', method: 'POST', url: '/demo-template/{templateName}', body: { rowCount: 3 }, example: '/demo-template/simple-template.docx', features: ['Vollständige Template-Analyse', 'Automatische Demo-Daten', 'Sofortiges Demo-Dokument', 'Detaillierte Analyse-Response'] }, generate: { description: 'Dokument mit eigenen Daten erstellen', method: 'POST', url: '/generate-document', body: { templateName: 'template.docx', data: {} } } } }); } catch (error) { res.status(500).json({ error: 'Fehler beim Auflisten der Templates' }); } }); // Template-Management Endpoints app.delete('/templates/:filename', (req, res) => { try { const filename = req.params.filename; const filePath = path.join(templatesDir, filename); if (!fs.existsSync(filePath)) { return res.status(404).json({ error: 'Template nicht gefunden' }); } fs.unlinkSync(filePath); res.json({ success: true, message: `Template ${filename} gelöscht` }); } catch (error) { res.status(500).json({ error: 'Fehler beim Löschen des Templates' }); } }); // Template-Info Endpoint app.get('/templates/:filename/info', (req, res) => { try { const filename = req.params.filename; const filePath = path.join(templatesDir, filename); if (!fs.existsSync(filePath)) { return res.status(404).json({ error: 'Template nicht gefunden' }); } const stats = fs.statSync(filePath); res.json({ name: filename, size: stats.size, created: stats.birthtime, modified: stats.mtime, webdavUrl: `/webdav/templates/${filename}`, msWordUrl: `ms-word:ofe|u|http://localhost:${PORT}/webdav/templates/${filename}`, downloadUrl: `/webdav/templates/${filename}` }); } catch (error) { res.status(500).json({ error: 'Fehler beim Abrufen der Template-Informationen' }); } }); // Beispiel-Route: Daten von externem Service abrufen app.get('/external-data/:id', async (req, res) => { try { const id = req.params.id; // Beispiel-Daten für Demonstration const exampleData = { id: id, name: 'Max Mustermann', email: 'max@beispiel.de', date: new Date().toLocaleDateString('de-DE'), items: [ { product: 'Produkt A', quantity: 2, price: 29.99 }, { product: 'Produkt B', quantity: 1, price: 49.99 }, { product: 'Produkt C', quantity: 3, price: 19.99 } ], total: 129.95 }; res.json(exampleData); } catch (error) { res.status(500).json({ error: 'Fehler beim Abrufen der externen Daten' }); } }); // Kombinierte Route: Externe Daten abrufen und Dokument erstellen app.post('/generate-from-external/:id', async (req, res) => { try { const id = req.params.id; const { templateName } = req.body; // Externe Daten abrufen (simulation) const externalData = { id: id, name: 'Max Mustermann', email: 'max@beispiel.de', date: new Date().toLocaleDateString('de-DE'), items: [ { product: 'Produkt A', quantity: 2, price: 29.99 }, { product: 'Produkt B', quantity: 1, price: 49.99 }, { product: 'Produkt C', quantity: 3, price: 19.99 } ], total: 129.95 }; // Template-Pfad const templatePath = path.join(templatesDir, templateName); if (!fs.existsSync(templatePath)) { return res.status(404).json({ error: 'Template nicht gefunden' }); } // Template laden und befüllen const template = fs.readFileSync(templatePath); const buffer = await createReport({ template, data: externalData, cmdDelimiter: ['++', '++'], }); // Datei speichern const timestamp = Date.now(); const outputFileName = `document_${id}_${timestamp}.docx`; const outputPath = path.join(outputDir, outputFileName); fs.writeFileSync(outputPath, buffer); res.json({ success: true, downloadUrl: `/download/${outputFileName}`, webdavUrl: `/webdav/output/${outputFileName}`, msWordUrl: `ms-word:ofe|u|${getBaseUrl(req)}/webdav/output/${outputFileName}`, data: externalData, message: 'Dokument mit externen Daten erstellt' }); } catch (error) { console.error('Fehler:', error); res.status(500).json({ error: 'Fehler beim Erstellen des Dokuments mit externen Daten' }); } }); // Health Check app.get('/health', (req, res) => { const networkIPs = getNetworkInterfaces(); res.json({ status: 'OK', message: 'DOCX Template Server läuft', webdav: 'WebDAV-ähnliche Datei-Routen verfügbar', ssl: req.secure ? 'HTTPS aktiv' : 'HTTP (unverschlüsselt)', access: { local: req.secure ? `https://localhost:${HTTPS_PORT}` : `http://localhost:${PORT}`, network: networkIPs.map(ip => req.secure ? `https://${ip}:${HTTPS_PORT}` : `http://${ip}:${PORT}`) }, timestamp: new Date().toISOString() }); }); // WebDAV Info Endpoint app.get('/webdav-info', (req, res) => { const baseUrl = req.query.baseUrl || getBaseUrl(req); const networkIPs = getNetworkInterfaces(); const protocol = req.secure ? 'https' : 'http'; const port = req.secure ? HTTPS_PORT : PORT; res.json({ webdav: { status: 'Verfügbar (ohne Authentifizierung)', baseUrl: baseUrl, ssl: req.secure ? 'HTTPS aktiv' : 'HTTP (unverschlüsselt)', endpoints: { templates: `${baseUrl}/webdav/templates/`, output: `${baseUrl}/webdav/output/` }, alternativeURLs: { localhost: { templates: `${protocol}://localhost:${port}/webdav/templates/`, output: `${protocol}://localhost:${port}/webdav/output/` }, network: networkIPs.reduce((acc, ip) => { acc[ip] = { templates: `${protocol}://${ip}:${port}/webdav/templates/`, output: `${protocol}://${ip}:${port}/webdav/output/` }; return acc; }, {}) }, msWordFormat: { templates: `ms-word:ofe|u|${baseUrl}/webdav/templates/dateiname.docx`, output: `ms-word:ofe|u|${baseUrl}/webdav/output/dateiname.docx` }, credentials: 'Keine Authentifizierung erforderlich', instructions: { access: 'Direkte HTTP/HTTPS-Zugriffe oder MS Word Links', msWord: 'Verwenden Sie ms-word:ofe|u|URL Format für direktes Öffnen in Word', ssl: req.secure ? 'Sichere HTTPS-Verbindung aktiv' : 'Für Netzwerkzugriff HTTPS empfohlen', example: `ms-word:ofe|u|${baseUrl}/webdav/output/beispiel.docx`, networkAccess: `Server erreichbar über: localhost, ${networkIPs.join(', ')}` } } }); }); // Route: Echte dynamische Templates mit docxtemplater app.post('/dynamic-template/:templateName', async (req, res) => { try { const templateName = req.params.templateName; const { tables = {}, data = {} } = req.body; console.log(`🔄 Dynamisches Template mit docxtemplater: ${templateName}`); // Template laden const templatePath = path.join(templatesDir, templateName); if (!fs.existsSync(templatePath)) { return res.status(404).json({ error: `Template ${templateName} nicht gefunden` }); } const content = fs.readFileSync(templatePath, 'binary'); const zip = new PizZip(content); const doc = new Docxtemplater(zip, { paragraphLoop: true, linebreaks: true, }); // Dynamische Daten generieren const templateData = { ...data }; // Einfache Tags befüllen templateData.projekt = templateData.projekt || 'Dynamisches Projekt'; templateData.datum = templateData.datum || new Date().toLocaleDateString('de-DE'); templateData.ersteller = templateData.ersteller || 'Automatisch generiert'; templateData.status = templateData.status || 'Aktiv'; // Tabellen-Arrays generieren Object.entries(tables).forEach(([tableName, rowCount]) => { console.log(`📊 Generiere ${rowCount} Zeilen für Tabelle: ${tableName}`); templateData[tableName] = generateTableData(tableName, parseInt(rowCount)); }); console.log(`🎲 Template-Daten:`, { ...templateData, tableCount: Object.keys(tables).length }); // Template rendern doc.render(templateData); const buffer = doc.getZip().generate({ type: 'nodebuffer', compression: 'DEFLATE', }); // Datei speichern const timestamp = Date.now(); const outputFileName = `dynamic_${templateName.replace('.docx', '')}_${timestamp}.docx`; const outputPath = path.join(outputDir, outputFileName); fs.writeFileSync(outputPath, buffer); res.json({ success: true, message: `✅ Dynamisches Template erstellt: ${Object.keys(tables).map(t => `${t} (${tables[t]} Zeilen)`).join(', ')}`, file: outputFileName, download: `/download/${outputFileName}`, webdav: { http: `http://localhost:3000/webdav/output/${outputFileName}`, https: `https://localhost:3443/webdav/output/${outputFileName}` }, data: templateData }); } catch (error) { console.error('Fehler bei dynamischem Template:', error); res.status(500).json({ error: 'Fehler beim Erstellen des dynamischen Templates', details: error.message }); } }); app.get('/files/output', (req, res) => { try { const files = fs.readdirSync(outputDir) .filter(file => file.endsWith('.docx')) .map(file => { const filePath = path.join(outputDir, file); const stats = fs.statSync(filePath); return { name: file, size: stats.size, created: stats.birthtime, modified: stats.mtime, webdavUrl: `/webdav/output/${file}`, msWordUrl: `ms-word:ofe|u|${req.secure ? 'https' : 'http'}://localhost:${req.secure ? HTTPS_PORT : PORT}/webdav/output/${file}`, downloadUrl: `/download/${file}` }; }) .sort((a, b) => b.modified - a.modified); res.json({ files, count: files.length, webdavAccess: `/webdav/output/` }); } catch (error) { res.status(500).json({ error: 'Fehler beim Auflisten der Dateien' }); } }); app.delete('/files/output/:filename', (req, res) => { try { const filename = req.params.filename; const filePath = path.join(outputDir, filename); if (!fs.existsSync(filePath)) { return res.status(404).json({ error: 'Datei nicht gefunden' }); } fs.unlinkSync(filePath); res.json({ success: true, message: `Datei ${filename} gelöscht` }); } catch (error) { res.status(500).json({ error: 'Fehler beim Löschen der Datei' }); } }); // Server starten function startServer() { const networkIPs = getNetworkInterfaces(); // HTTP Server starten const httpServer = app.listen(PORT, '0.0.0.0', () => { console.log(`🚀 DOCX Template Server (HTTP) läuft auf Port ${PORT}`); console.log(`📁 Templates-Ordner: ${templatesDir}`); console.log(`📄 Output-Ordner: ${outputDir}`); console.log(`🌐 Zugriff über:`); console.log(` http://localhost:${PORT}`); networkIPs.forEach(ip => { console.log(` http://${ip}:${PORT}`); }); console.log(`📂 WebDAV URLs (HTTP):`); console.log(` Templates: http://localhost:${PORT}/webdav/templates/`); console.log(` Output: http://localhost:${PORT}/webdav/output/`); }); // HTTPS Server starten (falls SSL-Zertifikate vorhanden) if (sslOptions) { const httpsServer = https.createServer(sslOptions, app); httpsServer.listen(HTTPS_PORT, '0.0.0.0', () => { console.log(`🔒 DOCX Template Server (HTTPS) läuft auf Port ${HTTPS_PORT}`); console.log(`🌐 Sichere Zugriff über:`); console.log(` https://localhost:${HTTPS_PORT}`); networkIPs.forEach(ip => { console.log(` https://${ip}:${HTTPS_PORT}`); }); console.log(`📂 WebDAV URLs (HTTPS):`); console.log(` Templates: https://localhost:${HTTPS_PORT}/webdav/templates/`); console.log(` Output: https://localhost:${HTTPS_PORT}/webdav/output/`); console.log(`🔐 SSL-verschlüsselte Verbindung aktiv`); }); return { http: httpServer, https: httpsServer }; } else { console.log(`⚠️ Nur HTTP verfügbar - für SSL Zertifikate in ssl/ Ordner bereitstellen`); console.log(`💡 Verwende './start.sh ssl-gen' um SSL-Zertifikate zu erstellen`); return { http: httpServer }; } } const servers = startServer(); module.exports = app;