From edfd7105b791b1be80837297dfae463aac6eca27 Mon Sep 17 00:00:00 2001 From: Housemann <40449280+Housemann@users.noreply.github.com> Date: Fri, 17 Jul 2026 20:02:05 +0200 Subject: [PATCH] README aktualisiert: Cover-DB, n8n-Workflows, aktuelle API-Endpunkte MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Port 8090 korrigiert - Alle 4 Migrationen dokumentiert (inkl. cover_image/cover_mime_type) - Neuen API-Endpunkt GET /records/{id}/cover ergänzt - n8n-Workflows (Haupt + Backfill) mit Ablauf beschrieben - Projektstruktur und deploy.sh aktualisiert - Fehlerbehebungs-Tabelle erweitert Co-Authored-By: Claude Sonnet 4.6 --- README.md | 368 +++++++++++++++++++++++------------------------------- 1 file changed, 158 insertions(+), 210 deletions(-) diff --git a/README.md b/README.md index abcbade..193b7d1 100644 --- a/README.md +++ b/README.md @@ -14,13 +14,13 @@ Web-Oberfläche zur Anzeige und Verwaltung von Medien-Metadaten aus **PostgreSQL - [Voraussetzungen](#voraussetzungen) - [Installation auf dem Server](#installation-auf-dem-server) - [Konfiguration](#konfiguration) -- [Datenbank](#datenbank) +- [Datenbank & Migrationen](#datenbank--migrationen) +- [n8n Workflows](#n8n-workflows) - [Bedienung](#bedienung) - [API-Referenz](#api-referenz) - [Projektstruktur](#projektstruktur) - [Entwicklung & Updates](#entwicklung--updates) - [Fehlerbehebung](#fehlerbehebung) -- [Hinweise für n8n](#hinweise-für-n8n) --- @@ -28,12 +28,12 @@ Web-Oberfläche zur Anzeige und Verwaltung von Medien-Metadaten aus **PostgreSQL Die Anwendung läuft als **zwei Docker-Container** auf einem Linux-Server: -1. **web** – Nginx liefert die Vue-App aus und leitet `/api` an das Backend weiter +1. **web** – Nginx liefert die Vue-App aus und leitet `/api` an das Backend weiter 2. **api** – FastAPI liest Daten aus PostgreSQL **Produktiv-Pfad auf dem Server:** `/opt/nginx_filebot` -> Auf dem Entwicklungs-PC (z. B. Mac) ist **kein** Node.js oder Python für den Betrieb nötig. Code anpassen, auf den Server kopieren, dort `docker compose up -d --build` ausführen. +> Auf dem Entwicklungs-PC ist kein Node.js oder Python für den Betrieb nötig. Code anpassen, mit `scripts/deploy.sh` auf den Server übertragen, dort `docker compose up -d --build` ausführen. Für KI-Assistenten / Projekt-Kontext: siehe [`PROMPT.md`](PROMPT.md). @@ -43,29 +43,30 @@ Für KI-Assistenten / Projekt-Kontext: siehe [`PROMPT.md`](PROMPT.md). ### Medien-Übersicht -- **Cover** aus `cover_url` (z. B. TVMaze-Poster) in der ersten Spalte / auf Karten -- **Original-** und **Neu-Dateiname** immer sichtbar +- **Cover** – primär aus der Datenbank (`cover_image` BYTEA), Fallback auf externe URL via Proxy +- **Original-** und **Neu-Dateiname** immer sichtbar (Rename-Kontrolle) - Technische Spalten: Rating, Format, Auflösung, Codecs, Bitraten, Container, Erstellungsdatum - **Neueste Einträge zuerst** (Sortierung nach `created_at`) ### Filter - Volltextsuche in beiden Dateinamen -- **Button-Filter** für Videoformat, Codec und Container (nebeneinander auf Desktop, gestapelt auf Mobil) -- **Zeitraum** „Von / Bis“ mit Kalender und Uhrzeit (HH:MM) +- **Button-Filter** für Typ (Movie/Episode), Videoformat, Codec und Container +- **Zeitraum** „Von / Bis" mit Kalender und Uhrzeit (HH:MM) - Schnellfilter in der Tabelle (clientseitig in geladenen Daten) ### Gesehen-Status - Ungesehene Zeilen sind **hervorgehoben** (blauer Akzent) - **Tippen auf eine Zeile** markiert sie als gesehen oder ungesehen -- Filter **„Nur ungesehen“** und Aktion **„Alle als gesehen“** (für aktuelle Filterauswahl) -- Zähler **„X neu“** im Header und in den Filtern +- Filter **„Nur ungesehen"** und Aktion **„Alle als gesehen"** (für aktuelle Filterauswahl) +- Zähler **„X neu"** im Header ### Weitere Aktionen - **Löschen** einzelner Einträge (Mülleimer, mit Bestätigung) -- Responsives Layout: **Tabelle** (Desktop) / **Karten** (Mobil, ≤900px Breite) +- **Live-Aktualisierung** alle 5 Sekunden (pausiert im Hintergrund-Tab) +- Responsives Layout: **Tabelle** (Desktop) / **Karten** (Mobil, ≤900px) --- @@ -77,7 +78,7 @@ Für KI-Assistenten / Projekt-Kontext: siehe [`PROMPT.md`](PROMPT.md). ▼ ┌─────────────────┐ /api/* ┌─────────────────┐ │ nginx (web) │ ───────────────►│ FastAPI (api) │ -│ Port 8080 │ │ Port 8000 │ +│ Port 8090 │ │ Port 8000 │ │ Vue static │ └────────┬────────┘ └─────────────────┘ │ ▼ @@ -92,8 +93,6 @@ Für KI-Assistenten / Projekt-Kontext: siehe [`PROMPT.md`](PROMPT.md). | `web` | `nginx/Dockerfile` (Node → Nginx) | Statische Dateien, Reverse Proxy | | `api` | `api/Dockerfile` | REST-API, DB-Zugriff | -Compose-Datei nutzt **relative Pfade** – immer aus `/opt/nginx_filebot` starten. - --- ## Voraussetzungen @@ -101,34 +100,24 @@ Compose-Datei nutzt **relative Pfade** – immer aus `/opt/nginx_filebot` starte - Linux-Server mit **Docker** und **Docker Compose v2** - Erreichbare **PostgreSQL**-Instanz (im LAN, z. B. `192.168.30.186:5432`) - Tabelle **`n8n.movie_files`** in Datenbank **`n8n_filebot`** (anpassbar in Config) -- Optional: Spalten `seen_at` und `cover_url` (siehe [Datenbank](#datenbank)) +- Alle SQL-Migrationen ausgeführt (siehe [Datenbank & Migrationen](#datenbank--migrationen)) --- ## Installation auf dem Server -Ausführliche Anleitung inkl. **SMB-Gruppe** und Umzug: [`docs/DEPLOY-NEUER-SERVER.md`](docs/DEPLOY-NEUER-SERVER.md). +Ausführliche Anleitung inkl. SMB-Gruppe und Umzug: [`docs/DEPLOY-NEUER-SERVER.md`](docs/DEPLOY-NEUER-SERVER.md). ### 1. Projekt ablegen ```bash -# Ein User (vogto): Rechte auf ganz /opt, nicht nur ein Unterordner -sudo groupadd -f opt-docker -sudo usermod -aG opt-docker,docker vogto -sudo chown vogto:opt-docker /opt && sudo chmod 2775 /opt sudo mkdir -p /opt/nginx_filebot - -# Variante A: Git -git clone /opt/nginx_filebot - -# Variante B: rsync vom Entwicklungsrechner -# rsync -avz --exclude node_modules --exclude config/config.yaml \ -# ./ user@server:/opt/nginx_filebot/ +git clone https://git.vogt.de.com/vogto/filebot-media-browser.git /opt/nginx_filebot ``` ### 2. Datenbank vorbereiten -Siehe Abschnitt [Datenbank](#datenbank) – Migrationen ausführen. +Alle Migrationen ausführen (siehe [Datenbank & Migrationen](#datenbank--migrationen)). ### 3. Konfiguration anlegen @@ -149,19 +138,17 @@ docker compose up -d --build ```bash docker compose ps -curl -s http://localhost:8080/api/health +curl -s http://localhost:8090/api/health # Erwartung: {"status":"ok"} ``` -Im Browser: **`http://:8080`** +Im Browser: **`http://:8090`** --- ## Konfiguration -Datei: **`/opt/nginx_filebot/config/config.yaml`** (nicht versionieren, in `.gitignore`). - -Beispiel (produktive Werte aus dem Projekt): +Datei: **`config/config.yaml`** (nicht versioniert, in `.gitignore`). ```yaml database: @@ -177,88 +164,126 @@ database: | Parameter | Bedeutung | |-----------|-----------| -| `name` | **Datenbank** (Catalog), nicht Tabellenname | +| `name` | Datenbank (Catalog) | | `table` | Tabellenname innerhalb des Schemas | -| `db_schema` | Schema (bei euch typisch `n8n`) | -| `schema` | Alias für `db_schema` in YAML (optional) | - -Die API verbindet sich mit: `"n8n"."movie_files"` (in Anführungszeichen, case-sensitiv). +| `db_schema` | Schema (typisch `n8n`) | --- -## Datenbank +## Datenbank & Migrationen -### Tabelle und Schema +### Tabelle -| Einstellung | Typischer Wert | -|-------------|----------------| +| Einstellung | Wert | +|-------------|------| | Datenbank | `n8n_filebot` | | Schema | `n8n` | | Tabelle | `movie_files` | +| Vollqualifiziert | `"n8n"."movie_files"` | -Tabellen prüfen: +### Migrationen ausführen + +Alle SQL-Dateien im Verzeichnis `sql/` der Reihe nach ausführen: ```bash -docker compose exec api python /app/scripts/list-tables.py +cd /opt/nginx_filebot + +for f in sql/00*.sql; do + echo "==> $f" + psql -h 192.168.30.186 -U n8n_filebot -d n8n_filebot -f "$f" +done ``` -### Migration: `seen_at` +| Datei | Inhalt | +|-------|--------| +| `001_add_seen_at.sql` | Spalte `seen_at TIMESTAMPTZ` + Index | +| `002_add_cover_url.sql` | Spalte `cover_url TEXT` | +| `003_add_object_type.sql` | Spalte `object_type TEXT` | +| `004_add_cover_image.sql` | Spalten `cover_image BYTEA` + `cover_mime_type TEXT` | -```bash -psql -h 192.168.30.186 -U n8n_filebot -d n8n_filebot \ - -f /opt/nginx_filebot/sql/001_add_seen_at.sql -``` +### Gesehen-Status -Oder manuell: - -```sql -ALTER TABLE n8n.movie_files - ADD COLUMN IF NOT EXISTS seen_at TIMESTAMPTZ NULL; - -CREATE INDEX IF NOT EXISTS idx_movie_files_seen_at - ON n8n.movie_files (seen_at); -``` - -- `seen_at IS NULL` → **ungesehen** (hervorgehoben in der UI) +- `seen_at IS NULL` → **ungesehen** (blau hervorgehoben) - gesetzt → **gesehen** -### Migration: `cover_url` +### Cover-Bilder + +- `cover_url` – externe Poster-URL (z. B. TVMaze, OMDB) +- `cover_image` – Bild-Binärdaten direkt in der DB (BYTEA) +- `cover_mime_type` – MIME-Typ des gespeicherten Bildes (z. B. `image/jpeg`) + +Die UI lädt Cover **primär aus der DB** (`/api/records/{id}/cover`), Fallback auf den URL-Proxy (`/api/cover?url=…`). + +--- + +## n8n Workflows + +### Workflow 1: FileBot (Haupt-Workflow) + +Wird von Filebot per Webhook ausgelöst und schreibt Medien-Metadaten in die DB. Danach läuft alle 2 Minuten ein Schedule-Trigger, der fehlende Cover-URLs nachlädt und die Cover-Bilder herunterlädt. + +**Ablauf:** -```bash -psql -h 192.168.30.186 -U n8n_filebot -d n8n_filebot \ - -f /opt/nginx_filebot/sql/002_add_cover_url.sql ``` +Webhook (Filebot) → Insert rows (Metadaten) + +Schedule (alle 2 Min) → Select (cover_url IS NULL) + → Loop → Switch (Movie / Episode) + Movie → HTTP omdbapi → Edit Fields → Download Cover → Prepare Data → Speichere Cover in DB + Episode → If thetvdb_id → HTTP tvmaze → Edit Fields1 → Download Cover → Prepare Data → Speichere Cover in DB +``` + +**Wichtig für n8n:** +- `seen_at` und `cover_image` beim INSERT/UPDATE **nicht** überschreiben +- `cover_url` bleibt immer erhalten (auch wenn der Bild-Download fehlschlägt) + +--- + +### Workflow 2: Cover Backfill + +Einmaliger / regelmäßiger Workflow zum Nachfüllen von `cover_image` für Einträge, die bereits eine `cover_url` haben, aber noch kein Bild in der DB. + +**Ablauf:** + +``` +Manuell / Stündlich + → Select (cover_image IS NULL AND cover_url IS NOT NULL) + → Loop (Batch 5) + → HTTP Download (cover_url, continueOnFail) + → Code (Base64 extrahieren) + → Execute Query (UPDATE cover_image, cover_mime_type) + → Loop (weiter) +``` + +**Hinweis:** Wenn der Workflow wegen Timeout abbricht (viele Einträge), beim erneuten Ausführen macht er automatisch dort weiter, wo er aufgehört hat – der SELECT lädt nur noch Einträge mit `cover_image IS NULL`. + +Tabellengröße prüfen: ```sql -ALTER TABLE n8n.movie_files - ADD COLUMN IF NOT EXISTS cover_url TEXT NULL; +SELECT + COUNT(*) AS zeilen, + COUNT(cover_image) AS mit_bild, + pg_size_pretty(pg_total_relation_size('"n8n"."movie_files"')) AS gesamt +FROM n8n.movie_files; ``` -Beispiel-URL: `https://static.tvmaze.com/uploads/images/medium_portrait/200/502332.jpg` -(Doppelte Anführungszeichen in der DB werden in der UI bereinigt.) - -### Weitere Spalten - -Die API liest alle in `api/main.py` → `COLUMNS` definierten Felder (entsprechen dem n8n/Filebot-Export). Fehlende Spalten führen zu API-Fehlern – dann Spalte in PostgreSQL ergänzen oder `COLUMNS` anpassen. - --- ## Bedienung ### Desktop -1. Seite öffnen → Daten werden geladen (Status „DB verbunden“). +1. Seite öffnen → Daten werden geladen (Status „DB verbunden"). 2. Filter oben nutzen; **Aktualisieren** lädt neu, **Zurücksetzen** löscht Filter. -3. **Live-Aktualisierung:** Alle 5 Sekunden werden die Daten still neu geladen (ohne Seiten-Reload). Im Header: „Live · HH:MM:SS“. Im Hintergrund-Tab pausiert das Polling. -4. **Cover:** Bilder werden über `/api/cover` geladen (Proxy), falls externe Poster (z. B. TVMaze) blockiert werden. -5. **Zeile antippen/klicken** → Gesehen-Status umschalten. -6. **Mülleimer** → Eintrag löschen (mit Bestätigung). -7. Spaltenköpfe klicken zum Sortieren; Schnellfilter unter der Filterleiste. +3. **Live-Aktualisierung:** Alle 5 Sekunden werden die Daten still neu geladen. Im Hintergrund-Tab pausiert das Polling. +4. **Zeile antippen/klicken** → Gesehen-Status umschalten. +5. **Mülleimer** → Eintrag löschen (mit Bestätigung). +6. Spaltenköpfe klicken zum Sortieren; Schnellfilter unter der Filterleiste. ### Mobil - Gleiche Logik in **Kartenform** mit Cover links. -- Filter untereinander. +- Filter untereinander gestapelt. ### Gesehen-Status @@ -266,81 +291,28 @@ Die API liest alle in `api/main.py` → `COLUMNS` definierten Felder (entspreche |--------|--------| | Zeile antippen (ungesehen) | `seen_at` = jetzt | | Zeile antippen (gesehen) | `seen_at` = NULL | -| „Nur ungesehen“ | Zeigt nur Einträge ohne `seen_at` | -| „Alle als gesehen“ | Setzt `seen_at` für alle **aktuell gefilterten** Einträge | +| „Nur ungesehen" | Zeigt nur Einträge ohne `seen_at` | +| „Alle als gesehen" | Setzt `seen_at` für alle aktuell gefilterten Einträge | --- ## API-Referenz -Basis-URL hinter Nginx: `http://:8080/api` +Basis-URL: `http://:8090/api` -### `GET /health` - -```json -{ "status": "ok" } -``` - -### `GET /status` - -Diagnose: Config-Pfad, qualifizierte Tabelle, Zeilenanzahl, bei Fehler Tabellenlisten. - -### `GET /records` - -Query-Parameter: - -| Parameter | Beschreibung | -|-----------|--------------| -| `search` | ILIKE in `file_name_original` und `file_name_new` | -| `format` | `standard_video_format` | -| `codec` | `video_codec_library` | -| `container` | `container_format` | -| `created_from` | ISO-Datum/Zeit, `created_at >=` | -| `created_to` | ISO-Datum/Zeit, `created_at <=` | -| `unseen_only` | `true` → nur `seen_at IS NULL` | -| `limit`, `offset` | Pagination (Standard limit=5000) | - -Antwort enthält u. a. `records`, `total`, `unseen_total`. - -### `GET /records/meta` - -Gleiche Query-Parameter wie `GET /records` (ohne Pagination). Leichtgewichtige Antwort für Auto-Refresh: - -```json -{ - "total": 120, - "unseen_total": 3, - "max_id": 456, - "latest_created_at": "2026-06-04T12:00:00+00:00", - "revision": "a1b2c3…" -} -``` - -### `GET /cover` - -Query: `url` (HTTP(S)-Poster-URL). Liefert das Bild über die API (umgeht Hotlink-/Referrer-Sperren). - -### `GET /filters` - -Distinct-Werte für Format-, Codec- und Container-Buttons. - -### `PATCH /records/{id}/seen` - -Body: - -```json -{ "seen": true } -``` - -oder `{ "seen": false }` (setzt `seen_at` auf NULL). - -### `POST /records/mark-all-seen` - -Markiert alle Einträge, die zu den **gleichen Query-Filtern** wie `GET /records` passen (ohne `unseen_only`). - -### `DELETE /records/{id}` - -Löscht einen Datensatz anhand der `id`. +| Methode | Pfad | Beschreibung | +|---------|------|--------------| +| GET | `/health` | DB-Ping | +| GET | `/status` | Diagnose (Config, Tabellenliste bei Fehler) | +| GET | `/columns` | Liste aller geladenen Spalten | +| GET | `/filters` | Distinct-Werte für Filter-Buttons | +| GET | `/records` | Datensätze; Query: `search`, `format`, `codec`, `container`, `object_type`, `created_from`, `created_to`, `unseen_only`, `limit`, `offset` | +| GET | `/records/meta` | Leichtgewichtiger Check für Auto-Refresh (total, unseen_total, revision) | +| GET | `/records/{id}/cover` | Cover-Bild direkt aus DB (BYTEA); 404 wenn kein Bild gespeichert | +| GET | `/cover?url=…` | Cover-Proxy für externe URLs (umgeht Hotlink-Sperren) | +| PATCH | `/records/{id}/seen` | Body: `{"seen": true\|false}` | +| POST | `/records/mark-all-seen` | Alle gefilterten Einträge als gesehen markieren | +| DELETE | `/records/{id}` | Eintrag löschen | --- @@ -349,65 +321,74 @@ Löscht einen Datensatz anhand der `id`. ```text . ├── docker-compose.yml -├── PROMPT.md # KI-/Projekt-Kontext +├── PROMPT.md # KI-/Projekt-Kontext ├── README.md ├── config/ │ └── config.example.yaml ├── api/ -│ ├── main.py # REST-Endpunkte, COLUMNS -│ ├── config.py # YAML-Config, db_schema +│ ├── main.py # REST-Endpunkte, COLUMNS +│ ├── config.py # YAML-Config, db_schema │ ├── Dockerfile │ ├── requirements.txt │ └── scripts/ -│ └── list-tables.py # Diagnose im Container +│ └── list-tables.py # Diagnose im Container ├── frontend/ │ ├── package.json │ ├── vite.config.ts │ └── src/ │ ├── App.vue │ ├── api.ts -│ ├── styles.css # Theme (helles Dark-UI) +│ ├── types.ts +│ ├── styles.css │ ├── components/ │ │ ├── DataTable.vue │ │ ├── MobileCards.vue │ │ ├── FilterBar.vue -│ │ ├── CoverImage.vue +│ │ ├── CoverImage.vue # DB-Bild primär, URL-Proxy als Fallback │ │ ├── NamePair.vue │ │ ├── DateTimeRangeFilter.vue │ │ └── DeleteButton.vue +│ ├── composables/ +│ │ └── useAutoRefresh.ts │ └── utils/ +│ ├── coverUrl.ts │ ├── datetime.ts +│ ├── names.ts +│ ├── objectType.ts +│ ├── preserveScroll.ts │ └── seen.ts ├── nginx/ │ ├── Dockerfile │ └── nginx.conf +├── scripts/ +│ └── deploy.sh # Einmaliges SSH-Login, alle Dateien übertragen └── sql/ ├── 001_add_seen_at.sql - └── 002_add_cover_url.sql + ├── 002_add_cover_url.sql + ├── 003_add_object_type.sql + └── 004_add_cover_image.sql ``` --- ## Entwicklung & Updates -### Code aktualisieren +### Dateien auf den Server übertragen + +```bash +cd "/Users/vogto/Documents/ClaudeCode/Webseite Filebot" +bash scripts/deploy.sh +``` + +Das Script baut die Container anschließend automatisch neu. + +### Manuell (einzelne Dateien) ```bash cd /opt/nginx_filebot -git pull # falls Git -docker compose up -d --build -``` - -Nur Frontend: - -```bash -docker compose up -d --build web -``` - -Nur API: - -```bash -docker compose up -d --build api +docker compose up -d --build # alles +docker compose up -d --build web # nur Frontend +docker compose up -d --build api # nur API ``` ### Logs @@ -417,19 +398,6 @@ docker compose logs -f api docker compose logs -f web ``` -### Optional: lokale Entwicklung (nicht für Produktion) - -Nur wenn bewusst gewünscht – normaler Workflow ist Server-only. - -```bash -# API -cd api && pip install -r requirements.txt -CONFIG_PATH=../config/config.yaml uvicorn main:app --reload --port 8000 - -# Frontend (proxied in vite.config.ts nach :8000) -cd frontend && npm install && npm run dev -``` - --- ## Fehlerbehebung @@ -437,19 +405,17 @@ cd frontend && npm install && npm run dev ### API-Fehler / leere Seite ```bash -cd /opt/nginx_filebot docker compose logs api --tail 80 -curl -s http://localhost:8080/api/status | python3 -m json.tool +curl -s http://localhost:8090/api/status | python3 -m json.tool ``` | Symptom | Ursache | Lösung | |---------|---------|--------| -| `relation "n8n.n8n_filebot" does not exist` | Falscher Tabellenname | `table: "movie_files"` in config.yaml | -| `relation "public...." does not exist` | Schema falsch | `db_schema: "n8n"` | -| Spalte `seen_at` / `cover_url` fehlt | Migration fehlt | SQL in `sql/` ausführen | -| `API-Fehler: 503` | DB nicht erreichbar | `nc -zv 5432` vom Server und aus Container | -| 0 Einträge | Filter zu streng / leere DB | Filter zurücksetzen, `SELECT COUNT(*)` prüfen | -| Altes Design | Cache / kein Rebuild | `docker compose up -d --build web` + Hard-Reload | +| `relation "..." does not exist` | Falscher Tabellenname oder Schema | `table: "movie_files"`, `db_schema: "n8n"` in config.yaml | +| Spalte fehlt (API 500) | Migration nicht ausgeführt | SQL in `sql/` ausführen | +| `503` | DB nicht erreichbar | `nc -zv 192.168.30.186 5432` vom Server prüfen | +| Cover fehlt | `cover_image IS NULL` | Backfill-Workflow in n8n ausführen | +| Altes Design | Kein Rebuild | `docker compose up -d --build web` + Hard-Reload | ### Netzwerk vom Container zur DB @@ -460,21 +426,3 @@ s = socket.create_connection(('192.168.30.186', 5432), timeout=5) print('OK'); s.close() " ``` - -### Port ändern - -In `docker-compose.yml` z. B. `"80:80"` oder `"8888:80"` statt `"8080:80"`. - ---- - -## Hinweise für n8n - -- **INSERT:** Neue Zeilen haben `seen_at = NULL` → erscheinen als „neu“. -- **UPDATE:** `seen_at` und `cover_url` nicht mit überschreiben, wenn der Status erhalten bleiben soll. -- Datenbank-Workflow und Tabellenname (`movie_files`, Schema `n8n`) mit der `config.yaml` abstimmen. - ---- - -## Lizenz / Nutzung - -Privates Heimnetz-Projekt. Anpassungen nach Bedarf; `config.yaml` mit Passwörtern nicht in öffentliche Repositories committen.