✅ Migration completed: - server.js: Complete rewrite using docxtemplater + pizzip - server-old.js: Backup of original docx-templates implementation - package.json: Added docxtemplater and pizzip dependencies - Removed docx-templates dependency 🚀 New features: - True dynamic tables with {#array}{field}{/array} syntax - Unlimited table rows (no more fixed array indices) - New API endpoints: /analyze, /generate, /demo - Simplified template creation scripts 🎯 Template improvements: - Updated template syntax examples - Added dynamic-template.docx with loop syntax - Enhanced template analysis functionality 📊 Benefits: - Real dynamic table generation - Better template flexibility - Cleaner API design - Preserved SSL and WebDAV functionality
1373 lines
51 KiB
JavaScript
1373 lines
51 KiB
JavaScript
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 AdmZip = require('adm-zip'); // Für DOCX-Analyse
|
|
|
|
// Alternative Template Engine für echte dynamische Tabellen
|
|
const Docxtemplater = require('docxtemplater');
|
|
const PizZip = require('pizzip');
|
|
|
|
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;
|
|
}
|
|
|
|
// ========================================
|
|
// RANDOM DATA GENERATION FUNCTIONS
|
|
// ========================================
|
|
|
|
// Zufällige deutsche Namen, Firmen, Städte etc.
|
|
const randomData = {
|
|
names: ['Max Mustermann', 'Anna Schmidt', 'Peter Weber', 'Julia Müller', 'Thomas Wagner', 'Sarah Becker', 'Michael Fischer', 'Lisa Hofmann', 'Stefan Meyer', 'Nicole Schulz'],
|
|
firstNames: ['Max', 'Anna', 'Peter', 'Julia', 'Thomas', 'Sarah', 'Michael', 'Lisa', 'Stefan', 'Nicole', 'David', 'Maria', 'Christian', 'Sandra', 'Daniel'],
|
|
lastNames: ['Mustermann', 'Schmidt', 'Weber', 'Müller', 'Wagner', 'Becker', 'Fischer', 'Hofmann', 'Meyer', 'Schulz', 'Klein', 'Wolf', 'Neumann', 'Richter'],
|
|
companies: ['Tech Solutions GmbH', 'Innovative Systems AG', 'Digital Services Ltd', 'Future Concepts GmbH', 'Smart Business Solutions', 'Advanced Technologies', 'Professional Services GmbH', 'Global Partners AG'],
|
|
cities: ['Berlin', 'München', 'Hamburg', 'Köln', 'Frankfurt', 'Stuttgart', 'Düsseldorf', 'Dortmund', 'Essen', 'Leipzig', 'Bremen', 'Dresden', 'Hannover'],
|
|
streets: ['Hauptstraße', 'Bahnhofstraße', 'Kirchstraße', 'Gartenstraße', 'Schulstraße', 'Marktplatz', 'Am Markt', 'Lindenstraße', 'Bergstraße', 'Dorfstraße'],
|
|
emails: ['max.mustermann@beispiel.de', 'info@firma.com', 'kontakt@unternehmen.de', 'service@company.com', 'support@business.de'],
|
|
products: ['Premium Service', 'Standard Paket', 'Professional Edition', 'Business Solution', 'Enterprise Package', 'Basic Version', 'Advanced Tools', 'Complete Suite']
|
|
};
|
|
|
|
// Funktion zur Generierung zufälliger Daten basierend auf Tag-Namen
|
|
function generateRandomValue(tagName) {
|
|
const lowerTag = tagName.toLowerCase();
|
|
|
|
// Namen
|
|
if (lowerTag.includes('name') && !lowerTag.includes('firma') && !lowerTag.includes('company')) {
|
|
return randomChoice(randomData.names);
|
|
}
|
|
if (lowerTag.includes('vorname') || lowerTag.includes('firstname')) {
|
|
return randomChoice(randomData.firstNames);
|
|
}
|
|
if (lowerTag.includes('nachname') || lowerTag.includes('lastname')) {
|
|
return randomChoice(randomData.lastNames);
|
|
}
|
|
|
|
// Firmen/Unternehmen
|
|
if (lowerTag.includes('firma') || lowerTag.includes('company') || lowerTag.includes('unternehmen')) {
|
|
return randomChoice(randomData.companies);
|
|
}
|
|
|
|
// Orte/Adressen
|
|
if (lowerTag.includes('stadt') || lowerTag.includes('city') || lowerTag.includes('ort')) {
|
|
return randomChoice(randomData.cities);
|
|
}
|
|
if (lowerTag.includes('straße') || lowerTag.includes('street') || lowerTag.includes('adresse')) {
|
|
return `${randomChoice(randomData.streets)} ${Math.floor(Math.random() * 200) + 1}`;
|
|
}
|
|
if (lowerTag.includes('plz') || lowerTag.includes('postcode') || lowerTag.includes('zip')) {
|
|
return String(Math.floor(Math.random() * 90000) + 10000);
|
|
}
|
|
|
|
// Kontakt
|
|
if (lowerTag.includes('email') || lowerTag.includes('mail')) {
|
|
return randomChoice(randomData.emails);
|
|
}
|
|
if (lowerTag.includes('telefon') || lowerTag.includes('phone') || lowerTag.includes('tel')) {
|
|
return `+49 ${Math.floor(Math.random() * 900) + 100} ${Math.floor(Math.random() * 9000000) + 1000000}`;
|
|
}
|
|
|
|
// Datum
|
|
if (lowerTag.includes('datum') || lowerTag.includes('date')) {
|
|
const date = new Date();
|
|
date.setDate(date.getDate() + Math.floor(Math.random() * 365) - 180);
|
|
return date.toLocaleDateString('de-DE');
|
|
}
|
|
|
|
// Zahlen/Preise
|
|
if (lowerTag.includes('preis') || lowerTag.includes('price') || lowerTag.includes('betrag') || lowerTag.includes('amount')) {
|
|
return `${(Math.random() * 9000 + 1000).toFixed(2)} €`;
|
|
}
|
|
if (lowerTag.includes('nummer') || lowerTag.includes('number') || lowerTag.includes('nr')) {
|
|
return String(Math.floor(Math.random() * 90000) + 10000);
|
|
}
|
|
if (lowerTag.includes('menge') || lowerTag.includes('anzahl') || lowerTag.includes('quantity')) {
|
|
return String(Math.floor(Math.random() * 50) + 1);
|
|
}
|
|
|
|
// Produkte/Services
|
|
if (lowerTag.includes('produkt') || lowerTag.includes('product') || lowerTag.includes('service') || lowerTag.includes('artikel')) {
|
|
return randomChoice(randomData.products);
|
|
}
|
|
|
|
// Beschreibungen
|
|
if (lowerTag.includes('beschreibung') || lowerTag.includes('description') || lowerTag.includes('text')) {
|
|
return 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.';
|
|
}
|
|
|
|
// Default für unbekannte Tags
|
|
return `Beispiel ${tagName}`;
|
|
}
|
|
|
|
// Hilfsfunktion für zufällige Auswahl
|
|
function randomChoice(array) {
|
|
return array[Math.floor(Math.random() * array.length)];
|
|
}
|
|
|
|
// Funktion zur Extraktion von Template-Tags aus DOCX
|
|
function extractTemplateTagsFromDocx(filePath) {
|
|
try {
|
|
const zip = new AdmZip(filePath);
|
|
const docXml = zip.readAsText('word/document.xml');
|
|
|
|
// Suche nach ++tag++ Pattern
|
|
const tagPattern = /\+\+([^+]+)\+\+/g;
|
|
const tags = new Set();
|
|
let match;
|
|
|
|
while ((match = tagPattern.exec(docXml)) !== null) {
|
|
tags.add(match[1]);
|
|
}
|
|
|
|
return Array.from(tags);
|
|
} catch (error) {
|
|
console.error('Fehler beim Extrahieren der Tags:', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// Funktion zur Generierung von Tabellendaten
|
|
function generateTableData(tableName, rowCount = 3) {
|
|
const tableData = [];
|
|
|
|
for (let i = 0; i < rowCount; i++) {
|
|
if (tableName.toLowerCase().includes('produkt') || tableName.toLowerCase().includes('artikel')) {
|
|
tableData.push({
|
|
pos: i + 1,
|
|
artikel: randomChoice(randomData.products),
|
|
beschreibung: 'Hochwertige Qualität und zuverlässige Leistung',
|
|
menge: Math.floor(Math.random() * 10) + 1,
|
|
preis: (Math.random() * 500 + 50).toFixed(2) + ' €',
|
|
gesamt: (Math.random() * 1000 + 100).toFixed(2) + ' €'
|
|
});
|
|
} else if (tableName.toLowerCase().includes('person') || tableName.toLowerCase().includes('mitarbeiter')) {
|
|
tableData.push({
|
|
nr: i + 1,
|
|
name: randomChoice(randomData.names),
|
|
position: randomChoice(['Manager', 'Developer', 'Designer', 'Analyst', 'Consultant']),
|
|
abteilung: randomChoice(['IT', 'Marketing', 'Vertrieb', 'Personal', 'Finanzen']),
|
|
email: randomChoice(randomData.emails)
|
|
});
|
|
} else if (tableName.toLowerCase().includes('position') || tableName.toLowerCase().includes('rechnung')) {
|
|
// Rechnungsposten
|
|
tableData.push({
|
|
nr: i + 1,
|
|
artikel: randomChoice(randomData.products),
|
|
menge: Math.floor(Math.random() * 10) + 1,
|
|
einzelpreis: (Math.random() * 500 + 50).toFixed(2) + ' €',
|
|
gesamtpreis: (Math.random() * 1000 + 100).toFixed(2) + ' €'
|
|
});
|
|
} else if (tableName.toLowerCase().includes('leistung') || tableName.toLowerCase().includes('angebot')) {
|
|
// Angebots-/Leistungsposten
|
|
tableData.push({
|
|
nr: i + 1,
|
|
titel: randomChoice(randomData.products),
|
|
beschreibung: 'Professionelle Dienstleistung mit hoher Qualität und termingerechter Umsetzung',
|
|
aufwand: `${Math.floor(Math.random() * 40) + 5} Stunden`,
|
|
preis: (Math.random() * 2000 + 500).toFixed(2) + ' €'
|
|
});
|
|
} else if (tableName.toLowerCase().includes('aufgaben') || tableName.toLowerCase().includes('task')) {
|
|
// Aufgaben/Tasks
|
|
tableData.push({
|
|
nr: i + 1,
|
|
titel: randomChoice(['Website Design', 'API Development', 'Testing', 'Documentation', 'Code Review']),
|
|
status: randomChoice(['In Bearbeitung', 'Abgeschlossen', 'Geplant', 'Blockiert']),
|
|
deadline: new Date(Date.now() + Math.random() * 30 * 24 * 60 * 60 * 1000).toLocaleDateString('de-DE')
|
|
});
|
|
} else {
|
|
// Standard Tabelle
|
|
tableData.push({
|
|
nr: i + 1,
|
|
bezeichnung: `Eintrag ${i + 1}`,
|
|
wert: (Math.random() * 1000).toFixed(2),
|
|
status: randomChoice(['Aktiv', 'Inaktiv', 'Pending', 'Abgeschlossen'])
|
|
});
|
|
}
|
|
}
|
|
|
|
return tableData;
|
|
}
|
|
|
|
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 = `<?xml version="1.0" encoding="utf-8"?>
|
|
<D:multistatus xmlns:D="DAV:">
|
|
${files.map(file => {
|
|
const filePath = path.join(templatesDir, file);
|
|
const stats = fs.statSync(filePath);
|
|
return `
|
|
<D:response>
|
|
<D:href>/webdav/templates/${file}</D:href>
|
|
<D:propstat>
|
|
<D:prop>
|
|
<D:displayname>${file}</D:displayname>
|
|
<D:getcontentlength>${stats.size}</D:getcontentlength>
|
|
<D:getlastmodified>${stats.mtime.toUTCString()}</D:getlastmodified>
|
|
<D:resourcetype/>
|
|
<D:getcontenttype>application/vnd.openxmlformats-officedocument.wordprocessingml.document</D:getcontenttype>
|
|
</D:prop>
|
|
<D:status>HTTP/1.1 200 OK</D:status>
|
|
</D:propstat>
|
|
</D:response>`;
|
|
}).join('')}
|
|
</D:multistatus>`;
|
|
|
|
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 = `<?xml version="1.0" encoding="utf-8"?>
|
|
<D:multistatus xmlns:D="DAV:">
|
|
${files.map(file => {
|
|
const filePath = path.join(outputDir, file);
|
|
const stats = fs.statSync(filePath);
|
|
return `
|
|
<D:response>
|
|
<D:href>/webdav/output/${file}</D:href>
|
|
<D:propstat>
|
|
<D:prop>
|
|
<D:displayname>${file}</D:displayname>
|
|
<D:getcontentlength>${stats.size}</D:getcontentlength>
|
|
<D:getlastmodified>${stats.mtime.toUTCString()}</D:getlastmodified>
|
|
<D:resourcetype/>
|
|
</D:prop>
|
|
<D:status>HTTP/1.1 200 OK</D:status>
|
|
</D:propstat>
|
|
</D:response>`;
|
|
}).join('')}
|
|
</D:multistatus>`;
|
|
|
|
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: Template mit automatischen Test-Daten befüllen
|
|
app.post('/test-template/:templateName', async (req, res) => {
|
|
try {
|
|
const templateName = req.params.templateName;
|
|
const { rowCount = 3 } = req.body; // Anzahl Tabellenzeilen
|
|
|
|
if (!templateName.endsWith('.docx')) {
|
|
return res.status(400).json({
|
|
error: 'Nur DOCX-Templates werden unterstützt'
|
|
});
|
|
}
|
|
|
|
const templatePath = path.join(templatesDir, templateName);
|
|
|
|
if (!fs.existsSync(templatePath)) {
|
|
return res.status(404).json({
|
|
error: 'Template nicht gefunden'
|
|
});
|
|
}
|
|
|
|
// Template-Tags extrahieren
|
|
const templateTags = extractTemplateTagsFromDocx(templatePath);
|
|
console.log(`📋 Gefundene Template-Tags in ${templateName}:`, templateTags);
|
|
|
|
// Automatische Test-Daten generieren
|
|
const testData = {};
|
|
const tableNames = [];
|
|
|
|
templateTags.forEach(tag => {
|
|
if (tag.includes('[') && tag.includes(']')) {
|
|
// Array/Tabellen-Tag gefunden (z.B. items[0].product)
|
|
const arrayMatch = tag.match(/([^[]+)\[/);
|
|
if (arrayMatch) {
|
|
const arrayName = arrayMatch[1];
|
|
if (!tableNames.includes(arrayName)) {
|
|
tableNames.push(arrayName);
|
|
}
|
|
}
|
|
} else if (tag.startsWith('$') || tag.includes('.')) {
|
|
// Andere Tabellen-Syntax (z.B. $tabelle oder tabelle.artikel)
|
|
const tableName = tag.split('.')[0].replace('$', '');
|
|
if (!tableNames.includes(tableName)) {
|
|
tableNames.push(tableName);
|
|
}
|
|
} else {
|
|
// Einfacher Platzhalter
|
|
testData[tag] = generateRandomValue(tag);
|
|
}
|
|
});
|
|
|
|
// Tabellendaten generieren für erkannte Arrays
|
|
tableNames.forEach(tableName => {
|
|
if (tableName === 'items') {
|
|
// Spezielle Behandlung für items Array
|
|
testData[tableName] = [];
|
|
for (let i = 0; i < rowCount; i++) {
|
|
testData[tableName].push({
|
|
product: randomChoice(randomData.products),
|
|
quantity: Math.floor(Math.random() * 10) + 1,
|
|
price: (Math.random() * 500 + 50).toFixed(2) + ' €'
|
|
});
|
|
}
|
|
} else if (tableName === 'mitarbeiterRows') {
|
|
testData[tableName] = generateTableData('mitarbeiter', rowCount);
|
|
} else if (tableName === 'aufgabenRows') {
|
|
testData[tableName] = generateTableData('aufgaben', rowCount);
|
|
} else {
|
|
// Für Arrays - erweitere auf bis zu 10 Elemente
|
|
const tableData = generateTableData(tableName, rowCount);
|
|
|
|
// Erstelle ein Array mit 10 Elementen
|
|
const expandedArray = Array.from({length: 10}, (_, index) => {
|
|
if (index < rowCount && tableData[index]) {
|
|
// Echte Daten für die gewünschte Anzahl
|
|
return tableData[index];
|
|
} else {
|
|
// Leere/unsichtbare Daten für den Rest
|
|
return {
|
|
nr: '',
|
|
name: '',
|
|
position: '',
|
|
email: '',
|
|
abteilung: '',
|
|
titel: '',
|
|
status: '',
|
|
deadline: '',
|
|
artikel: '',
|
|
menge: '',
|
|
preis: '',
|
|
einzelpreis: '',
|
|
gesamtpreis: '',
|
|
beschreibung: '',
|
|
wert: '',
|
|
aufwand: ''
|
|
};
|
|
}
|
|
});
|
|
|
|
testData[tableName] = expandedArray;
|
|
}
|
|
});
|
|
|
|
console.log(`🎲 Generierte Test-Daten:`, testData);
|
|
|
|
// Template laden
|
|
const template = fs.readFileSync(templatePath);
|
|
|
|
// Dokument mit Test-Daten befüllen
|
|
const buffer = await createReport({
|
|
template,
|
|
data: testData,
|
|
cmdDelimiter: ['++', '++'],
|
|
});
|
|
|
|
// Eindeutigen Dateinamen erstellen
|
|
const timestamp = Date.now();
|
|
const outputFileName = `test_${templateName.replace('.docx', '')}_${timestamp}.docx`;
|
|
const outputPath = path.join(outputDir, outputFileName);
|
|
|
|
// Befülltes Test-Dokument speichern
|
|
fs.writeFileSync(outputPath, buffer);
|
|
|
|
// Response mit Details über gefundene Tags und generierte Daten
|
|
res.json({
|
|
success: true,
|
|
templateName,
|
|
foundTags: templateTags,
|
|
tableNames,
|
|
generatedData: testData,
|
|
downloadUrl: `/download/${outputFileName}`,
|
|
webdavUrl: `/webdav/output/${outputFileName}`,
|
|
msWordUrl: `ms-word:ofe|u|${getBaseUrl(req)}/webdav/output/${outputFileName}`,
|
|
message: `Test-Dokument erfolgreich erstellt mit ${templateTags.length} Tags und ${tableNames.length} Tabellen`
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Erstellen des Test-Dokuments:', error);
|
|
res.status(500).json({
|
|
error: 'Fehler beim Erstellen des Test-Dokuments',
|
|
details: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// Route: One-Click Demo - Analyse + Demo-Dokument in einem Schritt
|
|
app.post('/demo-template/:templateName', async (req, res) => {
|
|
try {
|
|
const templateName = req.params.templateName;
|
|
const { rowCount = 3 } = req.body;
|
|
|
|
if (!templateName.endsWith('.docx')) {
|
|
return res.status(400).json({
|
|
error: 'Nur DOCX-Templates werden unterstützt'
|
|
});
|
|
}
|
|
|
|
const templatePath = path.join(templatesDir, templateName);
|
|
|
|
if (!fs.existsSync(templatePath)) {
|
|
return res.status(404).json({
|
|
error: 'Template nicht gefunden'
|
|
});
|
|
}
|
|
|
|
// SCHRITT 1: Template analysieren
|
|
const templateTags = extractTemplateTagsFromDocx(templatePath);
|
|
console.log(`🔍 Demo-Analyse für ${templateName}:`, templateTags);
|
|
|
|
// Tags kategorisieren
|
|
const simpleTags = [];
|
|
const tableTags = [];
|
|
const tableNames = new Set();
|
|
|
|
templateTags.forEach(tag => {
|
|
if (tag.includes('[') && tag.includes(']')) {
|
|
tableTags.push(tag);
|
|
const arrayMatch = tag.match(/([^[]+)\[/);
|
|
if (arrayMatch) {
|
|
tableNames.add(arrayMatch[1]);
|
|
}
|
|
} else if (tag.startsWith('$') || tag.includes('.')) {
|
|
tableTags.push(tag);
|
|
const tableName = tag.split('.')[0].replace('$', '');
|
|
tableNames.add(tableName);
|
|
} else {
|
|
simpleTags.push(tag);
|
|
}
|
|
});
|
|
|
|
// SCHRITT 2: Demo-Daten generieren
|
|
const demoData = {};
|
|
|
|
// Einfache Tags befüllen
|
|
simpleTags.forEach(tag => {
|
|
demoData[tag] = generateRandomValue(tag);
|
|
});
|
|
|
|
// Tabellen-Daten generieren
|
|
Array.from(tableNames).forEach(tableName => {
|
|
if (tableName === 'items') {
|
|
demoData[tableName] = [];
|
|
for (let i = 0; i < rowCount; i++) {
|
|
demoData[tableName].push({
|
|
product: randomChoice(randomData.products),
|
|
quantity: Math.floor(Math.random() * 10) + 1,
|
|
price: (Math.random() * 500 + 50).toFixed(2) + ' €'
|
|
});
|
|
}
|
|
} else if (tableName === 'mitarbeiterRows') {
|
|
demoData[tableName] = generateTableData('mitarbeiter', rowCount);
|
|
} else if (tableName === 'aufgabenRows') {
|
|
demoData[tableName] = generateTableData('aufgaben', rowCount);
|
|
} else {
|
|
// Für Arrays wie 'mitarbeiter' - erweitere auf bis zu 10 Elemente
|
|
const tableData = generateTableData(tableName, rowCount);
|
|
|
|
// Erstelle ein Array mit 10 Elementen
|
|
const expandedArray = Array.from({length: 10}, (_, index) => {
|
|
if (index < rowCount && tableData[index]) {
|
|
// Echte Daten für die gewünschte Anzahl
|
|
return tableData[index];
|
|
} else {
|
|
// Leere/unsichtbare Daten für den Rest
|
|
return {
|
|
nr: '',
|
|
name: '',
|
|
position: '',
|
|
email: '',
|
|
abteilung: '',
|
|
titel: '',
|
|
status: '',
|
|
deadline: '',
|
|
artikel: '',
|
|
menge: '',
|
|
preis: '',
|
|
einzelpreis: '',
|
|
gesamtpreis: '',
|
|
beschreibung: '',
|
|
wert: '',
|
|
aufwand: ''
|
|
};
|
|
}
|
|
});
|
|
|
|
demoData[tableName] = expandedArray;
|
|
}
|
|
});
|
|
|
|
console.log(`🎲 Generierte Demo-Daten:`, demoData);
|
|
|
|
// SCHRITT 3: Demo-Dokument erstellen
|
|
const template = fs.readFileSync(templatePath);
|
|
const buffer = await createReport({
|
|
template,
|
|
data: demoData,
|
|
cmdDelimiter: ['++', '++'],
|
|
});
|
|
|
|
const timestamp = Date.now();
|
|
const outputFileName = `demo_${templateName.replace('.docx', '')}_${timestamp}.docx`;
|
|
const outputPath = path.join(outputDir, outputFileName);
|
|
|
|
fs.writeFileSync(outputPath, buffer);
|
|
|
|
// SCHRITT 4: Detaillierte Analyse-Response
|
|
const analysisResult = {
|
|
// Template-Info
|
|
templateInfo: {
|
|
name: templateName,
|
|
size: fs.statSync(templatePath).size,
|
|
path: templatePath,
|
|
totalTags: templateTags.length
|
|
},
|
|
|
|
// Tag-Analyse
|
|
tagAnalysis: {
|
|
foundTags: templateTags,
|
|
simpleTags,
|
|
tableTags,
|
|
tableNames: Array.from(tableNames),
|
|
tagBreakdown: {
|
|
simple: simpleTags.length,
|
|
tables: tableNames.size,
|
|
total: templateTags.length
|
|
}
|
|
},
|
|
|
|
// Generierte Demo-Daten
|
|
demoData,
|
|
|
|
// Download-Links
|
|
demoDocument: {
|
|
filename: outputFileName,
|
|
downloadUrl: `/download/${outputFileName}`,
|
|
webdavUrl: `/webdav/output/${outputFileName}`,
|
|
msWordUrl: `ms-word:ofe|u|${getBaseUrl(req)}/webdav/output/${outputFileName}`
|
|
},
|
|
|
|
// Erfolgs-Status
|
|
success: true,
|
|
message: `✅ Demo erstellt: ${templateTags.length} Tags analysiert, ${tableNames.size} Tabellen befüllt, ${rowCount} Zeilen pro Tabelle`,
|
|
|
|
// Zeitstempel
|
|
created: new Date().toISOString()
|
|
};
|
|
|
|
res.json(analysisResult);
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Erstellen der Demo:', error);
|
|
res.status(500).json({
|
|
error: 'Fehler beim Erstellen der Demo',
|
|
details: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// Route: Template-Tags analysieren (ohne Dokument zu erstellen)
|
|
app.get('/analyze-template/:templateName', (req, res) => {
|
|
try {
|
|
const templateName = req.params.templateName;
|
|
|
|
if (!templateName.endsWith('.docx')) {
|
|
return res.status(400).json({
|
|
error: 'Nur DOCX-Templates werden unterstützt'
|
|
});
|
|
}
|
|
|
|
const templatePath = path.join(templatesDir, templateName);
|
|
|
|
if (!fs.existsSync(templatePath)) {
|
|
return res.status(404).json({
|
|
error: 'Template nicht gefunden'
|
|
});
|
|
}
|
|
|
|
// Template-Tags extrahieren
|
|
const templateTags = extractTemplateTagsFromDocx(templatePath);
|
|
|
|
// Tags kategorisieren
|
|
const simpleTags = [];
|
|
const tableTags = [];
|
|
const tableNames = new Set();
|
|
|
|
templateTags.forEach(tag => {
|
|
if (tag.includes('[') && tag.includes(']')) {
|
|
// Array/Tabellen-Tag gefunden (z.B. items[0].product)
|
|
tableTags.push(tag);
|
|
const arrayMatch = tag.match(/([^[]+)\[/);
|
|
if (arrayMatch) {
|
|
tableNames.add(arrayMatch[1]);
|
|
}
|
|
} else if (tag.startsWith('$') || tag.includes('.')) {
|
|
// Andere Tabellen-Syntax
|
|
tableTags.push(tag);
|
|
const tableName = tag.split('.')[0].replace('$', '');
|
|
tableNames.add(tableName);
|
|
} else {
|
|
simpleTags.push(tag);
|
|
}
|
|
});
|
|
|
|
// Beispiel-Daten generieren (ohne Dokument zu erstellen)
|
|
const exampleData = {};
|
|
simpleTags.forEach(tag => {
|
|
exampleData[tag] = generateRandomValue(tag);
|
|
});
|
|
|
|
Array.from(tableNames).forEach(tableName => {
|
|
if (tableName === 'items') {
|
|
// Spezielle Behandlung für items Array
|
|
exampleData[tableName] = [
|
|
{
|
|
product: randomChoice(randomData.products),
|
|
quantity: Math.floor(Math.random() * 10) + 1,
|
|
price: (Math.random() * 500 + 50).toFixed(2) + ' €'
|
|
},
|
|
{
|
|
product: randomChoice(randomData.products),
|
|
quantity: Math.floor(Math.random() * 10) + 1,
|
|
price: (Math.random() * 500 + 50).toFixed(2) + ' €'
|
|
}
|
|
];
|
|
} else {
|
|
exampleData[tableName] = generateTableData(tableName, 2); // Nur 2 Beispielzeilen
|
|
}
|
|
});
|
|
|
|
res.json({
|
|
templateName,
|
|
totalTags: templateTags.length,
|
|
simpleTags,
|
|
tableTags,
|
|
tableNames: Array.from(tableNames),
|
|
exampleData,
|
|
usage: {
|
|
testTemplate: `POST /test-template/${templateName}`,
|
|
generateDocument: `POST /generate-document`,
|
|
webdavEdit: `ms-word:ofe|u|${getBaseUrl(req)}/webdav/templates/${templateName}`
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Analysieren des Templates:', error);
|
|
res.status(500).json({
|
|
error: 'Fehler beim Analysieren des Templates',
|
|
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);
|
|
const baseUrl = getBaseUrl(req);
|
|
return {
|
|
name: file,
|
|
size: stats.size,
|
|
created: stats.birthtime,
|
|
modified: stats.mtime,
|
|
path: `/templates/${file}`,
|
|
webdavUrl: `/webdav/templates/${file}`,
|
|
msWordUrl: `ms-word:ofe|u|${baseUrl}/webdav/templates/${file}`,
|
|
// Neue Analyse- und Test-Links
|
|
analyzeUrl: `/analyze-template/${file}`,
|
|
testUrl: `/test-template/${file}`,
|
|
demoUrl: `/demo-template/${file}`,
|
|
actions: {
|
|
analyze: {
|
|
method: 'GET',
|
|
url: `/analyze-template/${file}`,
|
|
description: 'Template analysieren und alle Tags anzeigen'
|
|
},
|
|
test: {
|
|
method: 'POST',
|
|
url: `/test-template/${file}`,
|
|
description: 'Test-Dokument mit Zufallsdaten erstellen',
|
|
body: { rowCount: 3 }
|
|
},
|
|
demo: {
|
|
method: 'POST',
|
|
url: `/demo-template/${file}`,
|
|
description: 'One-Click Demo: Analyse + Demo-Dokument erstellen',
|
|
body: { rowCount: 3 }
|
|
},
|
|
generate: {
|
|
method: 'POST',
|
|
url: '/generate-document',
|
|
description: 'Dokument mit eigenen Daten erstellen',
|
|
body: { templateName: file, data: {} }
|
|
}
|
|
}
|
|
};
|
|
});
|
|
|
|
res.json({
|
|
templates,
|
|
count: templates.length,
|
|
webdavAccess: `/webdav/templates/`,
|
|
uploadInfo: {
|
|
method: 'PUT',
|
|
url: `/webdav/templates/dateiname.docx`,
|
|
contentType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
|
},
|
|
templateActions: {
|
|
analyze: {
|
|
description: 'Template analysieren - alle Tags und Beispieldaten anzeigen',
|
|
method: 'GET',
|
|
url: '/analyze-template/{templateName}',
|
|
example: '/analyze-template/simple-template.docx'
|
|
},
|
|
test: {
|
|
description: 'Test-Dokument mit Zufallsdaten erstellen',
|
|
method: 'POST',
|
|
url: '/test-template/{templateName}',
|
|
body: { rowCount: 3 },
|
|
example: '/test-template/simple-template.docx'
|
|
},
|
|
demo: {
|
|
description: '🚀 One-Click Demo: Analyse + Demo-Dokument in einem Schritt',
|
|
method: 'POST',
|
|
url: '/demo-template/{templateName}',
|
|
body: { rowCount: 3 },
|
|
example: '/demo-template/simple-template.docx',
|
|
features: ['Vollständige Template-Analyse', 'Automatische Demo-Daten', 'Sofortiges Demo-Dokument', 'Detaillierte Analyse-Response']
|
|
},
|
|
generate: {
|
|
description: 'Dokument mit eigenen Daten erstellen',
|
|
method: 'POST',
|
|
url: '/generate-document',
|
|
body: { templateName: 'template.docx', data: {} }
|
|
}
|
|
}
|
|
});
|
|
} 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(', ')}`
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// Route: Echte dynamische Templates mit docxtemplater
|
|
app.post('/dynamic-template/:templateName', async (req, res) => {
|
|
try {
|
|
const templateName = req.params.templateName;
|
|
const { tables = {}, data = {} } = req.body;
|
|
|
|
console.log(`🔄 Dynamisches Template mit docxtemplater: ${templateName}`);
|
|
|
|
// Template laden
|
|
const templatePath = path.join(templatesDir, templateName);
|
|
if (!fs.existsSync(templatePath)) {
|
|
return res.status(404).json({ error: `Template ${templateName} nicht gefunden` });
|
|
}
|
|
|
|
const content = fs.readFileSync(templatePath, 'binary');
|
|
const zip = new PizZip(content);
|
|
const doc = new Docxtemplater(zip, {
|
|
paragraphLoop: true,
|
|
linebreaks: true,
|
|
});
|
|
|
|
// Dynamische Daten generieren
|
|
const templateData = { ...data };
|
|
|
|
// Einfache Tags befüllen
|
|
templateData.projekt = templateData.projekt || 'Dynamisches Projekt';
|
|
templateData.datum = templateData.datum || new Date().toLocaleDateString('de-DE');
|
|
templateData.ersteller = templateData.ersteller || 'Automatisch generiert';
|
|
templateData.status = templateData.status || 'Aktiv';
|
|
|
|
// Tabellen-Arrays generieren
|
|
Object.entries(tables).forEach(([tableName, rowCount]) => {
|
|
console.log(`📊 Generiere ${rowCount} Zeilen für Tabelle: ${tableName}`);
|
|
templateData[tableName] = generateTableData(tableName, parseInt(rowCount));
|
|
});
|
|
|
|
console.log(`🎲 Template-Daten:`, {
|
|
...templateData,
|
|
tableCount: Object.keys(tables).length
|
|
});
|
|
|
|
// Template rendern
|
|
doc.render(templateData);
|
|
|
|
const buffer = doc.getZip().generate({
|
|
type: 'nodebuffer',
|
|
compression: 'DEFLATE',
|
|
});
|
|
|
|
// Datei speichern
|
|
const timestamp = Date.now();
|
|
const outputFileName = `dynamic_${templateName.replace('.docx', '')}_${timestamp}.docx`;
|
|
const outputPath = path.join(outputDir, outputFileName);
|
|
|
|
fs.writeFileSync(outputPath, buffer);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: `✅ Dynamisches Template erstellt: ${Object.keys(tables).map(t => `${t} (${tables[t]} Zeilen)`).join(', ')}`,
|
|
file: outputFileName,
|
|
download: `/download/${outputFileName}`,
|
|
webdav: {
|
|
http: `http://localhost:3000/webdav/output/${outputFileName}`,
|
|
https: `https://localhost:3443/webdav/output/${outputFileName}`
|
|
},
|
|
data: templateData
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Fehler bei dynamischem Template:', error);
|
|
res.status(500).json({
|
|
error: 'Fehler beim Erstellen des dynamischen Templates',
|
|
details: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
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; |