✨ 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
270 lines
8.8 KiB
JavaScript
270 lines
8.8 KiB
JavaScript
const axios = require('axios');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
class DataSourceManager {
|
|
constructor(configPath) {
|
|
this.configPath = configPath;
|
|
this.dataSources = this.loadDataSources();
|
|
}
|
|
|
|
loadDataSources() {
|
|
try {
|
|
const config = JSON.parse(fs.readFileSync(this.configPath, 'utf8'));
|
|
return config.dataSources || [];
|
|
} catch (error) {
|
|
console.error('❌ Fehler beim Laden der Datenquellen:', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
async fetchFromDataSource(dataSourceId, params = {}) {
|
|
const dataSource = this.dataSources.find(ds => ds.id === dataSourceId);
|
|
if (!dataSource || !dataSource.enabled) {
|
|
throw new Error(`Datenquelle ${dataSourceId} nicht gefunden oder deaktiviert`);
|
|
}
|
|
|
|
console.log(`🔌 Lade Daten von: ${dataSource.name} (${dataSource.type})`);
|
|
|
|
try {
|
|
switch (dataSource.type) {
|
|
case 'rest':
|
|
return await this.fetchFromRest(dataSource, params);
|
|
case 'database':
|
|
return await this.fetchFromDatabase(dataSource, params);
|
|
case 'file':
|
|
return await this.fetchFromFile(dataSource, params);
|
|
default:
|
|
throw new Error(`Unbekannter Datenquellen-Typ: ${dataSource.type}`);
|
|
}
|
|
} catch (error) {
|
|
console.error(`❌ Fehler bei Datenquelle ${dataSource.name}:`, error.message);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async fetchFromRest(dataSource, params) {
|
|
const config = {
|
|
method: dataSource.method || 'GET',
|
|
url: this.buildUrl(dataSource.url, params),
|
|
headers: dataSource.headers || {},
|
|
timeout: dataSource.timeout || 10000
|
|
};
|
|
|
|
if (dataSource.method === 'POST' && params.body) {
|
|
config.data = params.body;
|
|
}
|
|
|
|
const response = await axios(config);
|
|
return this.mapResponse(response.data, dataSource.mapping);
|
|
}
|
|
|
|
async fetchFromDatabase(dataSource, params) {
|
|
// Hier würde eine DB-Verbindung implementiert werden
|
|
// Für jetzt simulieren wir es
|
|
throw new Error('Database-Datenquellen noch nicht implementiert');
|
|
}
|
|
|
|
async fetchFromFile(dataSource, params) {
|
|
const filePath = path.resolve(dataSource.url);
|
|
const data = fs.readFileSync(filePath, 'utf8');
|
|
|
|
if (dataSource.format === 'json') {
|
|
return this.mapResponse(JSON.parse(data), dataSource.mapping);
|
|
} else if (dataSource.format === 'csv') {
|
|
// CSV-Parser würde hier implementiert
|
|
throw new Error('CSV-Format noch nicht implementiert');
|
|
}
|
|
|
|
return { content: data };
|
|
}
|
|
|
|
buildUrl(urlTemplate, params) {
|
|
let url = urlTemplate;
|
|
|
|
// URL-Parameter ersetzen: {param} -> Wert
|
|
Object.keys(params).forEach(key => {
|
|
url = url.replace(`{${key}}`, encodeURIComponent(params[key]));
|
|
});
|
|
|
|
return url;
|
|
}
|
|
|
|
mapResponse(data, mapping) {
|
|
if (!mapping || Object.keys(mapping).length === 0) {
|
|
return data;
|
|
}
|
|
|
|
const mapped = {};
|
|
|
|
Object.keys(mapping).forEach(targetKey => {
|
|
const sourcePath = mapping[targetKey];
|
|
mapped[targetKey] = this.getNestedValue(data, sourcePath);
|
|
});
|
|
|
|
return mapped;
|
|
}
|
|
|
|
getNestedValue(obj, path) {
|
|
return path.split('.').reduce((current, key) => {
|
|
return current && current[key] !== undefined ? current[key] : null;
|
|
}, obj);
|
|
}
|
|
|
|
// Template-Tags mit Datenquellen erweitern
|
|
async processDataSourceTags(templateContent, params = {}) {
|
|
// Finde alle Datenquellen-Tags: {ds:dataSourceId:field}
|
|
const dsTagRegex = /\{ds:([^:]+):([^}]+)\}/g;
|
|
const matches = [...templateContent.matchAll(dsTagRegex)];
|
|
|
|
if (matches.length === 0) {
|
|
return {};
|
|
}
|
|
|
|
const data = {};
|
|
const promises = [];
|
|
|
|
// Gruppiere Tags nach Datenquelle
|
|
const byDataSource = {};
|
|
matches.forEach(match => {
|
|
const [fullTag, dataSourceId, field] = match;
|
|
if (!byDataSource[dataSourceId]) {
|
|
byDataSource[dataSourceId] = [];
|
|
}
|
|
byDataSource[dataSourceId].push({ fullTag, field });
|
|
});
|
|
|
|
// Lade Daten von jeder Datenquelle
|
|
for (const [dataSourceId, tags] of Object.entries(byDataSource)) {
|
|
promises.push(
|
|
this.fetchFromDataSource(dataSourceId, params)
|
|
.then(sourceData => {
|
|
tags.forEach(({ fullTag, field }) => {
|
|
const value = this.getNestedValue(sourceData, field);
|
|
data[fullTag.slice(1, -1)] = value; // Remove { }
|
|
});
|
|
})
|
|
.catch(error => {
|
|
console.error(`❌ Datenquelle ${dataSourceId} fehlgeschlagen:`, error.message);
|
|
// Fallback-Werte setzen
|
|
tags.forEach(({ fullTag, field }) => {
|
|
data[fullTag.slice(1, -1)] = `[ERROR: ${field}]`;
|
|
});
|
|
})
|
|
);
|
|
}
|
|
|
|
await Promise.all(promises);
|
|
return data;
|
|
}
|
|
}
|
|
|
|
class CustomTagProcessor {
|
|
constructor(configPath) {
|
|
this.configPath = configPath;
|
|
this.customTags = this.loadCustomTags();
|
|
}
|
|
|
|
loadCustomTags() {
|
|
try {
|
|
const config = JSON.parse(fs.readFileSync(this.configPath, 'utf8'));
|
|
return config.customTags || {};
|
|
} catch (error) {
|
|
console.error('❌ Fehler beim Laden der Custom Tags:', error);
|
|
return {};
|
|
}
|
|
}
|
|
|
|
processCustomTags(doc, data) {
|
|
const processedData = { ...data };
|
|
|
|
// Header verarbeiten
|
|
if (this.customTags.header && this.customTags.header.enabled) {
|
|
processedData['custom_header'] = this.customTags.header.content;
|
|
this.insertHeader(doc, this.customTags.header.content);
|
|
}
|
|
|
|
// Footer verarbeiten
|
|
if (this.customTags.footer && this.customTags.footer.enabled) {
|
|
processedData['custom_footer'] = this.customTags.footer.content;
|
|
this.insertFooter(doc, this.customTags.footer.content);
|
|
}
|
|
|
|
// Signature verarbeiten
|
|
if (this.customTags.signature && this.customTags.signature.enabled) {
|
|
processedData['custom_signature'] = this.customTags.signature.content;
|
|
}
|
|
|
|
return processedData;
|
|
}
|
|
|
|
insertHeader(doc, content) {
|
|
try {
|
|
// Hier würde die Header-Insertion in das DOCX implementiert
|
|
console.log(`📄 Header hinzugefügt: ${content.substring(0, 50)}...`);
|
|
} catch (error) {
|
|
console.error('❌ Fehler beim Hinzufügen des Headers:', error);
|
|
}
|
|
}
|
|
|
|
insertFooter(doc, content) {
|
|
try {
|
|
// Hier würde die Footer-Insertion in das DOCX implementiert
|
|
console.log(`📄 Footer hinzugefügt: ${content.substring(0, 50)}...`);
|
|
} catch (error) {
|
|
console.error('❌ Fehler beim Hinzufügen des Footers:', error);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Export für Integration in den Hauptserver
|
|
module.exports = {
|
|
DataSourceManager,
|
|
CustomTagProcessor
|
|
};
|
|
|
|
// Beispiel-Konfiguration für Datenquellen
|
|
const exampleDataSources = [
|
|
{
|
|
id: 'customers_api',
|
|
name: 'Kunden-API',
|
|
type: 'rest',
|
|
url: 'https://api.example.com/customers/{customerId}',
|
|
method: 'GET',
|
|
headers: {
|
|
'Authorization': 'Bearer TOKEN',
|
|
'Content-Type': 'application/json'
|
|
},
|
|
mapping: {
|
|
'customer_name': 'data.name',
|
|
'customer_email': 'data.email',
|
|
'customer_address': 'data.address.street'
|
|
},
|
|
enabled: true
|
|
},
|
|
{
|
|
id: 'products_db',
|
|
name: 'Produkt-Datenbank',
|
|
type: 'database',
|
|
url: 'postgresql://user:pass@localhost:5432/products',
|
|
query: 'SELECT * FROM products WHERE id = ?',
|
|
mapping: {
|
|
'product_name': 'name',
|
|
'product_price': 'price',
|
|
'product_description': 'description'
|
|
},
|
|
enabled: false
|
|
},
|
|
{
|
|
id: 'local_data',
|
|
name: 'Lokale JSON-Datei',
|
|
type: 'file',
|
|
url: './data/sample.json',
|
|
format: 'json',
|
|
mapping: {
|
|
'company_info': 'company.name',
|
|
'contact_person': 'company.contact.name'
|
|
},
|
|
enabled: true
|
|
}
|
|
]; |