7a1329958d
- fix(render): ffmpeg overlay=0:0 -> overlay=0:0:shortest=1 to prevent hang on finite PNG sequences - feat(ws): add core/websocket.py ConnectionManager + Redis Pub/Sub subscriber loop - feat(ws): add /api/ws WebSocket endpoint with JWT query-param auth in main.py - feat(ws): emit render_complete/failed + cad_processing_complete events from step_tasks.py - feat(ws): emit order_status_change events from orders router - feat(ws): add beat_tasks.py broadcast_queue_status task (every 10s via Redis __broadcast__) - feat(frontend): add useWebSocket hook with auto-reconnect (exponential backoff, 25s ping) - feat(frontend): add WebSocketContext + WebSocketProvider wrapping App - refactor(frontend): remove polling from WorkerActivity (was 5s/3s) + OrderDetail (was 5s) - refactor(frontend): reduce polling in Layout (8s->60s) + NotificationCenter (15s->60s) - docs: add ffmpeg shortest=1 + WebSocket JWT auth learnings to LEARNINGS.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
268 lines
23 KiB
Markdown
268 lines
23 KiB
Markdown
# Projekt-Learnings — Schaeffler Automat
|
|
|
|
## Format
|
|
**Datum | Kategorie | Problem → Lösung**
|
|
|
|
---
|
|
|
|
## Learnings
|
|
|
|
### 2026-01-15 | Architektur | Backend-Port-Konflikt
|
|
**Problem:** FastAPI standardmäßig auf Port 8000 — war auf dem Entwicklungsrechner belegt
|
|
**Lösung:** Port 8888 in `docker-compose.yml` und Vite-Proxy konfiguriert
|
|
**Für künftige Projekte:** Port früh festlegen und in CLAUDE.md dokumentieren
|
|
|
|
---
|
|
|
|
### 2026-01-20 | Datenbank | SQLAlchemy trackt key-value-Store-Mutations nicht
|
|
**Problem:** Admin-Einstellungen (`system_settings`) wurden via ORM gespeichert, Änderungen wurden nicht persistiert
|
|
**Ursache:** SQLAlchemy erkennt keine Mutation an einem bereits geladenen Objekt wenn nur ein Value-Feld geändert wird
|
|
**Lösung:** Direktes SQL `UPDATE` via `op.execute()` statt ORM-Mutation in `admin.py`
|
|
**Für künftige Projekte:** Key-Value-Stores immer mit direktem SQL oder `session.execute(update(...))` verwalten
|
|
|
|
---
|
|
|
|
### 2026-01-25 | Render-Pipeline | Blender ignoriert STEP-Einheiten (mm vs. m)
|
|
**Problem:** STEP-Dateien sind in Millimetern, Blender arbeitet intern in Metern → 50mm-Lager erscheint 50 Meter breit, Kamera framt falsch
|
|
**Lösung:** `_scale_mm_to_m(parts)` Helper in allen 3 Render-Scripts: `part.scale = (0.001, 0.001, 0.001)`, Transform anwenden
|
|
**Betroffene Dateien:** `blender_render.py`, `still_render.py`, `turntable_render.py`
|
|
**Für künftige Projekte:** Einheiten-Konvertierung direkt nach STL-Import, vor jeder Kamera-Kalkulation
|
|
|
|
---
|
|
|
|
### 2026-01-28 | Render-Pipeline | Blender 5.0 hat `scene.node_tree` entfernt
|
|
**Problem:** `_setup_bg_compositor()` rief `scene.node_tree` auf (in Blender 5.0 entfernt) → Python-Exception → Blender exitete mit Code 0 → Flamenco markierte Task fälschlicherweise als "completed"
|
|
**Lösung:** `_setup_bg_compositor()` aus Setup + Render-Script entfernt; bg_color-Kompositing in FFmpeg verschoben (`-f lavfi -i color=...` + overlay-Filter)
|
|
**Wichtig:** Immer `try: main() except SystemExit: raise except Exception: traceback; sys.exit(1)` in Blender-Scripts — sonst verschluckt Blender Python-Exceptions
|
|
**Für künftige Projekte:** Nach Blender-Major-Updates alle API-Calls prüfen; Exception-Guard ist Pflicht
|
|
|
|
---
|
|
|
|
### 2026-02-05 | Material-System | Material-Alias-Lookup-Reihenfolge falsch
|
|
**Problem:** `Steel--Stahl` war sowohl ein kanonischer `Material.name` als auch ein Alias für `SCHAEFFLER_010101_Steel-Bare`. Der Lookup prüfte zuerst den exakten Namen und fand `Steel--Stahl` — Blender konnte diesen Namen aber nicht in der Library finden
|
|
**Lösung:** Lookup-Reihenfolge in `material_service.py` umgekehrt: **Aliases zuerst**, dann exakter Name, dann Pass-through
|
|
**Für künftige Projekte:** Alias-System immer so designen dass Aliases Vorrang haben; nie zwei Lookup-Pfade mit überlappenden Treffern
|
|
|
|
---
|
|
|
|
### 2026-02-10 | Render-Pipeline | Blender-Template zerstört HDRI/World
|
|
**Problem:** Im Template-Modus (Mode B) wurden trotzdem Auto-Lights und eine neue World erstellt → überschrieb den HDRI aus dem .blend-Template → falsche Beleuchtung
|
|
**Ursache:** Auto-Licht- und World-Setup-Code lief bedingungslos, nicht nur im Mode A
|
|
**Lösung:** In Template-Mode werden Lights, World und Color-Management-Override vollständig übersprungen; nur die Kamera wird ggf. neu berechnet
|
|
**Betroffene Dateien:** `still_render.py`, `turntable_render.py`, `schaeffler-still.js`, `schaeffler-turntable.js`
|
|
|
|
---
|
|
|
|
### 2026-02-15 | Celery | Blender-Queue-Flooding durch falsche Concurrency
|
|
**Problem:** Alle Celery-Tasks (schnelle Metadata-Extraktion + langsamer Blender-Render) liefen auf `step_processing` mit concurrency=8 → 8 Workers schickten gleichzeitig Requests an blender-renderer (der nur 1 gleichzeitig verarbeiten kann) → 7 davon liefen in 300s-Timeout → blockierte die gesamte Queue
|
|
**Lösung:** Pipeline aufgeteilt:
|
|
- `process_step_file` (step_processing, concurrency=8): nur schnelle Metadata-Extraktion (<2s), queut dann →
|
|
- `render_step_thumbnail` (thumbnail_rendering, concurrency=1): Blender-Call, niemals timeout
|
|
**Neuer Service:** `worker-thumbnail` in `docker-compose.yml` mit `--concurrency=1`
|
|
**Für künftige Projekte:** HTTP-Services die nur 1 Request gleichzeitig verarbeiten können IMMER auf einer separaten Queue mit concurrency=1 laufen lassen
|
|
|
|
---
|
|
|
|
### 2026-02-18 | Frontend | Tailwind CSS-Variablen inkompatibel mit opacity-Syntax
|
|
**Problem:** `bg-surface/50` oder `bg-surface` (wenn `--color-bg-surface` ein Hex-Wert ist) generiert `rgb(var(--color-bg-surface) / 0.5)` — invalides CSS, weil `rgb()` keine Hex-Werte als Channel-Input akzeptiert → Hintergrund transparent
|
|
**Ursache:** Tailwind erwartet CSS-Variablen mit RGB-Channel-Format (`255 255 255`), nicht Hex (`#ffffff`)
|
|
**Lösung:** Inline-Style verwenden: `style={{ backgroundColor: 'var(--color-bg-surface)' }}`
|
|
**Für künftige Projekte:** Entweder CSS-Variablen im RGB-Channel-Format definieren, oder konsequent inline styles für variable Farben
|
|
|
|
---
|
|
|
|
### 2026-02-20 | STL-Cache | Three.js-Renderer nutzte tempfile → kein Download möglich
|
|
**Problem:** Three.js-Renderer konvertierte STEP→STL in ein tempfile und löschte es anschließend → STL-Download-Endpoint fand keine Datei
|
|
**Ursache:** Three.js war ursprünglich nur für Thumbnails gebaut, STL-Cache-Konvention (`{stem}_low.stl` neben STEP-Datei) wurde nicht implementiert
|
|
**Lösung:** Persistent cache path: `step_path.parent / f"{step_path.stem}_low.stl"`, cache-hit-check vor Konvertierung, kein `unlink()` mehr
|
|
**Für künftige Projekte:** STL-Cache-Konvention (`{step_stem}_{quality}.stl` neben STEP-Datei) von Anfang an in allen Renderer-Services einhalten
|
|
|
|
---
|
|
|
|
### 2026-02-20 | STL-Cache | blender-renderer fehlte /convert-stl Endpoint
|
|
**Problem:** Für Produkte die mit Blender gerendert wurden war kein STL-Cache vorhanden wenn nicht explizit gerendert wurde (blender-renderer renderte + konvertierte in einem Schritt, aber STL wurde nicht persistiert)
|
|
**Lösung:** Neuer `/convert-stl` Endpoint in `blender-renderer/app.py`: konvertiert STEP→STL ohne Render, persistiert Cache. Neuer Celery-Task `generate_stl_cache` auf `thumbnail_rendering`-Queue. Admin-Funktion "Generate Missing STLs" zum Batch-Nachfüllen
|
|
|
|
---
|
|
|
|
### 2026-02-22 | Material-System | Fehlender Alias blockiert Material-Replacement
|
|
**Problem:** Produkt F-803422.01.TR2 (SA-2026-00080) renderte ohne Materialersetzung. Material "Stahl v2" war korrekt in der UI gespeichert, aber weder in `materials` noch in `material_aliases` vorhanden
|
|
**Ursache:** Alias-Seeding aus Excel deckte nicht alle Varianten der deutschen Materialbezeichnungen ab
|
|
**Lösung:** Alias direkt in DB eingetragen: `"Stahl v2"` → `SCHAEFFLER_010101_Steel-Bare`
|
|
**Für künftige Projekte:** Bei Render ohne Materialersetzung immer zuerst `resolve_material_map()` debuggen und Alias-Tabelle prüfen; Alias-Seeding regelmäßig mit neuen Excel-Varianten erweitern
|
|
|
|
---
|
|
|
|
### 2026-02-25 | Frontend | canDispatch-Bedingung zu restriktiv
|
|
**Problem:** "Dispatch Renders"-Button war nicht sichtbar obwohl der Auftrag offene Render-Zeilen hatte
|
|
**Ursache:** `canDispatch` enthielt `&& hasRetryable` — Button erschien nur wenn pending/failed/cancelled-Zeilen vorhanden waren, nicht wenn alle Zeilen "pending" im Erstauftrag
|
|
**Lösung:** `hasRetryable`-Bedingung entfernt; Button ist immer sichtbar wenn Auftrag im richtigen Status und User privilegiert ist
|
|
**Für künftige Projekte:** Aktions-Buttons nicht zu stark von abgeleiteten Zuständen abhängig machen; lieber im Backend validieren
|
|
|
|
---
|
|
|
|
### 2026-02-28 | Frontend | MaterialInput-Dropdown ohne Hintergrund
|
|
**Problem:** Dropdown der Material-Suchfeld-Komponente erschien transparent — Text über dem Hintergrund kaum lesbar
|
|
**Ursache:** `bg-surface` Tailwind-Klasse + CSS-Variable mit Hex-Wert (siehe Learning 2026-02-18)
|
|
**Lösung:** `style={{ backgroundColor: 'var(--color-bg-surface)' }}` für Dropdown-Container, Group-Header und Sticky-Button
|
|
**Datei:** `frontend/src/components/shared/MaterialInput.tsx`
|
|
|
|
---
|
|
|
|
### 2026-03-06 | Refactor | .gitignore `core` trifft Verzeichnisse
|
|
**Problem:** `.gitignore` enthielt `core` als Regel (für core dump files) — Git ignorierte damit auch `backend/app/core/` Verzeichnis
|
|
**Lösung:** Regel zu `/core` umbenannt (Root-relative Regel trifft nur `/core` Datei, nicht verschachtelte `core/`-Verzeichnisse)
|
|
**Für künftige Projekte:** Immer Root-relative Pfade (`/core`) für Dateien im Root-Verzeichnis nutzen
|
|
|
|
---
|
|
|
|
### 2026-03-06 | Architektur | Blender-HTTP-Service vs. direkter Subprocess
|
|
**Problem:** `blender-renderer` als Flask/FastAPI HTTP-Microservice war ein Single-Point-of-Failure (max. 1 concurrent Request), kein Scaling möglich, HTTP-Overhead bei jedem Render
|
|
**Lösung:** Render-Worker als Celery-Container (`render-worker/`) — Blender direkt via `subprocess.run` ohne HTTP. `is_blender_available()` prüft `BLENDER_BIN` env var für Kontext-Detection
|
|
**Wichtig:** `step_processor.py` erkennt über `BLENDER_BIN`-Env ob Blender im aktuellen Container verfügbar ist — Backend-Container fallen auf Pillow zurück
|
|
**Für künftige Projekte:** Subprocess-basierter Renderer > HTTP-Microservice für blocking compute tasks
|
|
|
|
---
|
|
|
|
### 2026-03-06 | Refactor | Bash CWD-Problem durch Hook-Pfad-Auflösung
|
|
**Problem:** Nach `cd frontend && npm test` in einem Bash-Tool-Call blieb CWD dauerhaft in `frontend/`. Der Pre-Tool-Use-Hook `python3 .claude/hooks/pre_tool_use.py` wurde dann relativ zu `frontend/` aufgelöst → Datei nicht gefunden → alle Tool-Calls blockiert
|
|
**Lösung:** Symlink `frontend/.claude → .claude` erstellt: `ln -sf $(pwd)/.claude frontend/.claude`
|
|
**Für künftige Projekte:** Hooks nie mit relativen Pfaden konfigurieren; absoluten Pfad im Hook-Command verwenden. Außerdem: `cd` immer in separate Bash-Calls oder mit `&&` am Ende der eigentlichen Command-Chain
|
|
|
|
---
|
|
|
|
### 2026-03-06 | Multi-Tenancy | PostgreSQL RLS mit current_setting und Null-Safety
|
|
**Problem:** `current_setting('app.current_tenant_id')` wirft Exception wenn Variable nicht gesetzt → alle Queries schlagen fehl wenn kein Tenant-Context gesetzt ist
|
|
**Lösung:** `current_setting('app.current_tenant_id', true)` — zweites Argument `true` macht die Funktion Null-safe: gibt NULL statt Exception zurück wenn Setting nicht gesetzt
|
|
**Admin-Bypass-Pattern:** Separates `CREATE POLICY admin_bypass ... USING (current_setting(...) = 'bypass')` — setzt `app.current_tenant_id = 'bypass'` für Admin-Cross-Tenant-Queries
|
|
**Für künftige Projekte:** IMMER das zweite `true`-Argument verwenden; Policies immer testen mit (a) gesetztem Tenant, (b) nicht gesetztem Setting, (c) Admin-Bypass
|
|
|
|
---
|
|
|
|
### 2026-03-06 | Refactor | Domain-Driven Migration: Compat-Shims statt Big-Bang
|
|
**Problem:** Vollständige Migration aller Models/Services/Router in neue Domain-Struktur in einem Schritt → alle bestehenden Imports brechen
|
|
**Lösung:** Compat-Shims-Ansatz: alte Dateien (`app/models/user.py` etc.) werden zu Re-Export-Wrappern die aus den neuen Domain-Locations importieren. So funktionieren alle bestehenden Imports weiter während die kanonische Location die neue Domain ist
|
|
**Pattern:**
|
|
```python
|
|
# app/models/user.py (Compat-Shim)
|
|
from app.domains.auth.models import User
|
|
__all__ = ["User"]
|
|
```
|
|
**Für künftige Projekte:** Immer Compat-Shims anlegen vor dem Verschieben; erst nach vollständiger Migration aller Imports die Shims entfernen
|
|
|
|
---
|
|
|
|
### 2026-03-06 | Workflow-System | Celery Canvas vs. Custom Workflow-Engine
|
|
**Problem:** Custom Workflow-Engine (Graph-Traversal, Dependency-Resolution, Retry-Logic) war zu komplex (~2-3 Wochen Eigenentwicklung)
|
|
**Lösung:** Celery Canvas als Execution-Engine (`chain`, `group`, `chord`). `dispatch_workflow(type, order_line_id, params)` baut den Canvas dynamisch aus Config-Typ. Backward-Compat: wenn kein `workflow_definition_id` → alter direkter Task-Call
|
|
**Seeded Workflows:** 3 Standard-Definitionen beim Migration-Upgrade direkt in DB geSEEDed (Still, Turntable, Multi-Angle)
|
|
**Für künftige Projekte:** Celery Canvas ist ausreichend für parallele/sequentielle Workflow-Execution; keine eigene Workflow-Engine bauen
|
|
|
|
---
|
|
|
|
### 2026-03-06 | Circular Import | template_service ↔ domains/rendering/service — Render nie ausgeführt
|
|
**Problem:** `app.services.template_service` war ein Shim der `app.domains.rendering.service` importiert. `app.domains.rendering.service` importierte wiederum `app.services.template_service` → zirkulärer Import → `resolve_template` konnte nie geladen werden → jeder Render schlug fehl mit "cannot import name 'resolve_template' from partially initialized module".
|
|
**Ursache:** B1-Refactor hat beide Module zu Shims gemacht die aufeinander zeigen. Die eigentliche Implementierung wurde nicht in die neue Domäne übertragen.
|
|
**Lösung:** `template_service.py` mit der Originalimplementierung aus dem git-Log wiederhergestellt (sync SQLAlchemy, Celery-sicher, 4-stufige Cascade). `domains/rendering/service.py` importiert jetzt korrekt aus `template_service` ohne Rückimport.
|
|
**Für künftige Projekte:** Nach Refactoring immer prüfen ob Shims auf die echte Implementierung zeigen oder wieder auf andere Shims. `grep -rn "def resolve_template"` vor dem Commit muss mindestens 1 Treffer liefern.
|
|
|
|
---
|
|
|
|
### 2026-03-06 | Multi-Tenancy | audit_log.tenant_id NOT NULL blockiert alle Notifications
|
|
**Problem:** Migration 036 machte `audit_log.tenant_id NOT NULL`, aber `emit_notification` setzt kein `tenant_id`. Die Notification-Insert schlug fehl → rollback → nachfolgende Session-Zugriffe schlugen fehl → Order-Submit gab 500 zurück.
|
|
**Lösung:** `audit_log.tenant_id` via `ALTER TABLE audit_log ALTER COLUMN tenant_id DROP NOT NULL` nullable gemacht. Broadcast-Notifications (system-weit, kein konkreter Tenant) DÜRFEN NULL tenant_id haben.
|
|
**Für künftige Projekte:** Audit-Logs die als Broadcast an alle Tenants gehen benötigen nullable tenant_id. Nie NOT NULL auf Tabellen setzen die auch System-Events speichern.
|
|
|
|
---
|
|
|
|
### 2026-03-06 | Frontend | GET /api/tenants gibt 307 Redirect zurück
|
|
**Problem:** FastAPI router registriert `/tenants/` (mit trailing slash). `GET /tenants` → 307 Redirect zu `/tenants/`. Axios folgt dem Redirect aber verliert den Authorization-Header → 401 → leere Tenant-Liste im Frontend.
|
|
**Lösung:** `getTenants()` in `api/tenants.ts` auf `/tenants/` (mit trailing slash) geändert.
|
|
**Für künftige Projekte:** FastAPI APIRouter mit `prefix="/tenants"` und `@router.get("")` erzeugt `/tenants` (kein Slash). Mit `@router.get("/")` erzeugt `/tenants/`. Axios folgt 307 nicht mit Auth-Header. Immer trailing slash im Frontend verwenden wenn Router mit Slash registriert.
|
|
|
|
---
|
|
|
|
## Offene Fragen
|
|
- [ ] Azure AI Credentials für Phase 4 (Bildvalidierung) noch nicht konfiguriert
|
|
- [ ] pythonOCC verfügbar im render-worker (via cadquery dependency)? Deployment-Test ausstehend
|
|
- [ ] @xyflow/react noch nicht installiert — npm install nötig nach nächstem `docker compose up --build frontend`
|
|
- [ ] Material-Alias-Seeding deckt noch nicht alle deutschen Materialbezeichnungs-Varianten ab
|
|
- [ ] Turntable-Animation: bg_color via FFmpeg-Overlay — Qualität bei Transparenz-Edges prüfen
|
|
|
|
### 2026-03-06 | Docker | apt-Paketname libgdk-pixbuf2.0-0 vs libgdk-pixbuf-2.0-0
|
|
WeasyPrint benötigt libgdk-pixbuf. Auf Debian bookworm (python:3.11-slim) heißt das Paket `libgdk-pixbuf-2.0-0` (mit Bindestrichen), nicht `libgdk-pixbuf2.0-0`. `apt-get install` schlägt mit exit code 100 fehl wenn der Name falsch ist.
|
|
→ Immer `apt-cache search libgdk` im Container prüfen bevor man Paketnamen in Dockerfiles schreibt.
|
|
|
|
### 2026-03-06 | Celery | thumbnail_rendering Queue braucht eigenen worker-thumbnail Service
|
|
Blender-Renderer verarbeitet nur 1 Request gleichzeitig. Wenn worker (concurrency=8) Tasks auf thumbnail_rendering queued, laufen 7 davon in Timeout (300s). Lösung: separaten `worker-thumbnail` Service mit `--concurrency=1` und `-Q thumbnail_rendering` in docker-compose.yml. step_processing bleibt bei concurrency=8.
|
|
|
|
### 2026-03-06 | Alembic | Migration exit code 100 bei enum-Konflikt
|
|
SQLAlchemy `Enum(create_type=False)` funktioniert nicht zuverlässig mit asyncpg. Bei bereits existierenden PostgreSQL-Enum-Typen: Raw SQL mit `DO $$ BEGIN CREATE TYPE ...; EXCEPTION WHEN duplicate_object THEN NULL; END $$;` verwenden. Für Tabellen: `CREATE TABLE IF NOT EXISTS`.
|
|
|
|
### 2026-03-06 | Render-Pipeline | Circular Shim blockiert alle Order-Renders
|
|
**Problem:** `dispatch_order_line_render` → `dispatch_render` (Shim A→B→A Circular Import) → Render startet nie. Die einzige funktionierende Render-Implementierung `render_order_line_task` war nie aus dem Dispatch-Chain erreichbar.
|
|
**Lösung:** `dispatch_order_line_render` direkt auf `render_order_line_task.delay()` umleiten. `render_dispatcher.py`-Shim ebenfalls repariert. Dispatch-Service `_legacy_dispatch` ebenfalls auf `render_order_line_task` umgeleitet.
|
|
**Erkenntnisse:** Bei Refactoring immer prüfen ob Shims zirkulär werden. Wenn zwei Module sich gegenseitig importieren (A→B und B→A), entsteht ein Circular Import — keine echte Implementierung wird aufgerufen. Den echten Aufruf-Pfad von der API zum Task vor Refactoring dokumentieren.
|
|
|
|
---
|
|
|
|
### 2026-03-06 | Render-Pipeline | render_order_line_task auf falschem Worker (kein Blender)
|
|
**Problem:** `render_order_line_task` war auf Queue `step_processing` → lief im `worker`-Container (Backend-Dockerfile, kein Blender). `render_to_file()` fiel still auf Pillow-Placeholder zurück. Renders scheinbar erfolgreich aber nur graue Platzhalterbilder.
|
|
**Ursache:** `is_blender_available()` prüft `BLENDER_BIN`-Env-Var — im `worker`-Container nicht gesetzt. Fallback auf Pillow passiert lautlos ohne Exception.
|
|
**Lösung:** `render_order_line_task` queue auf `thumbnail_rendering` geändert → läuft jetzt im `render-worker`-Container (hat Blender 5.0.1 + cadquery). `worker-thumbnail`-Service aus `docker-compose.yml` entfernt (hatte keinen Blender, blockierte aber die Queue).
|
|
**Für künftige Projekte:** Blender-Tasks IMMER auf `thumbnail_rendering` Queue routen. `worker-thumbnail` = kein Blender, `render-worker` = hat Blender. Wenn `is_blender_available()` False zurückgibt ist der Task auf dem falschen Worker.
|
|
|
|
---
|
|
|
|
### 2026-03-06 | Docker | worker-thumbnail vs render-worker — beide auf thumbnail_rendering
|
|
**Problem:** Sowohl `worker-thumbnail` (kein Blender) als auch `render-worker` (hat Blender) lauschten auf `thumbnail_rendering` Queue. Tasks wurden round-robin verteilt → 50% der Blender-Tasks schlugen fehl (Pillow-Fallback, kein echter Fehler).
|
|
**Lösung:** `worker-thumbnail`-Service aus docker-compose entfernt. `render-worker` ist der alleinige Consumer von `thumbnail_rendering`. Dieser hat Blender + cadquery + alle Render-Scripts.
|
|
**Für künftige Projekte:** Nie zwei Services mit unterschiedlichen Capabilities auf die gleiche Queue hören lassen.
|
|
|
|
---
|
|
|
|
### 2026-03-06 | Multi-Tenancy | tenant_id NOT NULL verletzt bei Order-Erstellung
|
|
**Problem:** Migration 036 machte `tenant_id NOT NULL` auf `orders`, `order_lines`, `order_items`. Alle Create-Endpoints übergaben `tenant_id` nicht → PostgreSQL NOT NULL Constraint Violation.
|
|
**Lösung:** Überall `tenant_id=getattr(user, 'tenant_id', None)` in Model-Konstruktoren: `orders.py` (create_order, split_order, add_line_to_order), `uploads.py` (finalize_excel).
|
|
**Für künftige Projekte:** Nach jeder RLS-Migration alle Create-Endpoints prüfen ob das neue Pflichtfeld befüllt wird. `getattr(user, 'tenant_id', None)` als sicheres Default-Pattern verwenden.
|
|
|
|
### 2026-03-06 | Celery | render_order_line_task auf falscher Queue → Pillow-Fallback
|
|
**Problem:** `render_order_line_task` war auf `step_processing` Queue → wurde von `worker`-Container bearbeitet, der kein Blender hat. `is_blender_available()` → False → Pillow-Placeholder-Bild ohne Fehlermeldung.
|
|
**Lösung:** Queue zu `thumbnail_rendering` geändert → nur `render-worker` (mit Blender 5.0.1) verarbeitet diese Tasks.
|
|
**Für künftige Projekte:** Nach jeder Architektur-Änderung (Container-Entfernung, Queue-Umbenennung) alle Celery-Task-Dekoratoren prüfen ob sie noch auf dem richtigen Worker laufen.
|
|
|
|
### 2026-03-06 | Celery | Zwei Worker auf derselben Queue mit unterschiedlichen Fähigkeiten
|
|
**Problem:** `worker-thumbnail` und `render-worker` konkurrierten auf `thumbnail_rendering`. `worker-thumbnail` hatte kein Blender → 50% aller Render-Tasks liefen auf dem falschen Worker → Silent-Fail.
|
|
**Lösung:** `worker-thumbnail` aus docker-compose.yml entfernt. `render-worker` ist einziger Consumer von `thumbnail_rendering`.
|
|
**Regel:** Jede Queue sollte nur von Workers mit identischen Fähigkeiten konsumiert werden. Nie zwei Worker unterschiedlicher Ausstattung auf dieselbe Queue setzen.
|
|
|
|
### 2026-03-06 | Python | Circular Import via doppelte Shim-Schicht
|
|
**Problem:** `template_service.py` importierte aus `domains/rendering/service.py`, das wiederum aus `template_service.py` importierte. Beide waren leere Shims. `resolve_template()` war nie aufrufbar → Render-Tasks crashing mit ImportError.
|
|
**Lösung:** Volle Implementierung in `template_service.py` wiederhergestellt (aus git history). `domains/rendering/service.py` importiert nur davon — kein Rückimport.
|
|
**Für künftige Projekte:** Shim-Layer immer auf circular imports prüfen. `domains/X/service.py` sollte entweder die echte Implementierung enthalten ODER aus einer anderen Domain importieren, aber nicht im Kreis.
|
|
|
|
### 2026-03-06 | FastAPI | 307-Redirect verliert Authorization-Header
|
|
**Problem:** `GET /api/tenants` → 307 Temporary Redirect zu `/api/tenants/` (trailing slash). axios folgt dem Redirect, verliert dabei den Authorization-Header → 401 → leere Tenant-Liste im Frontend.
|
|
**Lösung:** Frontend-API-Call auf `/tenants/` mit trailing slash geändert.
|
|
**Für künftige Projekte:** FastAPI-Router immer mit trailing slash aufrufen oder `redirect_slashes=False` am Router setzen.
|
|
|
|
### 2026-03-06 | Render-Pipeline | ffmpeg Turntable hängt ohne `shortest=1`
|
|
**Problem:** Turntable-Render (Order f0436188) mit bg_color schlug mit Timeout (300s) fehl. ffmpeg-Overlay-Befehl war `[1:v][0:v]overlay=0:0` — der `lavfi color`-Quell-Stream hat unendliche Dauer. ffmpeg wartete nach Ende der PNG-Sequenz weiter auf weitere Farb-Stream-Frames → hing unbegrenzt.
|
|
**Lösung:** `overlay=0:0` → `overlay=0:0:shortest=1`. `shortest=1` beendet den Output-Stream sobald der kürzeste Input-Stream endet (die PNG-Sequenz).
|
|
**Datei:** `backend/app/services/render_blender.py:507`
|
|
**Für künftige Projekte:** Bei ffmpeg-Overlays mit lavfi/color/nullsrc als ein Input IMMER `shortest=1` setzen. Sonst hängt ffmpeg nach Ende des finite Streams.
|
|
|
|
---
|
|
|
|
### 2026-03-06 | Architektur | WebSocket Auth via Query-Parameter (JWT)
|
|
**Problem:** WebSocket-Verbindungen können keinen `Authorization`-Header senden (Browser-WebSocket-API hat keine Header-Unterstützung). JWT muss anders übertragen werden.
|
|
**Lösung:** JWT als Query-Parameter: `ws://host/api/ws?token=<jwt>`. Backend verifiziert via `jwt.decode()` im WebSocket-Endpoint.
|
|
**Sicherheitshinweis:** Token ist in Server-Logs sichtbar. Für v2 akzeptabel. In v3: kurzlebigen WS-Token (TTL 30s) aus JWT generieren.
|
|
**Für künftige Projekte:** Immer Query-Param oder Cookie (bei HTTPS) für WebSocket-Auth verwenden; nie erwarten dass der Browser Headers setzen kann.
|
|
|
|
---
|
|
|
|
### 2026-03-06 | Celery Inspect | active_queues() zum Worker-Capability-Check
|
|
**Erkenntnis:** `celery_app.control.inspect().active_queues()` gibt pro Worker zurück welche Queues er konsumiert. Damit kann man gezielt prüfen ob ein Worker mit bestimmten Fähigkeiten (z.B. `thumbnail_rendering`) connected ist — besser als Worker-Namen-Heuristiken.
|
|
**Anwendung:** `GET /api/worker/health/render` nutzt `active_queues()` um `render_worker_connected` und `blender_available` korrekt zu bestimmen.
|