- ✅ 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
751 lines
30 KiB
JavaScript
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));
|
|
}); |