Zum Hauptinhalt springen

Watchers

Watchers sind die zentrale Entitaet in Postbox. Ein Watcher repraesentiert ein beobachtetes Social-Media-Profil innerhalb eines Workspaces. Die Verknuepfung zwischen Profil und Workspace erfolgt ueber WatcherSource.

Datenmodell

ModelTabelleBeschreibung
WatcherwatchersContainer im Workspace (Name, erstellt von)
WatcherSourcewatcher_sourcesVerknuepfung Watcher zu SocialProfile (Platform, Input-URL)
WatcherImportRunwatcher_import_runsBatch-Import-Tracking (Status, Zaehler)
WatcherImportItemwatcher_import_itemsEinzelne URLs eines Import-Runs
WatcherImportFailurewatcher_import_failuresFehlgeschlagene Import-Versuche mit Fehlerdetails

Location: app/Models/Watcher.php, app/Models/WatcherSource.php, app/Models/WatcherImportRun.php

Watcher-Erstellung

Einzelner Watcher

Ueber die Watcher-Detailseite oder direkte URL-Eingabe. Der Flow:

  1. User gibt eine YouTube- oder Instagram-URL ein
  2. YouTubeUrlParser::parse() bzw. InstagramUrlParser::parse() extrahiert den Key (Channel-ID, Handle)
  3. ImportWatcherFromUrl Job wird dispatcht
  4. Job ruft EnsureSocialProfileFromUrl auf, um das Profil global anzulegen oder wiederzuverwenden
  5. Watcher + WatcherSource werden im Workspace erstellt

Location: app/Jobs/ImportWatcherFromUrl.php, app/Services/Social/Actions/EnsureSocialProfileFromUrl.php

Multi-Import (Textarea)

User kann mehrere URLs gleichzeitig importieren, eine pro Zeile. YouTube- und Instagram-URLs koennen gemischt werden.

// Limit: 10.000 Zeilen (konfigurierbar via config('watchers.multi_import_limit'))
private const MULTI_IMPORT_LIMIT = 10000;

Der Import-Flow:

  1. User oeffnet das Multi-Import-Modal und fuegt URLs ein
  2. Jede Zeile wird durch YouTubeUrlParser und InstagramUrlParser geparsed (Fallback-Kette)
  3. Duplikate innerhalb des Inputs werden erkannt und uebersprungen
  4. Ein WatcherImportRun wird erstellt mit Status running
  5. Pro URL wird ein WatcherImportItem gespeichert
  6. YouTube-URLs werden als ImportWatcherFromUrl Jobs auf imports-youtube-priority dispatcht
  7. Instagram-URLs werden als Collector-Jobs via CollectorJobDispatcher dispatcht

Location: app/Livewire/Watchers/Index.php (Methode submitMultiImport())

CSV-Upload

Zusaetzlich zum Textarea-Import koennen CSV-Dateien hochgeladen werden. URLs werden aus allen Zellen extrahiert, auch mehrere pro Zelle (getrennt durch Whitespace oder Komma).

// CSV-Verarbeitung via Spatie SimpleExcel
$rows = SimpleExcelReader::create($path)->getRows();

CSV- und Textarea-Input werden zusammengefuehrt und gemeinsam verarbeitet.

Location: app/Livewire/Watchers/Index.php (Methode collectCsvLines())

URL-Erkennung

Die Parser erkennen verschiedene URL-Formate:

PlattformUnterstuetzte Formate
YouTubeyoutube.com/@handle, youtube.com/channel/UC..., youtube.com/c/name, youtu.be/...
Instagraminstagram.com/handle, instagram.com/p/...

Jeder Parser liefert:

  • key: Deduplizierungs-Schluessel (Channel-ID oder Handle)
  • normalizedUrl: Kanonische URL fuer stabiles Scraping

Location: app/Services/Social/YouTubeUrlParser.php, app/Services/Social/InstagramUrlParser.php

Import Runs: Status-Lifecycle

Ein WatcherImportRun durchlaeuft diese Status:

StatusBeschreibung
runningImport laeuft, Jobs werden abgearbeitet
pausedImport pausiert (z.B. wegen API-Quota, pause_reason = 'quota')
completedAlle Items verarbeitet
failedImport fehlgeschlagen

Zaehler auf dem Import Run:

FeldBeschreibung
total_countGesamtzahl erkannter URLs
processed_countBisher verarbeitete URLs
added_countErfolgreich hinzugefuegt
duplicates_in_input_countDuplikate im Input
already_in_workspace_countBereits im Workspace vorhanden
global_reused_countGlobal existierendes Profil wiederverwendet
failed_countFehlgeschlagene Imports

Import Failures

Fehlgeschlagene Import-Versuche werden in watcher_import_failures protokolliert. Die UI zeigt maximal 50 Failures pro Run an, um die Payload-Groesse zu begrenzen. Bei Quota-Pause wird die Failure-Liste nicht geladen.

Location: app/Models/WatcherImportFailure.php

Rescrape-Feature

Admins koennen einen Priority-Rescrape fuer einzelne Watcher ausloesen. Der Flow:

  1. Admin klickt "Rescrape" auf der Watcher-Detailseite
  2. Pro Source wird ein neuer WatcherImportRun erstellt
  3. YouTube-Sources werden auf imports-youtube-priority dispatcht
  4. Instagram-Sources werden mit erhoehter Prioritaet als Collector-Jobs dispatcht (force: true)
