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 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; } 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: 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); return { name: file, size: stats.size, created: stats.birthtime, modified: stats.mtime, path: `/templates/${file}`, webdavUrl: `/webdav/templates/${file}`, msWordUrl: `ms-word:ofe|u|${req.secure ? 'https' : 'http'}://localhost:${req.secure ? HTTPS_PORT : PORT}/webdav/templates/${file}` }; }); res.json({ templates, count: templates.length, webdavAccess: `/webdav/templates/`, uploadInfo: { method: 'PUT', url: `/webdav/templates/dateiname.docx`, contentType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' } }); } 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(', ')}` } } }); }); // Datei-Management Endpoints 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;