Initial commit: DOCX Template Server mit API und Tabellen-Support

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

939
server_webdav_backup.js Normal file
View File

@@ -0,0 +1,939 @@
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 crypto = require('crypto');
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 });
}
});
// WebDAV-Implementierung für Word/Office-Integration mit SharePoint-Kompatibilität
const webdavMethods = ['PROPFIND', 'PROPPATCH', 'MKCOL', 'COPY', 'MOVE', 'LOCK', 'UNLOCK'];
// SharePoint-spezifische Namespaces und Header
const sharePointNamespaces = {
'xmlns:D': 'DAV:',
'xmlns:S': 'http://schemas.microsoft.com/sharepoint/soap/',
'xmlns:Z': 'urn:schemas-microsoft-com:',
'xmlns:O': 'urn:schemas-microsoft-com:office:office',
'xmlns:T': 'http://schemas.microsoft.com/repl/'
};
// WebDAV PROPFIND Handler mit SharePoint-Modus
app.use('/webdav', (req, res, next) => {
// Erweiterte CORS und WebDAV-Header für Office/SharePoint
res.set({
'DAV': '1, 2, ordered-collections, versioning, extended-mkcol',
'MS-Author-Via': 'DAV',
'Server': 'Microsoft-IIS/10.0 Microsoft-HTTPAPI/2.0',
'MicrosoftSharePointTeamServices': '15.0.0.4569',
'SPRequestGuid': `{${crypto.randomUUID()}}`,
'SPIisLatency': '0',
'Allow': 'GET, HEAD, POST, PUT, DELETE, OPTIONS, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, HEAD, POST, PUT, DELETE, OPTIONS, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK',
'Access-Control-Allow-Headers': 'Content-Type, Depth, Authorization, Destination, If, Lock-Token, Overwrite, Timeout, X-Requested-With, SOAPAction',
'Cache-Control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache',
'Expires': '0',
'X-SharePointHealthScore': '0',
'X-AspNet-Version': '4.0.30319',
'X-Powered-By': 'ASP.NET'
});
if (req.method === 'OPTIONS') {
return res.status(200).end();
}
if (req.method === 'PROPFIND') {
const depth = req.headers.depth || 'infinity';
const requestPath = req.path.replace('/webdav', '');
let targetDir;
if (requestPath.startsWith('/templates')) {
targetDir = path.join(templateDir, requestPath.replace('/templates', ''));
} else if (requestPath.startsWith('/documents')) {
targetDir = path.join(outputDir, requestPath.replace('/documents', ''));
} else {
targetDir = __dirname;
}
try {
let items = [];
if (fs.existsSync(targetDir)) {
const stats = fs.statSync(targetDir);
if (stats.isDirectory()) {
const files = fs.readdirSync(targetDir);
// Verzeichnis selbst
items.push({
href: req.path + (req.path.endsWith('/') ? '' : '/'),
isDirectory: true,
lastModified: stats.mtime.toUTCString(),
size: 0
});
// Dateien im Verzeichnis
files.forEach(file => {
const filePath = path.join(targetDir, file);
const fileStats = fs.statSync(filePath);
items.push({
href: req.path + (req.path.endsWith('/') ? '' : '/') + file,
isDirectory: fileStats.isDirectory(),
lastModified: fileStats.mtime.toUTCString(),
size: fileStats.size || 0,
contentType: fileStats.isDirectory() ? 'httpd/unix-directory' : 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
});
});
} else {
// Einzelne Datei
items.push({
href: req.path,
isDirectory: false,
lastModified: stats.mtime.toUTCString(),
size: stats.size,
contentType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
});
}
}
// WebDAV XML Response mit SharePoint-spezifischen Eigenschaften
const xmlResponse = `<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:" xmlns:S="http://schemas.microsoft.com/sharepoint/soap/" xmlns:Z="urn:schemas-microsoft-com:" xmlns:O="urn:schemas-microsoft-com:office:office">
${items.map(item => `
<D:response>
<D:href>${item.href}</D:href>
<D:propstat>
<D:prop>
<D:displayname>${path.basename(item.href)}</D:displayname>
<D:getlastmodified>${item.lastModified}</D:getlastmodified>
<D:getcontentlength>${item.size}</D:getcontentlength>
<D:getcontenttype>${item.contentType}</D:getcontenttype>
<D:resourcetype>${item.isDirectory ? '<D:collection/>' : ''}</D:resourcetype>
<D:supportedlock>
<D:lockentry>
<D:lockscope><D:exclusive/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
<D:lockentry>
<D:lockscope><D:shared/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
</D:supportedlock>
<D:getetag>"${Date.now()}-${item.size}"</D:getetag>
<D:creationdate>${new Date(item.lastModified).toISOString()}</D:creationdate>
<D:iscollection>${item.isDirectory ? 'true' : 'false'}</D:iscollection>
<D:executable>F</D:executable>
<D:ishidden>F</D:ishidden>
<D:isreadonly>F</D:isreadonly>
<O:IsReadonly>F</O:IsReadonly>
<Z:Win32CreationTime>${new Date(item.lastModified).toISOString()}</Z:Win32CreationTime>
<Z:Win32LastAccessTime>${new Date().toISOString()}</Z:Win32LastAccessTime>
<Z:Win32LastModifiedTime>${new Date(item.lastModified).toISOString()}</Z:Win32LastModifiedTime>
<Z:Win32FileAttributes>00000020</Z:Win32FileAttributes>
<S:modifiedby>System Account</S:modifiedby>
<S:checkedoutby></S:checkedoutby>
<S:IsCheckedoutToLocal>0</S:IsCheckedoutToLocal>
<S:CheckoutExpires></S:CheckoutExpires>
<S:CanCheckOut>1</S:CanCheckOut>
<S:ContentTypeId>0x0101</S:ContentTypeId>
<S:DocumentWorkflowStatus>0</S:DocumentWorkflowStatus>
</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(404).send('Not Found');
}
return;
}
next();
});
// WebDAV PUT Handler für Dateien speichern
// PUT - File Upload (für Word Speichern)
app.put('/dav/*', (req, res) => {
const filePath = path.join(__dirname, 'uploads', req.params[0]);
const dirPath = path.dirname(filePath);
console.log(`<EFBFBD> PUT Request: ${req.params[0]}`);
console.log(`📂 Speichere nach: ${filePath}`);
// Stelle sicher dass der Ordner existiert
fs.mkdirSync(dirPath, { recursive: true });
const writeStream = fs.createWriteStream(filePath);
req.pipe(writeStream);
writeStream.on('finish', () => {
console.log(`✅ Datei gespeichert: ${filePath}`);
res.set({
'DAV': '1, 2, ordered-collections, versioning, extended-mkcol',
'MS-Author-Via': 'DAV',
'Server': 'Microsoft-IIS/10.0 Microsoft-HTTPAPI/2.0',
'MicrosoftSharePointTeamServices': '15.0.0.4569',
'SPRequestGuid': `{${crypto.randomUUID()}}`,
'Cache-Control': 'no-cache',
'ETag': `"${Date.now()}-${req.headers['content-length'] || 0}"`,
'Last-Modified': new Date().toUTCString(),
'X-SharePoint-HealthScore': '0'
});
res.status(201).send('Created');
});
writeStream.on('error', (err) => {
console.error(`❌ Fehler beim Speichern: ${err.message}`);
res.status(500).send('Internal Server Error');
});
});
app.put('/webdav/documents/:filename', (req, res) => {
const filename = req.params.filename;
const filePath = path.join(outputDir, filename);
console.log(`📝 Word speichert Dokument: ${filename}`);
const writeStream = fs.createWriteStream(filePath);
req.pipe(writeStream);
writeStream.on('finish', () => {
// Dateirechte explizit setzen
try {
fs.chmodSync(filePath, 0o666); // Lese-/Schreibrechte für alle
} catch (error) {
console.warn('Warnung: Konnte Dateiberechtigungen nicht setzen:', error.message);
}
res.set({
'DAV': '1, 2, ordered-collections, versioning',
'MS-Author-Via': 'DAV',
'Server': 'Microsoft-IIS/10.0',
'Cache-Control': 'no-cache',
'ETag': `"${Date.now()}-${req.headers['content-length'] || 0}"`,
'Last-Modified': new Date().toUTCString()
});
res.status(201).send('Created');
console.log(`✅ Dokument gespeichert: ${filename}`);
});
writeStream.on('error', (error) => {
console.error('❌ Fehler beim Speichern:', error);
res.status(500).send('Internal Server Error');
});
});
// WebDAV GET Handler für Dateien lesen
app.get('/webdav/templates/:filename', (req, res) => {
const filename = req.params.filename;
const filePath = path.join(templateDir, filename);
if (fs.existsSync(filePath)) {
const stats = fs.statSync(filePath);
res.set({
'DAV': '1, 2, ordered-collections, versioning',
'MS-Author-Via': 'DAV',
'Server': 'Microsoft-IIS/10.0',
'Content-Type': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'Content-Length': stats.size,
'Last-Modified': stats.mtime.toUTCString(),
'ETag': `"${stats.mtime.getTime()}-${stats.size}"`,
'Accept-Ranges': 'bytes',
'Cache-Control': 'no-cache'
});
console.log(`📖 Word öffnet Template: ${filename}`);
res.sendFile(filePath);
} else {
res.status(404).send('Not Found');
}
});
app.get('/webdav/documents/:filename', (req, res) => {
const filename = req.params.filename;
const filePath = path.join(outputDir, filename);
if (fs.existsSync(filePath)) {
const stats = fs.statSync(filePath);
res.set({
'DAV': '1, 2, ordered-collections, versioning',
'MS-Author-Via': 'DAV',
'Server': 'Microsoft-IIS/10.0',
'Content-Type': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'Content-Length': stats.size,
'Last-Modified': stats.mtime.toUTCString(),
'ETag': `"${stats.mtime.getTime()}-${stats.size}"`,
'Accept-Ranges': 'bytes',
'Cache-Control': 'no-cache'
});
console.log(`📖 Word öffnet Dokument: ${filename}`);
res.sendFile(filePath);
} else {
res.status(404).send('Not Found');
}
});
// WebDAV LOCK/UNLOCK für Office
app.use('/webdav', (req, res, next) => {
if (req.method === 'LOCK') {
const lockToken = 'opaquelocktoken:' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);
const lockResponse = `<?xml version="1.0" encoding="utf-8"?>
<D:prop xmlns:D="DAV:">
<D:lockdiscovery>
<D:activelock>
<D:locktype><D:write/></D:locktype>
<D:lockscope><D:exclusive/></D:lockscope>
<D:depth>0</D:depth>
<D:timeout>Second-604800</D:timeout>
<D:locktoken><D:href>${lockToken}</D:href></D:locktoken>
</D:activelock>
</D:lockdiscovery>
</D:prop>`;
res.set({
'Content-Type': 'application/xml; charset=utf-8',
'Lock-Token': `<${lockToken}>`,
'DAV': '1, 2',
'MS-Author-Via': 'DAV'
});
res.status(200).send(lockResponse);
return;
}
if (req.method === 'UNLOCK') {
res.set({
'DAV': '1, 2',
'MS-Author-Via': 'DAV'
});
res.status(204).send();
return;
}
next();
});
app.use('/webdav/documents', express.static(outputDir, {
setHeaders: (res, path) => {
res.set({
'DAV': '1, 2',
'Allow': 'GET, PUT, DELETE, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, HEAD, OPTIONS',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, PUT, DELETE, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, HEAD, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Depth, Authorization, If-Match, If-None-Match, Overwrite, Destination',
'MS-Author-Via': 'DAV',
'Cache-Control': 'no-cache'
});
}
}));
// WebDAV PROPFIND Support für Word
app.use('/webdav/*', (req, res, next) => {
if (req.method === 'PROPFIND') {
res.set({
'Content-Type': 'application/xml; charset=utf-8',
'DAV': '1, 2',
'MS-Author-Via': 'DAV'
});
const propfindResponse = `<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:">
<D:response>
<D:href>${req.originalUrl}</D:href>
<D:propstat>
<D:status>HTTP/1.1 200 OK</D:status>
<D:prop>
<D:resourcetype><D:collection/></D:resourcetype>
<D:getcontenttype>httpd/unix-directory</D:getcontenttype>
<D:supportedlock>
<D:lockentry>
<D:lockscope><D:exclusive/></D:lockscope>
<D:locktype><D:write/></D:locktype>
</D:lockentry>
</D:supportedlock>
</D:prop>
</D:propstat>
</D:response>
</D:multistatus>`;
res.status(207).send(propfindResponse);
return;
}
next();
});
// SharePoint-spezifische Endpunkte
app.get('/_vti_inf.html', (req, res) => {
// SharePoint Info-Seite
res.set('Content-Type', 'text/html');
res.send(`<!-- FrontPage Configuration Information
FP_VER:15.0.0.0000
FP_SCHEMA_VER:15.0.0.0000
-->`);
});
app.post('/_vti_bin/lists.asmx', (req, res) => {
// SharePoint Lists Web Service
res.set({
'Content-Type': 'text/xml; charset=utf-8',
'SOAPAction': req.headers.soapaction || ''
});
const soapResponse = `<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<GetListCollectionResponse xmlns="http://schemas.microsoft.com/sharepoint/soap/">
<GetListCollectionResult>
<Lists>
<List DocTemplateUrl="" DefaultViewUrl="/templates" ID="{${crypto.randomUUID()}}" Title="Templates" Description="Document Templates" ImageUrl="/_layouts/images/itdl.gif" Name="{${crypto.randomUUID()}}" BaseType="1" ServerTemplate="101" Created="2025-10-02T19:00:00Z" Modified="2025-10-02T19:00:00Z" LastDeleted="1900-01-01T00:00:00Z" Version="1" Direction="none" ThumbnailSize="0" WebImageWidth="0" WebImageHeight="0" Flags="0" ItemCount="3" AnonymousPermMask="0" RootFolder="/webdav/templates" ReadSecurity="1" WriteSecurity="1" Author="1" InheritedSecurity="true" AllowMultiResponses="false" AllowDeletion="true" AllowAnonymousAccess="false" EnableAttachments="true" EnableModeration="false" EnableVersioning="false" Hidden="false" MultipleDataList="false" Ordered="false" ShowUser="true" EnablePeopleSelector="false" EnableResourceSelector="false" EnableMinorVersion="false" RequireCheckout="false" />
</Lists>
</GetListCollectionResult>
</GetListCollectionResponse>
</soap:Body>
</soap:Envelope>`;
res.send(soapResponse);
});
app.get('/_vti_bin/owssvr.dll', (req, res) => {
// SharePoint OWS Service
if (req.query.Cmd === 'Display' && req.query.List) {
res.set('Content-Type', 'text/xml');
res.send(`<?xml version="1.0" encoding="utf-8"?>
<xml xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema">
<s:Schema id="RowsetSchema">
<s:ElementType name="row" content="eltOnly" rs:updatable="true">
<s:AttributeType name="ows_Title" rs:name="Title" rs:number="1"/>
<s:AttributeType name="ows_Modified" rs:name="Modified" rs:number="2"/>
<s:AttributeType name="ows_Editor" rs:name="Editor" rs:number="3"/>
</s:ElementType>
</s:Schema>
<rs:data>
<z:row ows_Title="Templates" ows_Modified="2025-10-02T19:00:00Z" ows_Editor="System Account"/>
</rs:data>
</xml>`);
} else {
res.status(404).send('Not Found');
}
});
// SharePoint-kompatible WebDAV-Root
app.use('/sharepoint', (req, res, next) => {
// SharePoint-spezifische Header
res.set({
'MicrosoftSharePointTeamServices': '15.0.0.4569',
'SharePointHealthScore': '0',
'SPRequestGuid': `{${crypto.randomUUID()}}`,
'X-SharePoint-HealthScore': '0'
});
// Weiterleitung zu WebDAV
const newPath = req.path.replace('/sharepoint', '/webdav');
req.url = newPath;
next();
});
console.log('SharePoint-Kompatibilitätsmodus aktiviert:');
console.log('SharePoint WebDAV: http://localhost:' + (process.env.PORT || 80) + '/sharepoint/templates/');
console.log('SharePoint Info: http://localhost:' + (process.env.PORT || 80) + '/_vti_inf.html');
// Multer für File Upload
const upload = multer({ dest: 'uploads/' });
// Demo-Daten Generator
class DemoDataGenerator {
static generateData(tags) {
const data = {};
tags.forEach(tag => {
const lowerTag = tag.toLowerCase();
if (lowerTag.includes('name') || lowerTag.includes('vorname')) {
data[tag] = faker.person.firstName();
} else if (lowerTag.includes('nachname') || lowerTag.includes('surname')) {
data[tag] = faker.person.lastName();
} else if (lowerTag.includes('email') || lowerTag.includes('mail')) {
data[tag] = faker.internet.email();
} else if (lowerTag.includes('telefon') || lowerTag.includes('phone')) {
data[tag] = faker.phone.number();
} else if (lowerTag.includes('adresse') || lowerTag.includes('address')) {
data[tag] = faker.location.streetAddress();
} else if (lowerTag.includes('stadt') || lowerTag.includes('city')) {
data[tag] = faker.location.city();
} else if (lowerTag.includes('plz') || lowerTag.includes('postal')) {
data[tag] = faker.location.zipCode();
} else if (lowerTag.includes('land') || lowerTag.includes('country')) {
data[tag] = faker.location.country();
} else if (lowerTag.includes('datum') || lowerTag.includes('date')) {
data[tag] = faker.date.recent().toLocaleDateString('de-DE');
} else if (lowerTag.includes('betrag') || lowerTag.includes('preis') || lowerTag.includes('amount')) {
data[tag] = faker.commerce.price();
} else if (lowerTag.includes('firma') || lowerTag.includes('company')) {
data[tag] = faker.company.name();
} else if (lowerTag.includes('produkt') || lowerTag.includes('product')) {
data[tag] = faker.commerce.productName();
} else if (lowerTag.includes('beschreibung') || lowerTag.includes('description')) {
data[tag] = faker.lorem.paragraph();
} else if (lowerTag.includes('nummer') || lowerTag.includes('number') || lowerTag.includes('id')) {
data[tag] = faker.string.numeric(6);
} else {
// Fallback für unbekannte Tags
data[tag] = faker.lorem.words(2);
}
});
return data;
}
static generateTableData(tableStructure) {
const rows = Math.floor(Math.random() * 5) + 2; // 2-6 Zeilen
const tableData = [];
for (let i = 0; i < rows; i++) {
const row = {};
tableStructure.forEach(column => {
if (column.includes('position')) {
row[column] = (i + 1).toString(); // Fortlaufende Positionsnummer
} else {
row[column] = this.generateData([column])[column];
}
});
tableData.push(row);
}
return tableData;
}
}
// Template Tag Extractor
class TemplateTagExtractor {
static extractTags(docxBuffer) {
try {
const zip = new PizZip(docxBuffer);
const doc = new Docxtemplater(zip, {
paragraphLoop: true,
linebreaks: true,
});
// Alle Tags aus dem Template extrahieren
const tags = new Set();
const content = zip.file('word/document.xml').asText();
// Einfache Tags: {tag}
const simpleTagRegex = /{([^{}]+)}/g;
let match;
while ((match = simpleTagRegex.exec(content)) !== null) {
const tag = match[1].trim();
if (!tag.includes('#') && !tag.includes('/')) {
tags.add(tag);
}
}
// Loop Tags für Tabellen: {#items}...{/items}
const loopTagRegex = /{#(\w+)}/g;
const loopTags = [];
while ((match = loopTagRegex.exec(content)) !== null) {
loopTags.push(match[1]);
}
return {
simpleTags: Array.from(tags),
loopTags: loopTags
};
} catch (error) {
console.error('Fehler beim Extrahieren der Tags:', error);
return { simpleTags: [], loopTags: [] };
}
}
}
// Template Processor
class TemplateProcessor {
static async processTemplate(templatePath, outputPath, customData = null) {
try {
const content = fs.readFileSync(templatePath, 'binary');
const zip = new PizZip(content);
// Tags extrahieren
const { simpleTags, loopTags } = TemplateTagExtractor.extractTags(Buffer.from(content, 'binary'));
// Daten generieren oder verwenden
let data = customData || {};
if (!customData) {
// Demo-Daten für einfache Tags generieren
data = DemoDataGenerator.generateData(simpleTags);
// Demo-Daten für Loop Tags (Tabellen) generieren
loopTags.forEach(loopTag => {
// Erweiterte Tabellen-Spalten für professionelle Rechnung
const tableColumns = [`${loopTag}_position`, `${loopTag}_name`, `${loopTag}_value`, `${loopTag}_date`];
data[loopTag] = DemoDataGenerator.generateTableData(tableColumns);
});
}
const doc = new Docxtemplater(zip, {
paragraphLoop: true,
linebreaks: true,
});
doc.render(data);
const buf = doc.getZip().generate({
type: 'nodebuffer',
compression: 'DEFLATE',
});
fs.writeFileSync(outputPath, buf);
return {
success: true,
data: data,
extractedTags: { simpleTags, loopTags }
};
} 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; }
.container { max-width: 800px; }
.section { margin: 20px 0; padding: 20px; border: 1px solid #ddd; border-radius: 5px; }
.button { background: #007cba; color: white; padding: 10px 20px; text-decoration: none; border-radius: 3px; display: inline-block; margin: 5px; }
.button:hover { background: #005a87; }
input[type="file"] { margin: 10px 0; }
.info { background: #f0f8ff; padding: 10px; border-left: 4px solid #007cba; margin: 10px 0; }
</style>
</head>
<body>
<div class="container">
<h1>DOCX Template Server</h1>
<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 hochladen und verarbeiten" class="button">
</form>
</div>
<div class="section">
<h2>📁 Office-Integration</h2>
<div class="info">
<strong>Word WebDAV Templates:</strong> <a href="/webdav/templates/" target="_blank">http://localhost:${PORT}/webdav/templates/</a><br>
<strong>Word WebDAV Dokumente:</strong> <a href="/webdav/documents/" target="_blank">http://localhost:${PORT}/webdav/documents/</a><br>
${fs.existsSync(path.join(__dirname, '203_key.pem')) && fs.existsSync(path.join(__dirname, '203_cert.pem')) ?
`<strong>Templates (HTTPS):</strong> <a href="https://localhost:443/webdav/templates/" target="_blank">https://localhost:443/webdav/templates/</a><br>
<strong>Dokumente (HTTPS):</strong> <a href="https://localhost:443/webdav/documents/" target="_blank">https://localhost:443/webdav/documents/</a>` :
'<em>HTTPS verfügbar sobald SSL-Zertifikate (203_cert.pem und 203_key.pem) im Projektverzeichnis vorhanden sind</em>'}
</div>
<p><strong>Office-Integration:</strong> Öffnen Sie in Word/Excel: Datei → Öffnen → Netzwerk hinzufügen → WebDAV → http://localhost:${PORT}/webdav/templates/</p>
<p><strong>Direkt speichern:</strong> Word speichert automatisch auf dem Server wenn über WebDAV geöffnet.</p>
</div>
<div class="section">
<h2>🔧 API Endpunkte</h2>
<ul>
<li><a href="/api/templates" class="button">Templates auflisten</a></li>
<li><a href="/api/documents" class="button">Dokumente auflisten</a></li>
</ul>
<p><strong>Word-Integration:</strong> Die API liefert spezielle Links für Microsoft Word:</p>
<ul>
<li><code>wordWebdavUrl</code> - Direkt in Word öffnen (ms-word: Protocol)</li>
<li><code>wordDirectUrl</code> - Alternative Word-Integration</li>
<li><code>webdavDirect</code> - Windows UNC-Pfad für Netzlaufwerk</li>
</ul>
</div>
<div class="section">
<h2>📋 Test Template</h2>
<a href="/create-test-template" class="button">Test Template erstellen</a>
<p>Erstellt ein Beispiel-Template mit verschiedenen Tag-Typen für Tests.</p>
</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);
// Template in Templates-Verzeichnis kopieren
fs.copyFileSync(req.file.path, templatePath);
// Template verarbeiten
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({
message: 'Template erfolgreich verarbeitet',
templateName: templateName,
outputName: outputName,
extractedTags: result.extractedTags,
generatedData: result.data,
fileUrls: {
template: `${protocol}://${host}/webdav/templates/${templateName}`,
document: `${protocol}://${host}/webdav/documents/${outputName}`,
wordWebdavTemplate: `ms-word:ofe|u|${protocol}://${host}/webdav/templates/${encodeURIComponent(templateName)}`,
wordWebdavDocument: `ms-word:ofe|u|${protocol}://${host}/webdav/documents/${encodeURIComponent(outputName)}`,
wordDirectTemplate: `word:ofe|u|${protocol}://${host}/webdav/templates/${encodeURIComponent(templateName)}`,
wordDirectDocument: `word:ofe|u|${protocol}://${host}/webdav/documents/${encodeURIComponent(outputName)}`
}
});
} else {
res.status(500).json({ error: result.error });
}
} catch (error) {
console.error('Upload Fehler:', error);
res.status(500).json({ error: 'Server Fehler beim Upload' });
}
});
// Test Template erstellen
app.get('/create-test-template', (req, res) => {
const PizZip = require('pizzip');
// Minimales DOCX Template erstellen
const testContent = `
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:body>
<w:p><w:r><w:t>Firmenname: {firma}</w:t></w:r></w:p>
<w:p><w:r><w:t>Ansprechpartner: {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>Telefon: {telefon}</w:t></w:r></w:p>
<w:p><w:r><w:t>Adresse: {adresse}, {plz} {stadt}</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>Rechnungsnummer: {nummer}</w:t></w:r></w:p>
<w:p><w:r><w:t>Positionen:</w:t></w:r></w:p>
<w:p><w:r><w:t>{#items}</w:t></w:r></w:p>
<w:p><w:r><w:t>- {items_name}: {items_value} EUR ({items_date})</w:t></w:r></w:p>
<w:p><w:r><w:t>{/items}</w:t></w:r></w:p>
<w:p><w:r><w:t>Gesamtbetrag: {betrag} EUR</w:t></w:r></w:p>
<w:p><w:r><w:t>Beschreibung:</w:t></w:r></w:p>
<w:p><w:r><w:t>{beschreibung}</w:t></w:r></w:p>
</w:body>
</w:document>
`;
try {
// Minimal DOCX Struktur
const zip = new PizZip();
// document.xml
zip.file('word/document.xml', testContent);
// [Content_Types].xml
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>`);
// _rels/.rels
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>`);
// word/_rels/document.xml.rels
zip.file('word/_rels/document.xml.rels', `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
</Relationships>`);
const buffer = zip.generate({ type: 'nodebuffer' });
const testTemplatePath = path.join(templateDir, 'test_template.docx');
fs.writeFileSync(testTemplatePath, buffer);
res.json({
message: 'Test Template erstellt',
templatePath: 'test_template.docx',
fileUrl: `http://localhost:${PORT}/webdav/templates/test_template.docx`,
info: 'Sie können das Template jetzt über den Datei-Server herunterladen, bearbeiten und wieder hochladen.'
});
} catch (error) {
console.error('Fehler beim Erstellen des Test Templates:', error);
res.status(500).json({ error: 'Fehler beim Erstellen des Test Templates' });
}
});
// API Endpunkte
app.get('/api/templates', (req, res) => {
try {
const files = fs.readdirSync(templateDir).filter(file => file.endsWith('.docx'));
const protocol = req.secure ? 'https' : 'http';
const host = req.get('host') || `localhost:${PORT}`;
res.json({
templates: files.map(file => ({
name: file,
fileUrl: `${protocol}://${host}/webdav/templates/${file}`,
wordWebdavUrl: `ms-word:ofe|u|${protocol}://${host}/webdav/templates/${encodeURIComponent(file)}`,
wordDirectUrl: `word:ofe|u|${protocol}://${host}/webdav/templates/${encodeURIComponent(file)}`,
downloadUrl: `${protocol}://${host}/webdav/templates/${file}`,
webdavDirect: `\\\\${host.split(':')[0]}@${PORT}\\webdav\\templates\\${file}`
}))
});
} catch (error) {
res.status(500).json({ error: 'Fehler beim Auflisten der Templates' });
}
});
app.get('/api/documents', (req, res) => {
try {
const files = fs.readdirSync(outputDir).filter(file => file.endsWith('.docx'));
const protocol = req.secure ? 'https' : 'http';
const host = req.get('host') || `localhost:${PORT}`;
res.json({
documents: files.map(file => ({
name: file,
fileUrl: `${protocol}://${host}/webdav/documents/${file}`,
wordWebdavUrl: `ms-word:ofe|u|${protocol}://${host}/webdav/documents/${encodeURIComponent(file)}`,
wordDirectUrl: `word:ofe|u|${protocol}://${host}/webdav/documents/${encodeURIComponent(file)}`,
downloadUrl: `${protocol}://${host}/webdav/documents/${file}`,
webdavDirect: `\\\\${host.split(':')[0]}@${PORT}\\webdav\\documents\\${file}`,
createdAt: fs.statSync(path.join(outputDir, file)).mtime
}))
});
} catch (error) {
res.status(500).json({ error: 'Fehler beim Auflisten der Dokumente' });
}
});
// Template mit benutzerdefinierten Daten verarbeiten
app.post('/api/process-template/:templateName', (req, res) => {
try {
const templateName = req.params.templateName;
const templatePath = path.join(templateDir, templateName);
if (!fs.existsSync(templatePath)) {
return res.status(404).json({ error: 'Template nicht gefunden' });
}
const outputName = templateName.replace('.docx', '_custom.docx');
const outputPath = path.join(outputDir, outputName);
TemplateProcessor.processTemplate(templatePath, outputPath, req.body).then(result => {
if (result.success) {
res.json({
message: 'Template mit benutzerdefinierten Daten verarbeitet',
outputName: outputName,
fileUrl: `http://localhost:${PORT}/webdav/documents/${outputName}`
});
} else {
res.status(500).json({ error: result.error });
}
});
} catch (error) {
res.status(500).json({ error: 'Fehler beim Verarbeiten des Templates' });
}
});
// SSL Konfiguration
const certPath = path.join(__dirname, '203_cert.pem');
const keyPath = path.join(__dirname, '203_key.pem');
if (fs.existsSync(keyPath) && fs.existsSync(certPath)) {
try {
const sslOptions = {
key: fs.readFileSync(keyPath),
cert: fs.readFileSync(certPath)
};
https.createServer(sslOptions, app).listen(443, () => {
console.log('🔒 HTTPS Server läuft auf Port 443');
console.log('🌐 HTTPS Zugang: https://localhost:443');
});
} catch (error) {
console.warn('⚠️ SSL-Zertifikate gefunden, aber Fehler beim Laden:', error.message);
console.log(' Server läuft nur mit HTTP');
}
} else {
console.log(' SSL-Zertifikate nicht gefunden - Server läuft nur mit HTTP');
console.log('💡 Für HTTPS: Platzieren Sie 203_cert.pem und 203_key.pem in /home/OfficeServerJS/');
}
// Server starten
app.listen(PORT, () => {
console.log(`\n🚀 DOCX Template Server gestartet!`);
console.log(`📍 HTTP Server: http://localhost:${PORT}`);
console.log(`📁 Templates: http://localhost:${PORT}/webdav/templates/`);
console.log(`📁 Documents: http://localhost:${PORT}/webdav/documents/`);
console.log(`\n💡 Tipp: Besuchen Sie http://localhost:${PORT} für die Web-Oberfläche`);
// SSL-Status anzeigen
const certPath = path.join(__dirname, '203_cert.pem');
const keyPath = path.join(__dirname, '203_key.pem');
if (fs.existsSync(keyPath) && fs.existsSync(certPath)) {
console.log(`🔒 HTTPS auch verfügbar: https://localhost:443`);
}
});
module.exports = app;