OfficeServerJs/server.js
OfficeServer dgsoft 88932bfbf4 🎛️ Web-GUI Management System mit Custom Tags
 Neue Features:
- Vollständiges Management Dashboard (Port 3000)
- Custom Tags System mit REST-API Integration
- Mehrere Tags erstellbar und konfigurierbar
- Externe Tag-Aktivierung per REST-API
- Server-Fernsteuerung (Start/Stop/Restart)
- SSL-Zertifikat Management
- Echtzeit-Überwachung mit Socket.IO

🏷️ Custom Tags Features:
- Dynamische Tag-Erstellung über GUI
- Tag-Aktivierung/Deaktivierung per Toggle
- REST-APIs für externe Tag-Kontrolle
- Integration in Template-Verarbeitung
- Konfigurierbare Positionen und Typen

📁 Neue Dateien:
- management/ - Komplettes Management-System
- API-TAGS-DOCUMENTATION.md - API Dokumentation
- start-management.sh - Startup Script

🔧 Verbesserte Template-Verarbeitung:
- Automatisches Laden aktivierter Custom Tags
- Priorität: Custom-Daten → Custom Tags → Auto-Generierung
- Erweiterte Logging und Status-Meldungen

🌐 REST-APIs:
- GET /api/public/tags - Alle Tags auflisten
- POST /api/public/tags/{TAG_NAME}/activate - Tag aktivieren
- POST /api/public/tags/{TAG_NAME}/deactivate - Tag deaktivieren
- Management APIs für vollständige CRUD-Operationen
2025-10-05 20:51:30 +02:00

811 lines
33 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
// Management-System Integration (falls verfügbar)
let DataSourceManager, CustomTagProcessor;
try {
const managementModules = require('./management/data-sources');
DataSourceManager = managementModules.DataSourceManager;
CustomTagProcessor = managementModules.CustomTagProcessor;
console.log('✅ Management-System Integration geladen');
} catch (error) {
console.log(' Management-System nicht verfügbar (optional)');
}
// Konfiguration laden
function loadManagementConfig() {
try {
const configPath = path.join(__dirname, 'management', 'config.json');
if (fs.existsSync(configPath)) {
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
}
} catch (error) {
console.log(' Management-Konfiguration nicht verfügbar');
}
return null;
}
const managementConfig = loadManagementConfig();
// 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 {
// Custom Tags aus Management-System laden
static loadCustomTags() {
try {
const configPath = path.join(__dirname, 'management', 'config.json');
if (!fs.existsSync(configPath)) {
console.log('📝 Keine Management-Konfiguration gefunden');
return {};
}
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
const customTags = config.customTags || [];
const activeTagsData = {};
customTags
.filter(tag => tag.enabled) // Nur aktive Tags
.forEach(tag => {
activeTagsData[tag.tagName] = tag.content || '';
console.log(`✅ Custom Tag aktiviert: ${tag.tagName} = "${tag.content}"`);
});
return activeTagsData;
} catch (error) {
console.log('⚠️ Fehler beim Laden der Custom Tags:', error.message);
return {};
}
}
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`);
// Custom Tags aus Management-System laden
const customTagsData = this.loadCustomTags();
console.log(`🏷️ Custom Tags geladen: ${Object.keys(customTagsData).length} aktive Tags`);
// Daten zusammenstellen: Custom-Daten haben Priorität, dann Custom Tags, dann Auto-Generierung
let data = {};
if (customData && typeof customData === 'object') {
data = { ...customData };
console.log(`🎯 Custom-Daten verwendet: ${Object.keys(customData).length} Felder`);
}
// Custom Tags hinzufügen
data = { ...data, ...customTagsData };
// 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));
});