# Skrift Backend - Handwritten Document Generator Docker-basiertes Backend für die Generierung von handschriftlichen Dokumenten (Briefe, Postkarten, Umschläge) mit SVG-Output. ## Features - **Preview-System**: Batch-Generierung von Vorschauen mit Caching (30 Briefe pro Batch) - **Scriptalizer Integration**: Nutzt externe API für natürliche Handschrift-Variationen - **SVG-Generierung**: Eigene Font-Engine für hochqualitative SVG-Ausgabe - **Multi-Format Support**: A4, A6 (Hoch-/Querformat), C6, DIN Lang Umschläge - **Platzhalter-System**: Automatische Ersetzung von `[[Platzhalter]]` mit CSV-Export - **Rate Limiting**: Schutz vor API-Spam (konfigurierbar) - **Docker-Ready**: Vollständig containerisiert mit docker-compose ## Quick Start ### 1. Konfiguration ```bash cp .env.example .env # .env bearbeiten und Scriptalizer License Key eintragen ``` ### 2. Lokal testen (ohne Docker) ```bash npm install npm start ``` Server läuft auf: `http://localhost:4000` ### 3. Mit Docker deployen ```bash docker-compose up -d ``` ## API-Endpunkte ### Health Check ``` GET /health ``` **Response:** ```json { "status": "healthy", "timestamp": "2026-01-15T12:00:00Z", "uptime": 12345, "scriptalizer": "configured", "storage": { "cache": true, "output": true } } ``` --- ### Preview Batch Generierung ``` POST /api/preview/batch ``` **Request Body:** ```json { "sessionId": "uuid-abc-123", "batchIndex": 0, "forceRegenerate": false, "config": { "font": "tilda", "letters": [ { "index": 0, "format": "a4", "text": "Hallo [[Vorname]], dein Code ist [[Gutscheincode]]...", "placeholders": { "Vorname": "Max", "Nachname": "Mustermann", "Strasse": "Hauptstr. 1", "PLZ": "10115", "Ort": "Berlin", "Gutscheincode": "SAVE20" } } ], "envelopes": [ { "index": 0, "format": "c6", "type": "recipient", "data": { "Vorname": "Max", "Nachname": "Mustermann", "Strasse": "Hauptstr. 1", "PLZ": "10115", "Ort": "Berlin" } } ] } } ``` **Response:** ```json { "sessionId": "uuid-abc-123", "files": [ { "type": "letter", "index": 0, "url": "/api/preview/uuid-abc-123/letter_000.svg" }, { "type": "envelope", "index": 0, "url": "/api/preview/uuid-abc-123/envelope_000.svg" } ], "csvUrl": "/api/preview/uuid-abc-123/platzhalter.csv", "expiresAt": "2026-01-15T14:00:00Z" } ``` **Rate Limit:** 2 Requests/Minute pro sessionId --- ### Preview-Datei abrufen ``` GET /api/preview/:sessionId/:filename ``` **Beispiel:** ``` GET /api/preview/uuid-abc-123/letter_000.svg ``` **Response:** SVG-Datei (Content-Type: image/svg+xml) --- ### Bestellung finalisieren (aus Cache) ``` POST /api/order/finalize ``` **Request Body:** ```json { "sessionId": "uuid-abc-123", "orderNumber": "SK-2026-01-15-001" } ``` **Response:** ```json { "orderNumber": "SK-2026-01-15-001", "outputPath": "/app/output/SK-2026-01-15-001", "files": { "letters": 100, "envelopes": 100, "csv": "platzhalter.csv" }, "timestamp": "2026-01-15T12:30:00Z" } ``` --- ### Bestellung neu generieren (ohne Cache) ``` POST /api/order/generate ``` **Request Body:** ```json { "orderNumber": "SK-2026-01-15-002", "config": { "font": "tilda", "letters": [...], "envelopes": [...] } } ``` **Response:** Gleich wie `/api/order/finalize` --- ## Formate ### Schriftstücke (Letters) - `a4` - A4 Hochformat (210 × 297 mm) - `a6p` - A6 Hochformat (105 × 148 mm) - `a6l` - A6 Querformat (148 × 105 mm) ### Umschläge (Envelopes) - `c6` - C6 Umschlag (162 × 114 mm) - `din_lang` - DIN Lang Umschlag (220 × 110 mm) ### Fonts - `tilda` - PremiumUltra79 - `alva` - PremiumUltra23 - `ellie` - PremiumUltra39 --- ## Umschlag-Typen ### Empfänger-Adresse (type: "recipient") Adresse wird **unten links** positioniert (kein Sichtfenster). ```json { "type": "recipient", "data": { "Vorname": "Max", "Nachname": "Mustermann", "Strasse": "Hauptstr. 1", "PLZ": "10115", "Ort": "Berlin" } } ``` ### Individueller Text (type: "custom") Text wird **mittig zentriert** positioniert. Max. 150 Zeichen. ```json { "type": "custom", "data": { "customText": "Für meine großartige Freundin Caro" } } ``` --- ## Verzeichnisstruktur ``` /app/ ├── cache/ │ └── previews/ │ └── {sessionId}/ │ ├── letter_000.svg │ ├── envelope_000.svg │ ├── platzhalter.csv │ └── metadata.json │ ├── output/ │ └── {orderNumber}/ │ ├── schriftstuecke/ │ │ ├── brief_000.svg │ │ └── ... │ ├── umschlaege/ │ │ ├── umschlag_000.svg │ │ └── ... │ ├── platzhalter.csv │ └── order-metadata.json │ └── fonts/ ├── tilda.svg ├── alva.svg └── ellie.svg ``` --- ## Umgebungsvariablen ```bash # Node Environment NODE_ENV=production # Scriptalizer API SCRIPTALIZER_LICENSE_KEY=your-key-here SCRIPTALIZER_ERR_FREQUENCY=10 # Preview System BATCH_SIZE=30 CACHE_LIFETIME_HOURS=2 RATE_LIMIT_PER_MINUTE=2 # Server PORT=4000 CORS_ORIGIN=* ``` --- ## Deployment ### Auf Server (mit Docker) ```bash # .env Datei erstellen mit production values docker-compose up -d # Logs ansehen docker-compose logs -f # Stoppen docker-compose down ``` ### Nginx Proxy Manager Setup 1. Proxy Host erstellen 2. Domain: `api.skrift.de` (oder deine Domain) 3. Forward Hostname/IP: `localhost` 4. Forward Port: `4000` 5. SSL Zertifikat über NPM erstellen --- ## Entwicklung ### Lokales Testen ```bash npm run dev # Mit nodemon ``` ### Scriptalizer Separator Test ```bash npm run test:separator ``` ### Logs ```bash # Docker logs docker-compose logs -f skrift-backend # Lokale logs # Output in console ``` --- ## Integration mit N8N N8N kann direkt auf den `/app/output/{orderNumber}/` Ordner zugreifen: ```javascript // N8N Workflow (Beispiel) const fs = require('fs'); const orderPath = '/var/skrift-output/SK-2026-01-15-001'; // Lese alle SVGs const letters = fs.readdirSync(`${orderPath}/schriftstuecke`); // Sende an Plotter for (const file of letters) { await sendToPlotter(`${orderPath}/schriftstuecke/${file}`); } ``` --- ## Fehlerbehandlung ### HTTP Status Codes - `200` - Success - `400` - Bad Request (z.B. ungültige Parameter) - `404` - Not Found (z.B. Session nicht gefunden) - `410` - Gone (z.B. Cache abgelaufen) - `429` - Too Many Requests (Rate Limit) - `500` - Internal Server Error - `503` - Service Unavailable (z.B. Scriptalizer down) ### Typische Fehler **Rate Limit überschritten:** ```json { "error": "Zu viele Vorschau-Anfragen. Bitte warten Sie.", "retryAfter": 45, "message": "Limit: 2 Anfragen pro Minute" } ``` **Scriptalizer Fehler:** ```json { "error": "Scriptalizer request failed: timeout" } ``` **Cache abgelaufen:** ```json { "error": "Preview-Session abgelaufen. Bitte neu generieren." } ``` --- ## Limits - **Scriptalizer API**: 10.000 Calls/Tag - **Batch Size**: 30 Briefe pro Request - **Input Size**: 48KB pro Scriptalizer Call - **Rate Limit**: 2 Preview-Requests/Minute - **Cache Lifetime**: 2 Stunden --- ## Troubleshooting ### Fonts nicht gefunden ```bash # Fonts kopieren cp /path/to/fonts/*.svg ./fonts/ ``` ### Scriptalizer API Fehler ```bash # License Key prüfen cat .env | grep SCRIPTALIZER_LICENSE_KEY # Test-Script ausführen npm run test:separator ``` ### Permissions Fehler ```bash # Cache/Output Ordner Permissions chmod -R 755 cache output ``` --- ## Weitere Infos - **Scriptalizer API**: [www.scriptalizer.co.uk](https://www.scriptalizer.co.uk) - **Support**: Siehe Issues in Repository --- **Version**: 1.0.0 **Last Updated**: 2026-01-01