OfficeServerJs/server.js
OfficeServer dgsoft 1bd5654f6b Initial commit: DOCX Template Server mit API und Tabellen-Support
-  Node.js/Express Server mit DOCX Template-Verarbeitung
-  Automatische Tag-Erkennung und Demo-Daten-Generierung
-  Tabellen-Unterstützung mit Schleifen-Tags
-  REST-API /api/process-template für externe Integration
-  Web-Oberfläche mit vollständiger Dokumentation
-  SSL-Unterstützung (HTTPS Port 443 öffentlich)
-  Intelligente Spaltenerkennung für Tabellen
-  Detaillierte Statusmeldungen für alle Operationen
-  Flexible Custom-Daten + Auto-Generierung
-  Template- und Dokument-Management APIs
2025-10-04 22:04:25 +02:00

751 lines
30 KiB
JavaScript

const express = require('express');
const Docxtemplater = require('docxtemplater');
const PizZip = require('pizzip');
const fs = require('fs');
const path = require('path');
const multer = require('multer');
const cors = require('cors');
const helmet = require('helmet');
const https = require('https');
const { faker } = require('@faker-js/faker');
const app = express();
const PORT = process.env.PORT || 80;
// Middleware
app.use(helmet());
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Verzeichnisse erstellen
const templateDir = path.join(__dirname, 'templates');
const outputDir = path.join(__dirname, 'documents');
[templateDir, outputDir].forEach(dir => {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
});
// Multer für File Upload
const upload = multer({ dest: 'uploads/' });
// Server-URL für dynamische Generierung
let serverBaseUrl = `http://localhost:${PORT}`;
// Funktion für Statusmeldungen
function logFileCreation(filePath, type = 'Datei', customBaseUrl = null) {
const absolutePath = path.resolve(filePath);
const fileSize = fs.existsSync(filePath) ? fs.statSync(filePath).size : 0;
const timestamp = new Date().toLocaleString('de-DE');
const baseUrl = customBaseUrl || serverBaseUrl;
console.log(`\n🎉 ${type} erfolgreich erstellt!`);
console.log(`📁 Pfad: ${absolutePath}`);
console.log(`📏 Größe: ${(fileSize / 1024).toFixed(2)} KB`);
console.log(`⏰ Zeit: ${timestamp}`);
console.log(`🔗 URL: ${baseUrl}/documents/${path.basename(filePath)}`);
console.log('─'.repeat(60));
return {
path: absolutePath,
size: fileSize,
timestamp: timestamp,
url: `${baseUrl}/documents/${path.basename(filePath)}`
};
}
// Demo-Daten-Generator
class DemoDataGenerator {
static generateData(tags) {
const data = {};
tags.forEach(tag => {
switch (tag.toLowerCase()) {
case 'firma':
case 'company':
case 'unternehmen':
data[tag] = faker.company.name();
break;
case 'vorname':
case 'firstname':
data[tag] = faker.person.firstName();
break;
case 'nachname':
case 'lastname':
case 'name':
data[tag] = faker.person.lastName();
break;
case 'email':
case 'mail':
data[tag] = faker.internet.email();
break;
case 'telefon':
case 'phone':
case 'tel':
data[tag] = faker.phone.number();
break;
case 'adresse':
case 'address':
case 'strasse':
data[tag] = faker.location.streetAddress();
break;
case 'stadt':
case 'city':
data[tag] = faker.location.city();
break;
case 'plz':
case 'zip':
case 'postcode':
data[tag] = faker.location.zipCode();
break;
case 'land':
case 'country':
data[tag] = faker.location.country();
break;
case 'datum':
case 'date':
data[tag] = faker.date.recent().toLocaleDateString('de-DE');
break;
case 'betrag':
case 'amount':
case 'summe':
case 'total':
data[tag] = faker.commerce.price({ min: 100, max: 5000 });
break;
case 'nummer':
case 'number':
case 'id':
data[tag] = faker.string.alphanumeric(8).toUpperCase();
break;
case 'beschreibung':
case 'description':
case 'text':
data[tag] = faker.lorem.paragraphs(2);
break;
case 'website':
case 'url':
data[tag] = faker.internet.url();
break;
case 'product':
case 'produkt':
data[tag] = faker.commerce.productName();
break;
case 'price':
case 'preis':
data[tag] = faker.commerce.price();
break;
default:
if (tag.includes('datum') || tag.includes('date')) {
data[tag] = faker.date.recent().toLocaleDateString('de-DE');
} else if (tag.includes('betrag') || tag.includes('preis') || tag.includes('price')) {
data[tag] = faker.commerce.price();
} else if (tag.includes('name')) {
data[tag] = faker.person.fullName();
} else {
data[tag] = faker.lorem.words(2);
}
}
});
return data;
}
static generateTableData(columns) {
const rowCount = faker.number.int({ min: 3, max: 8 });
const tableData = [];
for (let i = 0; i < rowCount; i++) {
const row = {};
columns.forEach(col => {
if (col.includes('name') || col.includes('bezeichnung')) {
row[col] = faker.commerce.productName();
} else if (col.includes('preis') || col.includes('price') || col.includes('betrag')) {
row[col] = faker.commerce.price();
} else if (col.includes('datum') || col.includes('date')) {
row[col] = faker.date.recent().toLocaleDateString('de-DE');
} else if (col.includes('anzahl') || col.includes('menge') || col.includes('qty')) {
row[col] = faker.number.int({ min: 1, max: 10 });
} else {
row[col] = faker.lorem.words(2);
}
});
tableData.push(row);
}
return tableData;
}
}
// Template-Verarbeitung
class TemplateProcessor {
static extractTags(content) {
const simpleTags = [...content.matchAll(/\{([^#/}]+)\}/g)].map(match => match[1].trim());
const loopTags = [...content.matchAll(/\{#(\w+)\}/g)].map(match => match[1].trim());
return { simpleTags: [...new Set(simpleTags)], loopTags: [...new Set(loopTags)] };
}
static async processTemplate(templatePath, outputPath, customData = null) {
try {
const content = fs.readFileSync(templatePath, 'binary');
const zip = new PizZip(content);
const doc = new Docxtemplater(zip, {
paragraphLoop: true,
linebreaks: true,
});
const xmlContent = zip.files['word/document.xml']?.asText() || '';
const { simpleTags, loopTags } = this.extractTags(xmlContent);
console.log(`📝 Gefundene Tags: ${simpleTags.length} einfache, ${loopTags.length} Schleifen`);
// Daten zusammenstellen: Custom-Daten haben Priorität, dann Auto-Generierung
let data = {};
if (customData && typeof customData === 'object') {
data = { ...customData };
console.log(`🎯 Custom-Daten verwendet: ${Object.keys(customData).length} Felder`);
}
// Fehlende Tags automatisch generieren
const missingTags = simpleTags.filter(tag => !(tag in data));
if (missingTags.length > 0) {
const generatedData = DemoDataGenerator.generateData(missingTags);
data = { ...data, ...generatedData };
console.log(`🤖 Auto-generiert: ${missingTags.length} fehlende Tags`);
}
// Loop-Tags verarbeiten
if (loopTags.length > 0) {
loopTags.forEach(loopTag => {
// Prüfen ob Custom-Daten für diese Tabelle vorhanden sind
if (!data[loopTag]) {
const loopContent = xmlContent.match(new RegExp(`{#${loopTag}}([\\s\\S]*?){/${loopTag}}`, 'g'));
if (loopContent && loopContent[0]) {
const innerContent = loopContent[0].replace(`{#${loopTag}}`, '').replace(`{/${loopTag}}`, '');
const tableColumns = [...innerContent.matchAll(/\{(\w+)\}/g)].map(match => match[1]);
data[loopTag] = DemoDataGenerator.generateTableData(tableColumns);
console.log(`📊 Auto-generierte Tabelle "${loopTag}" mit ${data[loopTag].length} Zeilen`);
}
} else {
console.log(`📋 Custom-Tabelle "${loopTag}" mit ${data[loopTag].length} Zeilen verwendet`);
}
});
}
doc.render(data);
const buf = doc.getZip().generate({
type: 'nodebuffer',
compression: 'DEFLATE',
});
fs.writeFileSync(outputPath, buf);
// Statusmeldung für erstellte Datei
const fileInfo = logFileCreation(outputPath, 'DOCX-Dokument');
return {
success: true,
data: data,
extractedTags: { simpleTags, loopTags },
fileInfo: fileInfo
};
} catch (error) {
console.error('❌ Template Verarbeitung fehlgeschlagen:', error);
return {
success: false,
error: error.message
};
}
}
}
// Routes
// Hauptseite
app.get('/', (req, res) => {
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>DOCX Template Server</title>
<meta charset="utf-8">
<style>
body { font-family: Arial, sans-serif; margin: 40px; background: #f5f5f5; }
.container { max-width: 1000px; background: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
.header { text-align: center; margin-bottom: 30px; }
.section { margin: 20px 0; padding: 20px; border: 1px solid #ddd; border-radius: 8px; background: #fafafa; }
.button { background: #007cba; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; display: inline-block; margin: 5px; border: none; cursor: pointer; font-size: 14px; }
.button:hover { background: #005a87; }
.success { background: #d4edda; color: #155724; padding: 15px; border-radius: 6px; margin: 10px 0; border-left: 4px solid #28a745; }
.info { background: #e7f3ff; color: #0c5460; padding: 15px; border-radius: 6px; margin: 10px 0; border-left: 4px solid #007cba; }
input[type="file"] { margin: 10px 0; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }
.features { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin: 20px 0; }
.feature { padding: 15px; background: white; border-radius: 6px; border: 1px solid #e0e0e0; }
.status { margin: 20px 0; padding: 15px; background: #f8f9fa; border-radius: 6px; border: 1px solid #dee2e6; }
pre { background: #f8f9fa; padding: 15px; border-radius: 5px; overflow-x: auto; font-size: 13px; }
.api-section { margin: 30px 0; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>📄 DOCX Template Server</h1>
<p>Automatische Befüllung von Word-Templates mit intelligenten Demo-Daten</p>
</div>
<div class="section">
<h2>🚀 Template hochladen und verarbeiten</h2>
<form action="/upload-template" method="post" enctype="multipart/form-data">
<input type="file" name="template" accept=".docx" required>
<br>
<input type="submit" value="Template verarbeiten" class="button">
</form>
<div class="info">
<strong>Automatische Tag-Erkennung:</strong> Der Server erkennt automatisch Tags wie {name}, {firma}, {datum} und füllt sie mit passenden Demo-Daten.
</div>
</div>
<div class="features">
<div class="feature">
<h3>📋 Unterstützte Tags</h3>
<ul>
<li>{firma} - Firmenname</li>
<li>{name}, {vorname} - Personendaten</li>
<li>{email}, {telefon} - Kontaktdaten</li>
<li>{datum} - Datumsangaben</li>
<li>{betrag} - Preise und Beträge</li>
</ul>
</div>
<div class="feature">
<h3>🔄 Tabellen & Schleifen</h3>
<ul>
<li>{#items}...{/items} - Wiederholungen</li>
<li>Automatische Tabellenbefüllung</li>
<li>Intelligente Spaltenerkennung</li>
<li>3-8 Zeilen pro Tabelle</li>
</ul>
</div>
</div>
<div class="section api-section">
<h2>🔌 API-Endpunkt: Template befüllen</h2>
<div class="info">
<strong>POST /api/process-template</strong><br>
Befüllt ein vorhandenes Template mit benutzerdefinierten oder auto-generierten Daten.
</div>
<h4>📋 Request Body (JSON):</h4>
<pre>{
"templateName": "mein_template.docx", // Erforderlich: Template aus /templates/
"outputName": "mein_dokument.docx", // Erforderlich: Name der Ausgabedatei
"customData": { // Optional: Eigene Daten
"firma": "Meine Firma GmbH",
"vorname": "Max",
"nachname": "Mustermann",
"email": "max@example.com",
"datum": "04.10.2025",
"betrag": "1500.00"
}
}</pre>
<div class="info">
<strong>💡 Tipp:</strong> Wenn customData nicht alle Tags abdeckt, werden fehlende Tags automatisch mit Demo-Daten befüllt.
</div>
</div>
<div class="section api-section">
<h2>📊 API-Endpunkt: Templates mit Tabellen</h2>
<div class="info">
<strong>POST /api/process-template</strong><br>
Unterstützt auch Tabellen/Schleifen mit benutzerdefinierten Daten.
</div>
<h4>🔄 Tabellen-Tags in Templates:</h4>
<pre>{#items}
Artikel: {items_name}
Preis: {items_preis} EUR
Datum: {items_datum}
{/items}</pre>
<h4>📋 Request mit Tabellendaten:</h4>
<pre>{
"templateName": "rechnung_mit_tabelle.docx",
"outputName": "rechnung_mit_daten.docx",
"customData": {
"firma": "TechSolutions GmbH",
"vorname": "Maria",
"nachname": "Weber",
"email": "maria.weber@techsolutions.de",
"datum": "04.10.2025",
"items": [
{
"items_name": "Webentwicklung",
"items_preis": "2500.00",
"items_datum": "01.10.2025",
"items_beschreibung": "Frontend und Backend Entwicklung"
},
{
"items_name": "Design & UX",
"items_preis": "1200.00",
"items_datum": "02.10.2025",
"items_beschreibung": "User Interface Design"
},
{
"items_name": "Hosting & Support",
"items_preis": "300.00",
"items_datum": "03.10.2025",
"items_beschreibung": "Monatliches Hosting und Support"
}
],
"betrag": "4000.00"
}
}</pre>
<h4>🔧 Automatische Tabellen-Generierung:</h4>
<div class="info">
<strong>Ohne customData für Tabellen:</strong> Werden automatisch 3-8 Zeilen mit passenden Demo-Daten generiert.<br>
<strong>Mit customData:</strong> Ihre Daten werden 1:1 verwendet.<br>
<strong>Gemischt:</strong> Ihre Tabellendaten + Auto-Generierung für fehlende einfache Tags.
</div>
<h4>📝 Beispiel: Nur einfache Daten, Tabelle auto-generiert:</h4>
<pre>{
"templateName": "rechnung_mit_tabelle.docx",
"outputName": "auto_tabelle_rechnung.docx",
"customData": {
"firma": "Mustermann GmbH",
"vorname": "Hans",
"nachname": "Mustermann"
// Tabelle "items" wird automatisch generiert mit 3-8 Zeilen
}
}</pre>
<h4>🎯 Intelligente Spaltenerkennung:</h4>
<ul>
<li><strong>*_name, *_bezeichnung:</strong> Produktnamen</li>
<li><strong>*_preis, *_price, *_betrag:</strong> Preise/Beträge</li>
<li><strong>*_datum, *_date:</strong> Datumsangaben</li>
<li><strong>*_anzahl, *_menge, *_qty:</strong> Mengenangaben</li>
<li><strong>Andere:</strong> Lorem-Ipsum Texte</li>
</ul>
</div>
<div class="section">
<h2>📁 API & Downloads</h2>
<a href="/api/templates" class="button">Templates anzeigen</a>
<a href="/api/documents" class="button">Dokumente anzeigen</a>
<a href="/create-test-template" class="button">Test Template erstellen</a>
<div class="status">
<h4>📊 Server Status</h4>
<p><strong>Port:</strong> ${PORT}</p>
<p><strong>Templates:</strong> ${fs.readdirSync(templateDir).length} Dateien</p>
<p><strong>Dokumente:</strong> ${fs.readdirSync(outputDir).length} Dateien</p>
</div>
</div>
</div>
</body>
</html>
`);
});
// Template Upload und Verarbeitung
app.post('/upload-template', upload.single('template'), async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ error: 'Keine Datei hochgeladen' });
}
const templateName = req.file.originalname;
const templatePath = path.join(templateDir, templateName);
const outputName = templateName.replace('.docx', '_ausgefuellt.docx');
const outputPath = path.join(outputDir, outputName);
console.log(`\n📤 Template Upload: ${templateName}`);
// Template in Templates-Verzeichnis kopieren
fs.copyFileSync(req.file.path, templatePath);
console.log(`📁 Template gespeichert: ${templatePath}`);
// Template verarbeiten
console.log(`⚙️ Verarbeitung gestartet...`);
const result = await TemplateProcessor.processTemplate(templatePath, outputPath);
// Upload-Datei löschen
fs.unlinkSync(req.file.path);
if (result.success) {
const protocol = req.secure ? 'https' : 'http';
const host = req.get('host') || `localhost:${PORT}`;
res.json({
success: true,
message: 'Template erfolgreich verarbeitet',
templateName: templateName,
outputName: outputName,
extractedTags: result.extractedTags,
generatedData: result.data,
fileInfo: result.fileInfo,
urls: {
template: `${protocol}://${host}/templates/${templateName}`,
document: `${protocol}://${host}/documents/${outputName}`,
download: `${protocol}://${host}/download/${outputName}`
}
});
} else {
console.log(`❌ Verarbeitung fehlgeschlagen: ${result.error}`);
res.status(500).json({ error: result.error });
}
} catch (error) {
console.error('❌ Upload Fehler:', error);
res.status(500).json({ error: 'Server Fehler beim Upload' });
}
});
// API-Endpunkt: Template befüllen mit konfigurierbaren Namen
app.post('/api/process-template', async (req, res) => {
try {
const { templateName, outputName, customData } = req.body;
// Validierung
if (!templateName) {
return res.status(400).json({ error: 'Template-Name ist erforderlich' });
}
if (!outputName) {
return res.status(400).json({ error: 'Output-Name ist erforderlich' });
}
// Template-Pfad prüfen
const templatePath = path.join(templateDir, templateName);
if (!fs.existsSync(templatePath)) {
return res.status(404).json({
error: 'Template nicht gefunden',
availableTemplates: fs.readdirSync(templateDir).filter(f => f.endsWith('.docx'))
});
}
// Output-Namen vorbereiten (automatisch .docx hinzufügen wenn nicht vorhanden)
const finalOutputName = outputName.endsWith('.docx') ? outputName : `${outputName}.docx`;
const outputPath = path.join(outputDir, finalOutputName);
console.log(`\n🎯 API Template-Verarbeitung gestartet:`);
console.log(`📋 Template: ${templateName}`);
console.log(`📄 Output: ${finalOutputName}`);
// Template verarbeiten
const result = await TemplateProcessor.processTemplate(templatePath, outputPath, customData);
if (result.success) {
const protocol = req.secure ? 'https' : 'http';
const host = req.get('host') || `localhost:${PORT}`;
res.json({
success: true,
message: 'Template erfolgreich verarbeitet via API',
request: {
templateName: templateName,
outputName: finalOutputName,
customDataProvided: !!customData
},
processing: {
extractedTags: result.extractedTags,
generatedData: result.data
},
fileInfo: result.fileInfo,
urls: {
template: `${protocol}://${host}/templates/${templateName}`,
document: `${protocol}://${host}/documents/${finalOutputName}`,
download: `${protocol}://${host}/download/${finalOutputName}`
}
});
} else {
console.log(`❌ API-Verarbeitung fehlgeschlagen: ${result.error}`);
res.status(500).json({
error: result.error,
templateName: templateName,
outputName: finalOutputName
});
}
} catch (error) {
console.error('❌ API-Verarbeitung Fehler:', error);
res.status(500).json({ error: 'Server Fehler bei API-Verarbeitung' });
}
});
// Statische Dateien servieren
app.use('/templates', express.static(templateDir));
app.use('/documents', express.static(outputDir));
// Download-Route mit Statusmeldung
app.get('/download/:filename', (req, res) => {
const filename = req.params.filename;
const filePath = path.join(outputDir, filename);
if (fs.existsSync(filePath)) {
console.log(`\n⬇️ Download gestartet: ${filename}`);
console.log(`📁 Pfad: ${filePath}`);
console.log(`👤 Client: ${req.ip}`);
console.log(`⏰ Zeit: ${new Date().toLocaleString('de-DE')}`);
res.download(filePath, filename, (err) => {
if (err) {
console.error('❌ Download Fehler:', err);
} else {
console.log(`✅ Download abgeschlossen: ${filename}`);
}
});
} else {
console.log(`❌ Datei nicht gefunden: ${filename}`);
res.status(404).json({ error: 'Datei nicht gefunden' });
}
});
// API Endpunkte
app.get('/api/templates', (req, res) => {
try {
const protocol = req.secure ? 'https' : 'http';
const host = req.get('host') || `localhost:${PORT}`;
const files = fs.readdirSync(templateDir)
.filter(file => file.endsWith('.docx'))
.map(file => {
const filePath = path.join(templateDir, file);
const stats = fs.statSync(filePath);
return {
name: file,
size: stats.size,
created: stats.birthtime,
modified: stats.mtime,
url: `${protocol}://${host}/templates/${file}`
};
});
res.json({ templates: files, count: files.length });
} catch (error) {
res.status(500).json({ error: 'Fehler beim Auflisten der Templates' });
}
});
app.get('/api/documents', (req, res) => {
try {
const protocol = req.secure ? 'https' : 'http';
const host = req.get('host') || `localhost:${PORT}`;
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,
url: `${protocol}://${host}/documents/${file}`,
download: `${protocol}://${host}/download/${file}`
};
});
res.json({ documents: files, count: files.length });
} catch (error) {
res.status(500).json({ error: 'Fehler beim Auflisten der Dokumente' });
}
});
// Test Template erstellen
app.get('/create-test-template', (req, res) => {
try {
const templateName = 'test_template.docx';
const templatePath = path.join(templateDir, templateName);
// Verwende das vorhandene rechnung_template.docx wenn verfügbar
const existingTemplate = path.join(__dirname, 'rechnung_template.docx');
if (fs.existsSync(existingTemplate)) {
fs.copyFileSync(existingTemplate, templatePath);
} else {
// Fallback: Einfaches Template
const PizZip = require('pizzip');
const zip = new PizZip();
zip.file('word/document.xml', `<?xml version="1.0"?>
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:body>
<w:p><w:r><w:t>Test Template</w:t></w:r></w:p>
<w:p><w:r><w:t>Firma: {firma}</w:t></w:r></w:p>
<w:p><w:r><w:t>Name: {vorname} {nachname}</w:t></w:r></w:p>
<w:p><w:r><w:t>E-Mail: {email}</w:t></w:r></w:p>
<w:p><w:r><w:t>Datum: {datum}</w:t></w:r></w:p>
<w:p><w:r><w:t>Betrag: {betrag}</w:t></w:r></w:p>
</w:body>
</w:document>`);
zip.file('[Content_Types].xml', `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="xml" ContentType="application/xml"/>
<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
</Types>`);
zip.file('_rels/.rels', `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
</Relationships>`);
const buffer = zip.generate({ type: 'nodebuffer' });
fs.writeFileSync(templatePath, buffer);
}
// Statusmeldung für Template-Erstellung
const fileInfo = logFileCreation(templatePath, 'Test-Template');
res.json({
success: true,
message: 'Test Template erfolgreich erstellt',
templateName: templateName,
fileInfo: fileInfo,
nextStep: 'Besuchen Sie die Hauptseite und laden das erstellte Template hoch'
});
} catch (error) {
console.error('❌ Test Template Erstellung fehlgeschlagen:', error);
res.status(500).json({ error: 'Fehler beim Erstellen des Test Templates' });
}
});
// SSL-Unterstützung
if (fs.existsSync(path.join(__dirname, '203_cert.pem')) && fs.existsSync(path.join(__dirname, '203_key.pem'))) {
try {
const sslOptions = {
key: fs.readFileSync(path.join(__dirname, '203_key.pem')),
cert: fs.readFileSync(path.join(__dirname, '203_cert.pem'))
};
https.createServer(sslOptions, app).listen(443, '0.0.0.0', () => {
console.log('🔒 HTTPS Server läuft auf Port 443 (öffentlich)');
console.log('🌐 HTTPS Zugang: https://[Ihre-Server-IP]:443');
});
} catch (error) {
console.error('❌ HTTPS Server Fehler:', error.message);
}
}
// HTTP Server starten
app.listen(PORT, () => {
console.log('\n🚀 DOCX Template Server gestartet!');
console.log(`📍 HTTP Server: http://localhost:${PORT}`);
console.log(`📁 Templates Verzeichnis: ${templateDir}`);
console.log(`📁 Output Verzeichnis: ${outputDir}`);
console.log('\n💡 Tipp: Besuchen Sie http://localhost:80 für die Web-Oberfläche');
if (fs.existsSync(path.join(__dirname, '203_cert.pem'))) {
console.log('🔒 HTTPS auch verfügbar: https://localhost:443');
}
console.log('\n📊 Server bereit für Template-Verarbeitung mit Statusmeldungen!');
console.log('─'.repeat(60));
});