Files
HousemannandClaude Sonnet 4.6 edfd7105b7 README aktualisiert: Cover-DB, n8n-Workflows, aktuelle API-Endpunkte
- 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 <noreply@anthropic.com>
2026-07-17 20:02:05 +02:00

429 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Filebot Media Browser
Web-Oberfläche zur Anzeige und Verwaltung von Medien-Metadaten aus **PostgreSQL** (Filebot / n8n). Fokus: **Original- und neuer Dateiname** zum Prüfen von Renames, plus Cover, Filtern und Gesehen-Status.
![Stack](https://img.shields.io/badge/Vue-3-42b883) ![FastAPI](https://img.shields.io/badge/FastAPI-009688) ![PostgreSQL](https://img.shields.io/badge/PostgreSQL-336791) ![Docker](https://img.shields.io/badge/Docker-2496ED)
---
## Inhaltsverzeichnis
- [Überblick](#überblick)
- [Features](#features)
- [Architektur](#architektur)
- [Voraussetzungen](#voraussetzungen)
- [Installation auf dem Server](#installation-auf-dem-server)
- [Konfiguration](#konfiguration)
- [Datenbank & Migrationen](#datenbank--migrationen)
- [n8n Workflows](#n8n-workflows)
- [Bedienung](#bedienung)
- [API-Referenz](#api-referenz)
- [Projektstruktur](#projektstruktur)
- [Entwicklung & Updates](#entwicklung--updates)
- [Fehlerbehebung](#fehlerbehebung)
---
## Überblick
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
2. **api** FastAPI liest Daten aus PostgreSQL
**Produktiv-Pfad auf dem Server:** `/opt/nginx_filebot`
> 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).
---
## Features
### Medien-Übersicht
- **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 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
### Weitere Aktionen
- **Löschen** einzelner Einträge (Mülleimer, mit Bestätigung)
- **Live-Aktualisierung** alle 5 Sekunden (pausiert im Hintergrund-Tab)
- Responsives Layout: **Tabelle** (Desktop) / **Karten** (Mobil, ≤900px)
---
## Architektur
```text
Browser
┌─────────────────┐ /api/* ┌─────────────────┐
│ nginx (web) │ ───────────────►│ FastAPI (api) │
│ Port 8090 │ │ Port 8000 │
│ Vue static │ └────────┬────────┘
└─────────────────┘ │
┌─────────────────┐
│ PostgreSQL │
│ n8n.movie_files│
└─────────────────┘
```
| Container | Image-Build | Aufgabe |
|-----------|-------------|---------|
| `web` | `nginx/Dockerfile` (Node → Nginx) | Statische Dateien, Reverse Proxy |
| `api` | `api/Dockerfile` | REST-API, DB-Zugriff |
---
## Voraussetzungen
- 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)
- 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).
### 1. Projekt ablegen
```bash
sudo mkdir -p /opt/nginx_filebot
git clone https://git.vogt.de.com/vogto/filebot-media-browser.git /opt/nginx_filebot
```
### 2. Datenbank vorbereiten
Alle Migrationen ausführen (siehe [Datenbank & Migrationen](#datenbank--migrationen)).
### 3. Konfiguration anlegen
```bash
cd /opt/nginx_filebot
cp config/config.example.yaml config/config.yaml
nano config/config.yaml
```
### 4. Starten
```bash
cd /opt/nginx_filebot
docker compose up -d --build
```
### 5. Prüfen
```bash
docker compose ps
curl -s http://localhost:8090/api/health
# Erwartung: {"status":"ok"}
```
Im Browser: **`http://<Server-IP>:8090`**
---
## Konfiguration
Datei: **`config/config.yaml`** (nicht versioniert, in `.gitignore`).
```yaml
database:
host: "192.168.30.186"
port: 5432
name: "n8n_filebot" # PostgreSQL-Datenbankname
user: "n8n_filebot"
password: "geheim"
table: "movie_files" # Tabellenname (ohne Schema)
db_schema: "n8n" # PostgreSQL-Schema
ssl: false
```
| Parameter | Bedeutung |
|-----------|-----------|
| `name` | Datenbank (Catalog) |
| `table` | Tabellenname innerhalb des Schemas |
| `db_schema` | Schema (typisch `n8n`) |
---
## Datenbank & Migrationen
### Tabelle
| Einstellung | Wert |
|-------------|------|
| Datenbank | `n8n_filebot` |
| Schema | `n8n` |
| Tabelle | `movie_files` |
| Vollqualifiziert | `"n8n"."movie_files"` |
### Migrationen ausführen
Alle SQL-Dateien im Verzeichnis `sql/` der Reihe nach ausführen:
```bash
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
```
| 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` |
### Gesehen-Status
- `seen_at IS NULL`**ungesehen** (blau hervorgehoben)
- gesetzt → **gesehen**
### 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:**
```
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
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;
```
---
## Bedienung
### Desktop
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. 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 gestapelt.
### Gesehen-Status
| Aktion | Effekt |
|--------|--------|
| 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 |
---
## API-Referenz
Basis-URL: `http://<host>:8090/api`
| 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 |
---
## Projektstruktur
```text
.
├── docker-compose.yml
├── PROMPT.md # KI-/Projekt-Kontext
├── README.md
├── config/
│ └── config.example.yaml
├── api/
│ ├── main.py # REST-Endpunkte, COLUMNS
│ ├── config.py # YAML-Config, db_schema
│ ├── Dockerfile
│ ├── requirements.txt
│ └── scripts/
│ └── list-tables.py # Diagnose im Container
├── frontend/
│ ├── package.json
│ ├── vite.config.ts
│ └── src/
│ ├── App.vue
│ ├── api.ts
│ ├── types.ts
│ ├── styles.css
│ ├── components/
│ │ ├── DataTable.vue
│ │ ├── MobileCards.vue
│ │ ├── FilterBar.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
├── 003_add_object_type.sql
└── 004_add_cover_image.sql
```
---
## Entwicklung & Updates
### 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
docker compose up -d --build # alles
docker compose up -d --build web # nur Frontend
docker compose up -d --build api # nur API
```
### Logs
```bash
docker compose logs -f api
docker compose logs -f web
```
---
## Fehlerbehebung
### API-Fehler / leere Seite
```bash
docker compose logs api --tail 80
curl -s http://localhost:8090/api/status | python3 -m json.tool
```
| Symptom | Ursache | Lösung |
|---------|---------|--------|
| `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
```bash
docker compose exec api python -c "
import socket
s = socket.create_connection(('192.168.30.186', 5432), timeout=5)
print('OK'); s.close()
"
```