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;