// Instagram: Priority ueber aktuellem Maximum
$maxPriority = (int) CollectorJob::query()
->where('status', 'queued')
->max('priority');

Location: app/Livewire/Watchers/Show.php (Methode queuePriorityRescrape())

Move-Flow

Watcher koennen zwischen Workspaces verschoben werden (Einzel- und Bulk-Move):

  1. User waehlt Watcher aus und waehlt Ziel-Workspace
  2. WatcherMover::moveWatchers() prueft auf Duplikate im Ziel
  3. Bei Duplikat: Watcher werden zusammengefuehrt (merged)
  4. Favoriten- und Admin-Workspace sind als Ziel gesperrt

Location: app/Services/Watchers/WatcherMover.php, app/Livewire/Watchers/Index.php (Methode moveSelectedWatchers())

Delete-Flow

Beim Loeschen eines Watchers:

  1. WatcherSource-Eintraege werden entfernt
  2. Watcher wird geloescht
  3. Das globale SocialProfile bleibt erhalten (wird weiter getrackt)
  4. Sonderfall Favoriten-Workspace: Beim Loeschen wird auch der Favorit-Marker entfernt
// Admin-Hinweis nach dem Loeschen
if (Gate::allows('admin')) {
$this->dispatch('show-toast', [
'message' => 'Admin: Das Profil bleibt intern weiter getrackt.',
'type' => 'info',
]);
}

Location: app/Livewire/Watchers/Index.php (Methode delete()), app/Livewire/Watchers/Show.php (Methode deleteWatcher())

Blocked-Watcher-Sichtbarkeit

Watchers, deren verknuepfte Profile von einem Admin gesperrt wurden (blocked_at), sind fuer regulaere User nicht sichtbar. Admins sehen gesperrte Watcher weiterhin mit einem roten Banner.

Verhalten

KontextRegulaere UserAdmins
Watcher-ListeGesperrte Watcher ausgeblendetSichtbar mit rotem Banner
Dashboard-UebersichtGesperrte Watcher ausgeblendetSichtbar
Workspace-StatistikenGesperrte Watcher nicht gezaehltGezaehlt
Watcher-Detailseite (Direktzugriff)Redirect zur Watcher-Liste + ToastSichtbar mit rotem Banner

Technische Umsetzung

  • Scope: Watcher::withoutBlockedProfiles() — filtert Watchers mit gesperrten Profilen per whereDoesntHave('sources.profile', blocked_at)
  • Methode: Watcher::hasBlockedProfile() — prueft ob ein Watcher gesperrte Profile hat (nutzt vorgeladene Relations wenn verfuegbar)
  • Show-Seite: Nicht-Admins werden beim Mount redirected, Admins sehen $isProfileBlocked = true Banner

Location: app/Models/Watcher.php, app/Livewire/Watchers/Index.php, app/Livewire/Watchers/Show.php

UI-Komponenten

KomponenteRouteBeschreibung
Watchers\Index/watchers, /workspace/{workspace}Watcher-Liste mit Multi-Import, Filter, Sortierung, Bulk-Aktionen
Watchers\Show/watcher/{watcher}Detailseite mit Metriken-Charts, Profil-Info, Related, Score
Watchers\ContactLinks(Sub-Component)Kontakt-Links verwalten
Watchers\RelatedChannels(Sub-Component)Aehnliche YouTube-Kanaele
Watchers\RelatedProfiles(Sub-Component)Aehnliche Instagram-Profile
Watchers\CrossPlatformRelated(Sub-Component)Cross-Platform Verknuepfungen
Watchers\ScoreChart(Sub-Component)Postbox Score Verlauf
Watchers\AggregatedVideoChart(Sub-Component)Channel-weite aggregierte Video-Performance (Summe der taeglichen View/Like-Deltas ueber alle Videos)
Watchers\VideoStatsChart(Sub-Component)Einzelvideo-Statistiken mit Views/Likes-Chart, Engagement-Rate und Rotations-Status (active/archived/new/removed)
Watchers\AddYouTubeVideoByUrl(Sub-Component)Admin: Video per YouTube-URL manuell hinzufuegen

Location: app/Livewire/Watchers/, resources/views/livewire/watchers/

Admin: Video manuell hinzufuegen

Admins koennen auf der Watcher-Detailseite manuell YouTube-Videos per URL hinzufuegen. Voraussetzung: YouTube-Profil mit PRO + Auto-Sync aktiviert.

Flow:

  1. Admin klickt „Video hinzufuegen" im Aktionen-Dropdown
  2. Flux Named-Modal (add-video-modal) oeffnet sich
  3. Admin gibt YouTube-URL ein (watch, youtu.be, shorts)
  4. YouTubeVideoUrlResolver::resolve() validiert, fetcht und speichert das Video
  5. Ergebnis wird im Modal angezeigt (erstellt/existiert/Fehler)

Technische Details:

  • Trigger: flux:modal.trigger name="add-video-modal" im Aktionen-Dropdown
  • Modal: AddYouTubeVideoByUrl Livewire-Component mit Named Flux Modal
  • Formular-Reset beim Schliessen via x-on:close="$wire.resetForm()"

Location: app/Livewire/Watchers/AddYouTubeVideoByUrl.php, app/Services/Social/YouTube/YouTubeVideoUrlResolver.php