Initial commit mit MkDocs-Dokumentation

This commit is contained in:
2026-03-04 14:18:33 +00:00
parent 6115090da1
commit 31d3e36597
97 changed files with 27518 additions and 1 deletions

View File

@@ -0,0 +1,221 @@
# Backend-Services
Das Backend ist in Node.js/Express geschrieben und in **Services** aufgeteilt, die jeweils eine klar abgegrenzte Verantwortlichkeit haben.
---
## pipelineService.js
**Der Kern von Ripster** orchestriert den gesamten Ripping-Workflow.
### Zuständigkeiten
- Verwaltung des Pipeline-Zustands als State Machine
- Koordination zwischen allen externen Tools
- Generierung von Encode-Plänen
- Fehlerbehandlung und Recovery
### Haupt-Methoden
| Methode | Beschreibung |
|---------|-------------|
| `analyzeDisc()` | Startet MakeMKV-Analyse der eingelegten Disc |
| `selectMetadata(jobId, omdbData, playlist)` | Setzt Metadaten und Playlist für einen Job |
| `startJob(jobId)` | Startet den Ripping-Prozess |
| `confirmEncode(jobId, trackSelection)` | Bestätigt Encode mit Track-Auswahl |
| `cancelPipeline()` | Bricht aktiven Prozess ab |
| `retryJob(jobId)` | Wiederholt fehlgeschlagenen Job |
| `reencodeJob(jobId)` | Encodiert bestehende Raw-MKV neu |
### Zustandsübergänge
```mermaid
stateDiagram-v2
[*] --> IDLE
IDLE --> ANALYZING: analyzeDisc()
ANALYZING --> METADATA_SELECTION: MakeMKV fertig
METADATA_SELECTION --> READY_TO_START: selectMetadata()
READY_TO_START --> RIPPING: startJob()
RIPPING --> MEDIAINFO_CHECK: MKV erstellt
MEDIAINFO_CHECK --> READY_TO_ENCODE: Tracks analysiert
READY_TO_ENCODE --> ENCODING: confirmEncode()
ENCODING --> FINISHED: HandBrake fertig
ENCODING --> ERROR: Fehler
RIPPING --> ERROR: Fehler
ERROR --> IDLE: retryJob() / cancel
FINISHED --> IDLE: cancel / neue Disc
```
---
## diskDetectionService.js
Überwacht das Disc-Laufwerk auf Disc-Einleger- und Auswurf-Ereignisse.
### Modi
| Modus | Beschreibung |
|------|-------------|
| `auto` | Erkennt verfügbare Laufwerke automatisch |
| `explicit` | Überwacht ein bestimmtes Gerät (z.B. `/dev/sr0`) |
### Polling
Der Service pollt das Laufwerk im konfigurierten Intervall (`disc_poll_interval_ms`, Standard: 5000ms) und emittiert Events:
```js
// Ereignisse
emit('disc-detected', { device: '/dev/sr0' })
emit('disc-removed', { device: '/dev/sr0' })
```
---
## processRunner.js
Verwaltet externe CLI-Prozesse.
### Features
- **Streaming**: stdout/stderr werden zeilenweise gelesen
- **Progress-Callbacks**: Ermöglicht Echtzeit-Fortschrittsanzeige
- **Graceful Shutdown**: SIGINT → Warte-Timeout → SIGKILL
- **Prozess-Registry**: Verfolgt aktive Prozesse für sauberes Beenden
### Nutzung
```js
const result = await runProcess(
'HandBrakeCLI',
['--input', rawFile, '--output', outputFile, '--preset', preset],
{
onStderr: (line) => parseHandBrakeProgress(line),
onStdout: (line) => logger.debug(line)
}
);
```
---
## websocketService.js
WebSocket-Server für Echtzeit-Client-Kommunikation.
### Betrieb
- Läuft auf Pfad `/ws` des Express-Servers
- Hält eine Registry aller verbundenen Clients
- Ermöglicht Broadcast an alle Clients oder gezieltes Senden
### API
```js
broadcast({ type: 'PIPELINE_STATE_CHANGE', data: { state, jobId } });
broadcast({ type: 'PROGRESS_UPDATE', data: { progress, eta } });
```
---
## omdbService.js
Integration mit der [OMDb API](https://www.omdbapi.com/).
### Methoden
| Methode | Beschreibung |
|---------|-------------|
| `searchByTitle(title, type)` | Suche nach Titel (movie/series) |
| `fetchById(imdbId)` | Vollständige Metadaten per IMDb-ID |
### Zurückgegebene Daten
```json
{
"imdbId": "tt1375666",
"title": "Inception",
"year": "2010",
"type": "movie",
"poster": "https://...",
"plot": "...",
"director": "Christopher Nolan"
}
```
---
## settingsService.js
Verwaltet alle Anwendungseinstellungen.
### Features
- **Schema-getriebene Validierung**: Jede Einstellung hat Typ, Grenzen und Pflichtfeld-Flag
- **Kategorisierung**: Einstellungen sind in Kategorien gruppiert (Paths, Tools, Encoding, ...)
- **Persistenz**: Werte in SQLite, Schema ebenfalls in SQLite
- **Defaults**: `defaultSettings.js` definiert Standardwerte
### Einstellungs-Kategorien
| Kategorie | Einstellungen |
|-----------|--------------|
| `paths` | `raw_dir`, `movie_dir`, `log_dir` |
| `tools` | `makemkv_command`, `handbrake_command`, `mediainfo_command` |
| `encoding` | `handbrake_preset`, `handbrake_extra_args`, `output_extension`, `filename_template` |
| `drive` | `drive_mode`, `drive_device`, `disc_poll_interval_ms` |
| `makemkv` | `makemkv_min_length_minutes`, `makemkv_backup_mode` |
| `omdb` | `omdb_api_key`, `omdb_default_type` |
| `notifications` | `pushover_user_key`, `pushover_api_token` |
---
## historyService.js
Datenbankoperationen für Job-Historie.
### Hauptoperationen
| Operation | Beschreibung |
|-----------|-------------|
| `listJobs(filters)` | Jobs nach Status/Titel filtern |
| `getJob(id)` | Job-Details mit Logs abrufen |
| `findOrphanRawFolders()` | Nicht-getrackte Raw-Ordner finden |
| `importOrphanRaw(path)` | Orphan-Ordner als Job importieren |
| `assignOmdb(id, omdbData)` | OMDb-Metadaten nachträglich zuweisen |
| `deleteJob(id, deleteFiles)` | Job und optional Dateien löschen |
---
## notificationService.js
PushOver-Push-Benachrichtigungen.
```js
await notify({
title: 'Ripster: Job abgeschlossen',
message: 'Inception (2010) wurde erfolgreich encodiert'
});
```
---
## logger.js
Strukturiertes Logging mit täglicher Log-Rotation.
### Log-Level
| Level | Verwendung |
|-------|-----------|
| `debug` | Detaillierte Entwicklungs-Informationen |
| `info` | Normale Betriebsereignisse |
| `warn` | Warnungen, die Aufmerksamkeit benötigen |
| `error` | Fehler, die den Betrieb beeinträchtigen |
### Log-Dateien
```
logs/
├── ripster-2024-01-15.log ← Tages-Log
└── jobs/
└── job-42-handbrake.log ← Prozess-spezifische Logs
```

View File

@@ -0,0 +1,161 @@
# Datenbank
Ripster verwendet **SQLite3** als Datenbank. Die Datenbankdatei liegt unter `backend/data/ripster.db`.
---
## Schema-Übersicht
```sql
-- Vier Haupt-Tabellen
settings_schema -- Einstellungs-Definitionen
settings_values -- Benutzer-Werte
jobs -- Rip-Job-Datensätze
pipeline_state -- Aktueller Pipeline-Zustand (Singleton)
```
---
## Tabelle: jobs
Die wichtigste Tabelle speichert alle Ripping-Jobs.
```sql
CREATE TABLE jobs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
status TEXT NOT NULL, -- Aktueller Status
title TEXT, -- Filmtitel (von OMDb)
imdb_id TEXT, -- IMDb-ID
omdb_year TEXT, -- Erscheinungsjahr
omdb_type TEXT, -- movie/series
omdb_poster TEXT, -- Poster-URL
raw_path TEXT, -- Pfad zur Raw-MKV
output_path TEXT, -- Pfad zur Ausgabedatei
playlist TEXT, -- Gewählte Blu-ray Playlist
makemkv_output TEXT, -- MakeMKV-Ausgabe (JSON)
mediainfo_output TEXT, -- MediaInfo-Ausgabe (JSON)
encode_plan TEXT, -- Encode-Plan (JSON)
handbrake_log TEXT, -- HandBrake Log-Pfad
error_message TEXT, -- Fehlermeldung bei ERROR
error_details TEXT -- Detaillierte Fehler-Infos
);
```
### Job-Status-Werte
| Status | Beschreibung |
|--------|-------------|
| `ANALYZING` | MakeMKV analysiert die Disc |
| `METADATA_SELECTION` | Wartet auf Benutzer-Metadaten-Auswahl |
| `READY_TO_START` | Bereit zum Starten |
| `RIPPING` | MakeMKV rippt die Disc |
| `MEDIAINFO_CHECK` | MediaInfo analysiert die Raw-Datei |
| `READY_TO_ENCODE` | Wartet auf Encode-Bestätigung |
| `ENCODING` | HandBrake encodiert |
| `FINISHED` | Erfolgreich abgeschlossen |
| `ERROR` | Fehler aufgetreten |
---
## Tabelle: pipeline_state
Singleton-Tabelle für den aktuellen Pipeline-Zustand (immer genau 1 Zeile).
```sql
CREATE TABLE pipeline_state (
id INTEGER PRIMARY KEY CHECK(id = 1),
state TEXT NOT NULL DEFAULT 'IDLE',
job_id INTEGER, -- Aktiver Job (NULL wenn IDLE)
progress REAL, -- Fortschritt 0-100
eta TEXT, -- Geschätzte Restzeit
updated_at TEXT NOT NULL
);
```
---
## Tabelle: settings_schema
Definiert alle verfügbaren Einstellungen mit Metadaten.
```sql
CREATE TABLE settings_schema (
key TEXT PRIMARY KEY,
category TEXT NOT NULL, -- paths, tools, encoding, ...
type TEXT NOT NULL, -- string, number, boolean, select
label TEXT NOT NULL, -- Anzeigename
description TEXT, -- Hilfetext
default_val TEXT, -- Standardwert
required INTEGER, -- 1 = Pflichtfeld
min_val REAL, -- Minimalwert (für number)
max_val REAL, -- Maximalwert (für number)
options TEXT -- JSON-Array für select-Typ
);
```
---
## Tabelle: settings_values
Speichert benutzer-konfigurierte Werte.
```sql
CREATE TABLE settings_values (
key TEXT PRIMARY KEY REFERENCES settings_schema(key),
value TEXT NOT NULL,
updated_at TEXT NOT NULL
);
```
---
## Schema-Migrationen
`database.js` implementiert **automatische Migrationen**:
1. Beim Start wird das aktuelle Schema geprüft
2. Fehlende Tabellen werden erstellt
3. Fehlende Spalten werden hinzugefügt
4. Neue Default-Einstellungen werden eingefügt
### Korruptions-Recovery
Falls die Datenbankdatei korrupt ist:
```
1. Korrupte Datei wird erkannt (Verbindungsfehler / Integritätsprüfung)
2. Datei wird in /backend/data/quarantine/ verschoben
3. Neue, leere Datenbank wird erstellt
4. Schema wird neu initialisiert
5. Log-Eintrag mit Warnung
```
---
## Datenbankpfad konfigurieren
Standard: `./data/ripster.db` (relativ zum Backend-Verzeichnis)
Über Umgebungsvariable anpassen:
```env
DB_PATH=/var/lib/ripster/ripster.db
```
---
## Direkte Datenbankinspektion
```bash
# SQLite3-CLI
sqlite3 backend/data/ripster.db
# Alle Jobs anzeigen
.mode table
SELECT id, status, title, created_at FROM jobs ORDER BY created_at DESC;
# Einstellungen anzeigen
SELECT key, value FROM settings_values;
```

View File

@@ -0,0 +1,190 @@
# Frontend-Komponenten
Das Frontend ist mit **React 18** und **PrimeReact** gebaut und kommuniziert über REST-API und WebSocket mit dem Backend.
---
## Seiten (Pages)
### DashboardPage.jsx
Die Hauptseite von Ripster zeigt den aktuellen Pipeline-Status und ermöglicht alle Workflow-Aktionen.
**Funktionen:**
- Anzeige des aktuellen Pipeline-Zustands (IDLE, ANALYZING, RIPPING, ENCODING, ...)
- Live-Fortschrittsbalken mit ETA
- Trigger für Metadaten-Dialog
- Playlist-Entscheidungs-UI (bei Blu-ray Obfuskierung)
- Encode-Review mit Track-Auswahl
- Job-Steuerung (Start, Abbruch, Retry)
**Zugehörige Komponenten:**
- `PipelineStatusCard` Status-Widget
- `MetadataSelectionDialog` OMDb-Suche und Playlist-Auswahl
- `MediaInfoReviewPanel` Track-Auswahl vor dem Encoding
- `DiscDetectedDialog` Benachrichtigung bei Disc-Erkennung
### SettingsPage.jsx
Konfigurationsoberfläche für alle Ripster-Einstellungen.
**Funktionen:**
- Dynamisch generiertes Formular aus dem Settings-Schema
- Echtzeit-Validierungsfeedback
- PushOver-Verbindungstest
- Automatische Aktualisierung des Encode-Reviews bei relevanten Änderungen
### HistoryPage.jsx
Job-Historie mit vollständigem Audit-Trail.
**Funktionen:**
- Sortier- und filterbares Job-Verzeichnis
- Statusfilter (FINISHED, ERROR, WAITING_FOR_USER_DECISION, ...)
- Job-Detail-Dialog mit vollständigen Logs
- Re-Encode, Löschen und Metadaten-Zuweisung
- Import von Orphan-Raw-Ordnern
---
## Komponenten (Components)
### MetadataSelectionDialog.jsx
Dialog für die Metadaten-Auswahl nach der Disc-Analyse.
```
┌─────────────────────────────────────┐
│ Metadaten auswählen │
├─────────────────────────────────────┤
│ Suche: [Inception ] 🔍 │
├─────────────────────────────────────┤
│ Ergebnisse: │
│ ▶ Inception (2010) Movie │
│ Inception: ... (2011) Series │
├─────────────────────────────────────┤
│ Playlist (nur Blu-ray): │
│ ▶ 00800.mpls (2:30:15) ✓ Empfohlen │
│ 00801.mpls (0:01:23) │
├─────────────────────────────────────┤
│ [Bestätigen] │
└─────────────────────────────────────┘
```
### MediaInfoReviewPanel.jsx
Track-Auswahl-Panel vor dem Encoding.
```
┌─────────────────────────────────────┐
│ Encode-Review │
├─────────────────────────────────────┤
│ Audio-Tracks: │
│ ☑ Track 1: Deutsch (AC-3, 5.1) │
│ ☑ Track 2: English (TrueHD, 7.1) │
│ ☐ Track 3: Français (AC-3, 2.0) │
├─────────────────────────────────────┤
│ Untertitel: │
│ ☑ Track 1: Deutsch │
│ ☐ Track 2: English │
├─────────────────────────────────────┤
│ [Encodierung starten] │
└─────────────────────────────────────┘
```
### DynamicSettingsForm.jsx
Wiederverwendbares Formular, das aus dem Settings-Schema generiert wird.
**Unterstützte Feldtypen:**
| Typ | UI-Element |
|----|-----------|
| `string` | Text-Input |
| `number` | Zahlen-Input mit Min/Max |
| `boolean` | Toggle/Checkbox |
| `select` | Dropdown |
| `password` | Passwort-Input |
### PipelineStatusCard.jsx
Status-Anzeige-Widget für die Dashboard-Seite.
### JobDetailDialog.jsx
Vollständiger Job-Detail-Dialog mit Logs-Viewer.
---
## Hooks
### useWebSocket.js
Zentraler Custom-Hook für die WebSocket-Verbindung.
```js
const { status, lastMessage } = useWebSocket({
onMessage: (msg) => {
if (msg.type === 'PIPELINE_STATE_CHANGE') {
setPipelineState(msg.data);
}
}
});
```
**Features:**
- Automatische Verbindung zu `/ws`
- Reconnect mit exponential backoff
- Message-Parsing (JSON)
- Status-Tracking (connecting, connected, disconnected)
---
## API-Client (client.js)
Zentraler HTTP-Client für alle Backend-Anfragen.
```js
// Beispiel-Aufrufe
const state = await api.getPipelineState();
const results = await api.searchOmdb('Inception');
await api.selectMetadata(jobId, omdbData, playlist);
await api.confirmEncode(jobId, { audioTracks: [0, 1], subtitleTracks: [0] });
```
**Features:**
- Zentralisierte Fehlerbehandlung
- Automatische JSON-Serialisierung
- Basis-URL aus Umgebungsvariable (`VITE_API_BASE`)
---
## Build & Entwicklung
### Entwicklungsserver
```bash
cd frontend
npm run dev
# → http://localhost:5173
```
### Vite-Proxy-Konfiguration
In der Entwicklungsumgebung proxied Vite API-Anfragen zum Backend:
```js
// vite.config.js
proxy: {
'/api': 'http://localhost:3001',
'/ws': { target: 'ws://localhost:3001', ws: true }
}
```
### Production-Build
```bash
cd frontend
npm run build
# → frontend/dist/
```

112
docs/architecture/index.md Normal file
View File

@@ -0,0 +1,112 @@
# Architektur
Ripster ist als klassische **Client-Server-Anwendung** mit Echtzeit-Kommunikation über WebSockets aufgebaut.
---
## Systemüberblick
```mermaid
graph TB
subgraph Browser["Browser (React)"]
Dashboard["Dashboard"]
Settings["Einstellungen"]
History["History"]
end
subgraph Backend["Node.js Backend"]
API["REST API\n(Express)"]
WS["WebSocket\nServer"]
Pipeline["Pipeline\nService"]
DB["SQLite\nDatenbank"]
end
subgraph ExternalTools["Externe Tools"]
MakeMKV["makemkvcon"]
HandBrake["HandBrakeCLI"]
MediaInfo["mediainfo"]
end
subgraph ExternalAPIs["Externe APIs"]
OMDb["OMDb API"]
PushOver["PushOver"]
end
Browser <-->|HTTP REST| API
Browser <-->|WebSocket| WS
Pipeline --> MakeMKV
Pipeline --> HandBrake
Pipeline --> MediaInfo
Pipeline <-->|Metadaten| OMDb
Pipeline -->|Benachrichtigungen| PushOver
API --> DB
Pipeline --> DB
```
---
## Schichten-Architektur
### Backend
```
index.js (Express Server)
├── Routes (API-Endpunkte)
│ ├── pipelineRoutes.js
│ ├── settingsRoutes.js
│ └── historyRoutes.js
├── Services (Business Logic)
│ ├── pipelineService.js ← Kern-Orchestrierung
│ ├── diskDetectionService.js
│ ├── processRunner.js
│ ├── websocketService.js
│ ├── omdbService.js
│ ├── settingsService.js
│ ├── notificationService.js
│ ├── historyService.js
│ └── logger.js
├── Database
│ ├── database.js
│ └── defaultSettings.js
└── Utils
├── encodePlan.js
├── playlistAnalysis.js
├── progressParsers.js
└── files.js
```
### Frontend
```
App.jsx (React Router)
├── Pages
│ ├── DashboardPage.jsx ← Haupt-Interface
│ ├── SettingsPage.jsx
│ └── HistoryPage.jsx
├── Components
│ ├── PipelineStatusCard.jsx
│ ├── MetadataSelectionDialog.jsx
│ ├── MediaInfoReviewPanel.jsx
│ ├── DynamicSettingsForm.jsx
│ └── JobDetailDialog.jsx
├── Hooks
│ └── useWebSocket.js
└── API
└── client.js
```
---
## Weiterführende Dokumentation
<div class="grid cards" markdown>
- [:octicons-arrow-right-24: Übersicht](overview.md)
- [:octicons-arrow-right-24: Backend-Services](backend.md)
- [:octicons-arrow-right-24: Frontend-Komponenten](frontend.md)
- [:octicons-arrow-right-24: Datenbank](database.md)
</div>

View File

@@ -0,0 +1,144 @@
# Architektur-Übersicht
---
## Kern-Designprinzipien
### Event-Driven Pipeline
Der gesamte Ripping-Workflow ist als **State Machine** implementiert. Der `pipelineService` verwaltet den aktuellen Zustand und emittiert Ereignisse bei jedem Zustandswechsel. Der WebSocket-Service überträgt diese Ereignisse sofort an alle verbundenen Clients.
```
Zustandswechsel → Event → WebSocket → Frontend-Update
```
### Service-Layer-Muster
```
HTTP-Route → Service → Datenbank
```
Routes delegieren die gesamte Business-Logik an Services. Services sind voneinander unabhängig und können einzeln getestet werden.
### Schema-getriebene Einstellungen
Die Settings-Konfiguration definiert **sowohl** die Validierungsregeln als auch die UI-Struktur in einer einzigen Quelle (`settings_schema`-Tabelle). Die `DynamicSettingsForm`-Komponente rendert das Formular dynamisch aus dem Schema.
---
## Echtzeit-Kommunikation
### WebSocket-Protokoll
Der WebSocket-Server läuft unter dem Pfad `/ws`. Nachrichten werden als JSON übertragen:
```json
{
"type": "PIPELINE_STATE_CHANGE",
"data": {
"state": "ENCODING",
"jobId": 42,
"progress": 73.5,
"eta": "00:12:34"
}
}
```
**Nachrichtentypen:**
| Typ | Beschreibung |
|----|-------------|
| `PIPELINE_STATE_CHANGE` | Pipeline-Zustand hat gewechselt |
| `PROGRESS_UPDATE` | Fortschritt (% und ETA) |
| `DISC_DETECTED` | Disc wurde erkannt |
| `DISC_REMOVED` | Disc wurde entfernt |
| `ERROR` | Fehler aufgetreten |
| `JOB_COMPLETE` | Job abgeschlossen |
### Reconnect-Logik
Der Frontend-Hook `useWebSocket.js` implementiert automatisches Reconnect mit exponential backoff bei Verbindungsabbrüchen.
---
## Prozess-Management
### processRunner.js
Externe Tools (MakeMKV, HandBrake, MediaInfo) werden als **Child Processes** gestartet:
```js
spawn(command, args, { stdio: ['ignore', 'pipe', 'pipe'] })
```
- **stdout/stderr** werden zeilenweise gelesen und in Echtzeit verarbeitet
- **Progress-Parsing** erfolgt über reguläre Ausdrücke in `progressParsers.js`
- **Graceful Shutdown**: SIGINT → Timeout → SIGKILL
- **Prozess-Tracking**: Aktive Prozesse werden registriert für sauberes Beenden
---
## Datenpersistenz
### SQLite-Datenbank
Ripster verwendet eine **einzige SQLite-Datei** für alle persistenten Daten:
```
backend/data/ripster.db
```
**Tabellen:**
| Tabelle | Inhalt |
|---------|--------|
| `jobs` | Alle Rip-Jobs mit Status, Logs, Metadaten |
| `pipeline_state` | Aktueller Pipeline-Zustand (Singleton) |
| `settings_schema` | Schema aller verfügbaren Einstellungen |
| `settings_values` | Benutzer-konfigurierte Werte |
### Migrations-Strategie
Beim Start prüft `database.js` automatisch, ob das Schema aktuell ist, und führt fehlende Migrationen aus. Korrupte Datenbankdateien werden in ein Quarantäne-Verzeichnis verschoben und eine neue Datenbank erstellt.
---
## Fehlerbehandlung
### Strukturierte Fehler
Alle Fehler werden mit Kontext-Metadaten protokolliert:
```js
logger.error('Encoding fehlgeschlagen', {
jobId: job.id,
command: cmd,
exitCode: code,
stderr: lastLines
});
```
### Job-Fehler-Recovery
- Fehlgeschlagene Jobs bleiben in der Datenbank (Status `ERROR`)
- Vollständige Fehler-Logs werden im Job-Datensatz gespeichert
- **Retry-Funktion** ermöglicht Neustart von einem Fehler-Zustand
- **Re-Encode** erlaubt erneutes Encodieren ohne neu zu rippen
---
## Sicherheit
### Eingabe-Validierung
- Alle Benutzer-Eingaben werden in `validators.js` validiert
- CLI-Argumente werden sicher über `commandLine.js` konstruiert (kein Shell-Injection-Risiko)
- Pfade werden sanitisiert bevor sie an externe Prozesse übergeben werden
### CORS-Konfiguration
```env
CORS_ORIGIN=http://localhost:5173
```
In Produktion sollte dieser Wert auf die tatsächliche Frontend-URL gesetzt werden.