Admin & Wartung Commands
Commands für die Administration: Workspace-Management, Profil-Wartung, Pruning, Retry-Logik und Notifications.
watchers:backfill-admin-workspace
Spiegelt alle Social Profiles in den Admin-Workspace (ID: 999999999999).
Der Admin-Workspace hat damit Zugriff auf alle Profile im System.
watchers:backfill-admin-workspace
{--platform= : Filter (youtube, instagram)}
{--dry-run : Nur Statistiken anzeigen}
Beispiele
# Alle Profile spiegeln
php artisan watchers:backfill-admin-workspace
# Nur YouTube-Profile
php artisan watchers:backfill-admin-workspace --platform=youtube
# Statistiken anzeigen ohne Änderungen
php artisan watchers:backfill-admin-workspace --dry-run
Optionen
| Option | Beschreibung |
|---|---|
--platform= | youtube oder instagram |
--dry-run | Zeigt Total/Existing/Missing Counts ohne Watcher zu erstellen |
Interne Schritte
- Admin-Workspace via
AdminWorkspaceManager::ensureAdminWorkspace()sicherstellen - Alle Profile mit
external_idladen, optional nach Plattform filtern - Vorhandene Admin-Watcher zählen
- Fehlende Watcher via
ensureProfileWatcher()erstellen (in 200er Chunks) - Progress-Bar und Fehler-Logging
Schedule
Stündlich mit .withoutOverlapping().
Location: app/Console/Commands/BackfillAdminWorkspaceWatchers.php
watchers:resume-imports
Setzt pausierte Watcher-Import-Runs automatisch fort, sobald paused_until erreicht ist.
Dies ist ein Schedule-Call (kein Artisan Command), definiert in routes/console.php.
watchers:resume-imports
Interne Schritte
WatcherImportRunRecords mitstatus = 'paused'laden- Filter:
paused_until IS NULL OR paused_until <= now() - Status auf
runningsetzen,paused_untilundpause_reasonleeren - Bereits in der Queue "geparkte" Jobs laufen automatisch weiter
Hinweise
- Es werden bewusst keine neuen
ImportWatcherFromUrlJobs dispatched - Die bestehenden Queue-Jobs werden durch den Status-Wechsel freigeschaltet
- Verhindert Doppel-Dispatch und damit falsche Counts / doppelte API-Calls
Schedule
Alle 5 Minuten mit .withoutOverlapping().
Location: routes/console.php (Schedule::call)
imports:retry-failed
Recovery-Command für fehlgeschlagene Import-Runs. Erstellt frische ImportWatcherFromUrl-Jobs mit dem aktuellen Code ($tries = 0, retryUntil = 48h), anstatt alte Payloads via queue:retry erneut zu pushen.
imports:retry-failed
{--run= : Komma-getrennte Run-IDs (z. B. --run=747,748,749)}
{--from= : Startzeitpunkt für Run-Suche (z. B. 2026-02-07)}
{--to= : Endzeitpunkt für Run-Suche (z. B. 2026-02-10)}
{--workspace= : Filter auf Workspace-ID}
{--source= : URL-Quelle: "research" liest URLs direkt aus youtube_research_queries}
{--chunk=500 : Anzahl Jobs pro Batch}
{--delay=0 : Sekunden Verzögerung zwischen Batches}
{--queue=imports-youtube : Ziel-Queue für die neuen Jobs}
{--include-failed-jobs : Auch Laravel failed_jobs-Tabelle durchsuchen}
{--dry-run : Nur anzeigen, was passieren würde}
Beispiele
# Bestimmte Runs wiederholen (Vorschau)
php artisan imports:retry-failed --run=747,748,749 --dry-run
# Zeitraum-basierte Suche
php artisan imports:retry-failed --from=2026-02-07 --to=2026-02-10
# Mit failed_jobs-Tabelle, 200er Batches, 5s Pause
php artisan imports:retry-failed --run=750 --include-failed-jobs --chunk=200 --delay=5
# Bestimmter Workspace, andere Queue
php artisan imports:retry-failed --from=2026-02-07 --to=2026-02-10 --workspace=42 --queue=imports-youtube-priority
# Research-URLs als Quelle (Recovery nach Admin-WS-Guard-Bug)
php artisan imports:retry-failed --source=research --from=2026-02-10 --to=2026-02-14 --dry-run
# Research-URLs importieren (Admin-Workspace als Default-Ziel)
php artisan imports:retry-failed --source=research --from=2026-02-10 --to=2026-02-14
Optionen
| Option | Typ | Default | Beschreibung |
|---|---|---|---|
--run= | string | -- | Komma-getrennte Run-IDs |
--from= | date | -- | Start-Datum (alternativ zu --run) |
--to= | date | -- | End-Datum (alternativ zu --run) |
--workspace= | int | -- | Workspace-Filter (bei --source=research: Admin-WS als Default) |
--source= | string | -- | URL-Quelle: research liest aus youtube_research_queries |
--chunk= | int | 500 | Jobs pro Dispatch-Batch |
--delay= | int | 0 | Sekunden Pause zwischen Batches |
--queue= | string | imports-youtube | Ziel-Queue |
--include-failed-jobs | flag | -- | Auch failed_jobs-Tabelle durchsuchen |
--dry-run | flag | -- | Vorschau ohne Dispatch |
URL-Quellen
Standard-Modus (ohne --source)
Der Command sammelt URLs aus drei Quellen in Prioritaetsreihenfolge:
| Quelle | Tabelle | Beschreibung |
|---|---|---|
| 1. Failures | watcher_import_failures | URLs aus failed()-Callback (zuverlaessigste Quelle) |
| 2. Pending Items | watcher_import_items | Noch nicht verarbeitete URLs |
| 3. Failed Jobs | failed_jobs | Fallback: URL per Regex aus serialisiertem Payload extrahiert |
Duplikate werden automatisch entfernt (URL als Key).
Research-Modus (--source=research)
Liest URLs direkt aus youtube_research_queries.result_urls (JSON-Spalte). Filtert auf Queries mit gesetztem imported_at im angegebenen Datumsbereich. Nützlich für Recovery wenn Imports lautlos fehlgeschlagen sind und weder watcher_import_failures noch watcher_import_items Eintraege enthalten (z.B. nach dem Admin-WS-Guard-Bug).
- Zeigt eine Tabelle aller gefundenen Research-Queries (ID, Query, URL-Anzahl, Import-Datum)
- URLs werden query-übergreifend dedupliziert
- Standard-Ziel: Admin-Workspace (
AdminWorkspaceManager::ADMIN_WORKSPACE_ID)
Interne Schritte
- Run-IDs ermitteln (via
--runoder--from/--toQuery) — oder Research-Queries laden bei--source=research - Run-Statistiken als Tabelle anzeigen (bzw. Query-Übersicht bei Research)
- URLs aus allen Quellen sammeln und deduplizieren
- Bei
--dry-run: Vorschau (max 20 URLs) anzeigen und beenden - Interaktive Bestaetigung
- Neuen
WatcherImportRunerstellen (Statusrunning) WatcherImportItem-Records in 1000er-Chunks einfuegenImportWatcherFromUrl-Jobs in konfigurierbaren Batches dispatchen (Progress-Bar)- Ergebnis loggen
Warum nicht queue:retry?
queue:retry re-pushed die originale Payload mit baked-in maxTries: 3. Aenderungen am Job-Code (z.B. $tries = 0) haben keinen Effekt auf bereits serialisierte Jobs. imports:retry-failed erstellt komplett neue Jobs mit dem aktuellen Code.
Schedule
Manuell (kein automatischer Schedule).
Location: app/Console/Commands/RetryFailedImportRun.php
watchers:prune-by-metrics
Löscht Watcher global deren Latest-Metriken die angegebenen Schwellwerte/Ranges matchen. Verwaiste Social Profiles werden physisch gelöscht (Metriken + Bilder + Dateien).
watchers:prune-by-metrics {type}
{--followers=} {--following=} {--videos=} {--posts=}
{--min-followers=} {--max-followers=}
{--min-following=} {--max-following=}
{--min-videos=} {--max-videos=}
{--min-posts=} {--max-posts=}
{--confirm : Bestätigung überspringen}
Beispiele
# Instagram Watcher mit 0/0/0 Metriken löschen
php artisan watchers:prune-by-metrics instagram --followers=0 --following=0 --posts=0
# YouTube Watcher mit 0 Followern und 0 Videos
php artisan watchers:prune-by-metrics youtube --followers=0 --videos=0
# Follower-Range ohne Prompt
php artisan watchers:prune-by-metrics youtube --min-followers=1000 --max-followers=5000 --confirm
Optionen
| Option | Beschreibung |
|---|---|
type | Plattform: instagram oder youtube |
--followers= | Exakter Follower-Wert |
--min-followers= / --max-followers= | Follower-Range |
--following= / --min-following= / --max-following= | Following-Filter |
--videos= / --min-videos= / --max-videos= | Video-Filter |
--posts= / --min-posts= / --max-posts= | Post-Filter |
--confirm | Interaktive Bestätigung überspringen |
Interne Schritte
- Mindestens ein Metrik-Filter erforderlich
- Watcher und
watcher_sourcesglobal löschen (über alle Workspaces) - Verwaiste Social Profiles hard-deleten (analog zum Admin "Final Delete")
- Auch wenn keine Watcher matchen: Prüfung auf verwaiste Profile (mit Bestätigung)
Schedule
Manuell (kein automatischer Schedule).
Location: app/Console/Commands/PruneWatchersByMetrics.php
watchers:prune-to-max
Löscht Watcher global bis ein Maximum pro Plattform erreicht ist. Entfernt die Profile mit den niedrigsten Follower-Zahlen zuerst.
watchers:prune-to-max {type}
{--max= : Maximale Watcher-Anzahl}
Beispiele
# YouTube auf 250 Watcher begrenzen
php artisan watchers:prune-to-max youtube --max=250
# Instagram auf 500 Watcher begrenzen
php artisan watchers:prune-to-max instagram --max=500
Optionen
| Option | Beschreibung |
|---|---|
type | Plattform: instagram oder youtube |
--max= | Maximale Watcher-Anzahl (Pflicht) |
Interne Schritte
- Sortierung nach Latest
followers_count(niedrigste zuerst) - Überzählige Watcher und
watcher_sourcesglobal löschen - Verwaiste Social Profiles hard-deleten (Metriken + Bilder + Dateien)
- Interaktive Bestätigung vor dem Löschen
Schedule
Manuell.
Location: app/Console/Commands/PruneWatchersToMax.php
profiles:retry-inactive
Retry-Mechanismus für Profile die durch Fail-Streaks deaktiviert wurden. Archiviert Profile die zu lange inaktiv sind (> 6 Monate).
profiles:retry-inactive
{--platform= : Filter (youtube, instagram)}
{--limit=1000 : Maximale Profile pro Run}
{--dry-run : Vorschau ohne Job-Dispatch}
Beispiele
# Standard-Lauf
php artisan profiles:retry-inactive
# Nur YouTube, Vorschau
php artisan profiles:retry-inactive --platform=youtube --dry-run
# Begrenzter Lauf
php artisan profiles:retry-inactive --limit=100
Optionen
| Option | Beschreibung |
|---|---|
--platform= | youtube oder instagram |
--limit=1000 | Max Profile pro Run |
--dry-run | Tabelle mit Profilen anzeigen, keine Jobs dispatchen |
Fail-Streak-Cooldown (vor Deaktivierung)
Bevor ein Profil deaktiviert wird (Streak < Threshold), greift ein Cooldown-Mechanismus in den Daily-Scrape-Commands (social:scrape-daily-followers, social:queue-daily-instagram):
| Phase | Verhalten | Bedingung |
|---|---|---|
| Normal (Streak 0-2) | Täglich gescrapt | scrape_fail_streak < 3 |
| Cooldown (Streak 3-13) | Nur alle 3 Tage | scrape_fail_streak >= 3, basierend auf last_scrape_failed_at |
| Deaktivierung | Profil wird deaktiviert | scrape_fail_streak >= 14 |
Die Cooldown-Logik basiert auf last_scrape_failed_at (nicht auf dem Streak-Zähler), weil der Streak nicht inkrementiert wird wenn das Profil übersprungen wird.
Konfiguration:
# Ab welchem Fail-Streak der Cooldown greift (Default: 3)
POSTBOX_FAIL_STREAK_COOLDOWN_AFTER=3
# Tage zwischen Retry-Versuchen im Cooldown (Default: 3)
POSTBOX_FAIL_STREAK_COOLDOWN_DAYS=3
Retry-Strategie (nach Deaktivierung)
| Phase | Intervall | Bedingung |
|---|---|---|
| Initial (Retry 1-3) | 14 Tage | retry_count < 3 |
| Extended (Retry 4+) | 30 Tage (monatlich) | retry_count >= 3 |
| Archive | - | deactivated_at > 6 Monate |
Interne Schritte
- Phase 1: Archivieren - Profile mit
deactivated_at> 6 Monate archivieren- Setzt
archived_at,archive_reason,next_retry_at = null
- Setzt
- Phase 2: Retry - Profile mit
next_retry_at <= now()laden- Filter:
archived_at IS NULL,tracking_enabled = false RetryInactiveProfileScrapeJobs aufprofile-retryQueue dispatchen- Sortierung nach
next_retry_atASC
- Filter:
Konfiguration
POSTBOX_PROFILE_RETRY_FAIL_STREAK_THRESHOLD=14
POSTBOX_PROFILE_RETRY_ARCHIVE_AFTER_MONTHS=6
Schedule
Täglich um 04:00 UTC mit .withoutOverlapping().
Location: app/Console/Commands/RetryInactiveProfiles.php
profiles:sanitize
Automatische Deaktivierung von Low-Value Profilen basierend auf Metrik-Regeln. Zwei Phasen: Neue Kandidaten sanitizen + bereits sanitized Profile re-checken.
profiles:sanitize
{--platform= : Filter (youtube, instagram)}
{--dry-run : Vorschau ohne Ausführung}
{--limit=5000 : Maximale Profile pro Run}
Beispiele
# Standard-Lauf
php artisan profiles:sanitize
# Nur Instagram, Vorschau
php artisan profiles:sanitize --platform=instagram --dry-run
# Begrenzter Lauf
php artisan profiles:sanitize --limit=1000
Sanitization-Regeln
| Plattform | Bedingung | Aktion |
|---|---|---|
| YouTube | video_count < 1 AND followers_count < 100 | Deaktivieren |
post_count < 1 AND followers_count < 100 | Deaktivieren |
Schutz-Mechanismen
| Schutz | Beschreibung |
|---|---|
| Min. Alter | Profil muss >= min_age_days alt sein |
| Min. Datenpunkte | >= min_data_points Metriken-Einträge erforderlich |
| Blocked/Archived/Deactivated | Werden übersprungen |
| Admin Override | sanitize_checked_at IS NOT NULL AND sanitized_at IS NULL = permanent geschützt |
Interne Schritte
- Phase 1: Neue Kandidaten -- Aktive Profile gegen Regeln prüfen, bei Match deaktivieren
- Setzt
sanitized_at,sanitize_reason,tracking_enabled = false - Dispatcht
ProfileSanitizedEvent (Broadcast an betroffene User)
- Setzt
- Phase 2: Re-Check -- Bereits sanitized Profile alle 3 Monate erneut prüfen
- Falls Regeln nicht mehr zutreffen: Profil reaktivieren
- Dispatcht
ProfileUnsanitizedEvent
Admin Override Flow
- Admin findet sanitized Profil in
/admin/social-profiles - Admin klickt "Enable" ->
enableTracking()setztsanitized_at = null, behältsanitize_checked_at - Sanitizer überspringt Profile mit
sanitize_checked_at IS NOT NULL AND sanitized_at IS NULL
Schedule
Täglich um 04:30 UTC (nach profiles:retry-inactive).
Location: app/Console/Commands/SanitizeInactiveProfiles.php
cross-platform:queue-related
Queued Cross-Platform Related Profile Berechnungen (Bulk oder Einzelprofil). Findet Instagram-Profile für YouTube-Channels und umgekehrt, nur über lokale Daten (keine API-Calls, keine Quota-Kosten).
cross-platform:queue-related
{--profile= : Einzelnes Profil per ID queuen}
{--platform= : Source-Plattform (youtube, instagram)}
{--days= : Nur Profile nicht berechnet in letzten N Tagen (Default: config, 90)}
{--limit=500 : Max Profile pro Run}
{--force : Neuberechnung erzwingen}
Optionen
| Option | Typ | Default | Beschreibung |
|---|---|---|---|
--profile= | int | – | Einzelnes Profil per ID queuen (Bulk-Filter werden ignoriert) |
--platform= | string | beide | Source-Plattform: youtube oder instagram |
--days= | int | 90 (Config) | Stale-Schwelle in Tagen (config('postbox.cross_platform.stale_days')) |
--limit= | int | 500 | Maximale Anzahl Profile pro Bulk-Lauf |
--force | flag | – | Neuberechnung erzwingen, auch für kürzlich berechnete Profile |
Beispiele
# Einzelnes Profil manuell berechnen (sofort dispatched)
php artisan cross-platform:queue-related --profile=12345
# Standard Bulk-Lauf (alle Plattformen, 90 Tage stale, Limit 500)
php artisan cross-platform:queue-related
# Nur YouTube → Instagram (findet Instagram-Profile zu YouTube-Channels)
php artisan cross-platform:queue-related --platform=youtube
# Nur Instagram → YouTube (findet YouTube-Channels zu Instagram-Profilen)
php artisan cross-platform:queue-related --platform=instagram
# Force Recalculation für alle, Limit 100
php artisan cross-platform:queue-related --force --limit=100
# Profile die in den letzten 30 Tagen nicht berechnet wurden
php artisan cross-platform:queue-related --days=30
Matching-Score (0-100)
| Kriterium | Punkte | Beschreibung |
|---|---|---|
| Keywords | 30 | AI-detected Keywords Match |
| Category | 25 | AI-detected Category Match |
| Favorites | 15 | Bonus für favorisierte Profile |
| Follower Tier | 10 | Ähnliche Follower-Größenordnung |
| Name Similarity | 10 | Handle/Titel-Ähnlichkeit |
| Language/Country | 10 | Sprach-/Land-Match |
Interne Logik
- Single-Profile Mode (
--profile=ID): Lädt das Profil direkt, prüft obyoutube/instagram, dispatchedFindCrossPlatformRelatedProfilesJob - Bulk Mode: Nutzt
followers_countdirekt aussocial_profiles(kein teurer Metrics-Join), filtert nachtracking_enabled,notExcluded()und Stale-Schwelle - Memory-Safe: Nur IDs werden geladen (
pluck('id')) statt vollständiger Eloquent-Models — ermöglicht Limits von 1M+ ohne OOM. Dispatch in 1000er-Chunks. - Priorität: Noch nie berechnete Profile zuerst (nach Followern absteigend), dann veraltete
Job
Dispatched FindCrossPlatformRelatedProfiles auf Queue cross-platform-related.
| Property | Wert |
|---|---|
| Queue | cross-platform-related |
| Tries | 3 |
| Unique For | 3600 (1 Stunde) |
| Backoff | [60, 300, 900] (1min, 5min, 15min) |
Schedule
Täglich um 06:00 UTC mit --limit=200.
Location: app/Console/Commands/QueueCrossPlatformRelated.php
cross-platform:auto-fill-related
Dispatched Cross-Platform Related Jobs in kleinen Batches für graduellen Ausbau. Läuft alle 5 Minuten und füllt automatisch Lücken auf (keine API-Kosten).
cross-platform:auto-fill-related
{--batch=5 : Anzahl Profile pro Run}
{--dry-run : Vorschau ohne Jobs zu dispatchen}
Optionen
| Option | Typ | Default | Beschreibung |
|---|---|---|---|
--batch= | int | 5 | Anzahl Profile pro Durchlauf |
--dry-run | flag | – | Nur Anzeige, keine Jobs dispatchen |
Beispiele
# Standard: 5 Profile pro Lauf
php artisan cross-platform:auto-fill-related
# Größerer Batch (z.B. initialer Backfill)
php artisan cross-platform:auto-fill-related --batch=20
# Vorschau: welche Profile würden berechnet?
php artisan cross-platform:auto-fill-related --dry-run
Interne Logik
- Queue-Guard: Überspringt, wenn Queue bereits ≥
batch * 3offene Jobs hat - Phase 1 — Noch nie berechnete Profile (nach Followern absteigend)
- Phase 2 — Veraltete Profile (älteste zuerst,
stale_daysaus Config, Default 90 Tage) - Output: Für jedes Profil: Name, Plattform, Follower-Anzahl und Grund (never calculated / stale)
Schedule
Alle 5 Minuten mit ->withoutOverlapping().
Location: app/Console/Commands/AutoFillCrossPlatformRelated.php
social:recalculate-related-scores
Berechnet Relevance-Scores für bestehende Related-Beziehungen (YouTube + Instagram) basierend auf aktuellen Follower-Daten neu. Führt keine neuen API-Suchen durch — aktualisiert nur die Scores bestehender Beziehungen.
social:recalculate-related-scores
{--platform=all : Plattform (youtube, instagram, all)}
{--only-existing : Nur Profile mit bestehenden Related-Beziehungen}
{--dry-run : Vorschau ohne Jobs zu dispatchen}
{--sync : Synchron ausführen statt Queue}
Optionen
| Option | Typ | Default | Beschreibung |
|---|---|---|---|
--platform= | string | all | Plattform-Filter: youtube, instagram oder all |
--only-existing | flag | – | Nur Profile mit mindestens einer bestehenden Related-Beziehung |
--dry-run | flag | – | Nur Anzeige, keine Jobs dispatchen |
--sync | flag | – | Synchron ausführen (statt über Queue), nützlich für Debugging |
Beispiele
# Alle Plattformen, über Queue
php artisan social:recalculate-related-scores
# Nur Instagram, nur Profile mit bestehenden Beziehungen
php artisan social:recalculate-related-scores --platform=instagram --only-existing
# YouTube synchron (direkt, ohne Queue)
php artisan social:recalculate-related-scores --platform=youtube --sync
# Vorschau
php artisan social:recalculate-related-scores --dry-run
Interne Logik
- YouTube: Dispatched
RecalculateRelatedChannelScorespro YouTube-Profil auf Queueyoutube-related-channels - Instagram: Dispatched
RecalculateRelatedInstagramScorespro Instagram-Profil auf Queueinstagram-related-profiles --only-existing: Prüftyoutube_related_channels/instagram_related_profilesTabellen für existierende Beziehungen--sync: Führt die Job-handle()-Methode direkt aus (nützlich für einzelne Profile beim Debugging)- Fortschrittsbalken für beide Plattformen
Score-Neuberechnung (YouTube)
| Kriterium | Punkte | Beschreibung |
|---|---|---|
| Base Score (Existenz) | 40 | Channel existiert in Postbox |
| Subscriber Bonus | 0-25 | Tier-basiert (1K → 3, 10K → 6, ..., 10M+ → 25) |
| Title Similarity | 0-5 | similar_text() > 30% |
| Topic Match | 10 | Wenn search_query vorhanden |
| Position Estimate | 10 | Durchschnittliche Position |
Score-Neuberechnung (Instagram)
| Kriterium | Punkte | Beschreibung |
|---|---|---|
| Base Score (Existenz) | 25 | Bestehende Beziehung |
| Popularity Bonus | 0-25 | Tier-basiert (1K → 3, ..., 1M+ → 25) |
| Follower Similarity | 0-15 | Log-Scale (gleiche Größenordnung = 15) |
| Name Similarity | 0-15 | similar_text() > 30% |
| Same Profile Type | 0-10 | Business/Creator/Private Match |
| Language/Country | 0-10 | +5 pro Match |
Schedule
Manuell (nach größeren Datenänderungen oder Follower-Backfills).
Location: app/Console/Commands/RecalculateRelatedScores.php
queue:prune-stale
Entfernt veraltete Queue-Jobs aus Collector-Queue und/oder Laravel Jobs-Tabelle. Schützt First-Time-Imports vor versehentlichem Löschen.
queue:prune-stale
{--before= : Zeitpunkt (Datum + Uhrzeit)}
{--scope= : collector, laravel, both}
{--source= : Collector-Source Filter}
{--queue= : Laravel Queue Filter}
{--delete-duplicates : Duplicate queued Jobs entfernen}
{--duplicates-only : Nur Duplikate entfernen, kein Time-Pruning}
{--dry-run : Vorschau}
Beispiele
# Collector-Jobs vor bestimmtem Zeitpunkt löschen
php artisan queue:prune-stale --before="2026-01-24 12:00:00" --scope=collector --dry-run
# Beide Scopes, mit Duplikat-Bereinigung
php artisan queue:prune-stale --before="2026-01-24 12:00:00" --scope=both --delete-duplicates
# Nur Duplikate entfernen
php artisan queue:prune-stale --scope=both --duplicates-only
Schedule
Manuell.
Location: app/Console/Commands/PruneStaleQueueJobs.php
notifications:cleanup
Löscht abgelaufene und alte Notifications und Announcements.
notifications:cleanup
{--dry-run : Vorschau ohne Löschen}
Beispiele
# Standard Cleanup
php artisan notifications:cleanup
# Vorschau
php artisan notifications:cleanup --dry-run
Cleanup-Regeln
| Kategorie | Bedingung |
|---|---|
| Expired Notifications | expires_at < now() |
| Old Notifications | > 90 Tage ohne Expiry |
| Expired Announcements | expires_at < now() |
| Old Announcements | > 90 Tage ohne Expiry |
| Old Announcement Reads | > 30 Tage |
Schedule
Täglich um 03:00 UTC.
Location: app/Console/Commands/CleanupNotifications.php
vantage:prune
Memory-effizientes Pruning der vantage_jobs Tabelle. Ersetzt den Vendor-Command (houdaslassi/vantage), der bei grossen Tabellen (45+ GB) durch Eloquent Model-Hydration mit serialisierten Payloads OOM verursacht. Nutzt batched raw SQL DELETE ohne Model-Loading.
vantage:prune
{--days= : Jobs der letzten X Tage behalten (Default: Config oder 30)}
{--hours= : Jobs der letzten X Stunden behalten (ueberschreibt --days)}
{--status= : Nur Jobs mit bestimmtem Status prunen (completed, failed, processing)}
{--keep-processing : Jobs mit Status "processing" immer behalten}
{--dry-run : Vorschau ohne Loeschung}
{--force : Bestaetigungsprompt ueberspringen}
Beispiele
# Completed Jobs aelter als 24h loeschen
php artisan vantage:prune --status=completed --hours=24
# Failed Jobs aelter als 4 Tage loeschen
php artisan vantage:prune --status=failed --days=4
# Vorschau: was wuerde geloescht?
php artisan vantage:prune --status=completed --hours=24 --dry-run
Optionen
| Option | Typ | Default | Beschreibung |
|---|---|---|---|
--days= | int | Config / 30 | Aufbewahrungsfrist in Tagen |
--hours= | int | – | Aufbewahrungsfrist in Stunden (ueberschreibt --days) |
--status= | string | – | completed (alias: processed), failed oder processing |
--keep-processing | flag | – | Processing-Jobs immer ausnehmen |
--dry-run | flag | – | Nur Vorschau mit Status-Breakdown |
--force | flag | – | Keine interaktive Bestaetigung |
Interne Schritte (3 Phasen)
- Phase 1: Tags loeschen —
vantage_job_tagsfuer betroffene Jobs in Batches loeschen (Foreign Key Constraint) - Phase 2: Retry-Chains aufraumen —
retried_from_idnullifizieren fuer Child-Jobs deren Parent geloescht wird - Phase 3: Jobs loeschen —
vantage_jobsRows in 10.000er-Batches loeschen
Cursor-basierte Pagination: Phase 1 und 2 nutzen WHERE id > $afterId ORDER BY id statt einfaches LIMIT, da die Jobs in diesen Phasen noch nicht geloescht werden. Ohne Cursor wuerde getJobIdBatch() immer dieselben IDs zurueckliefern (Endlosschleife).
Schedule
Zweimal taeglich:
- 01:25 UTC:
--status=completed --hours=24 --force(Completed > 24h) - 01:30 UTC:
--status=failed --days=4 --force(Failed > 4 Tage)
Location: app/Console/Commands/VantagePruneCommand.php
db:prune-historical-data
Löscht alte Aggregate-Daten aus Dashboard-Rollups und Leaderboard-Snapshots.
WICHTIG: social_profile_daily_metrics wird NIEMALS gelöscht — nur abgeleitete Aggregate.
db:prune-historical-data
{--rollups-days=400 : Aufbewahrung für dashboard_daily_rollups (Tage)}
{--snapshots-days=90 : Aufbewahrung für dashboard_leaderboard_snapshots (Tage)}
{--dry-run : Vorschau ohne Löschung}
Optionen
| Option | Typ | Default | Min | Beschreibung |
|---|---|---|---|---|
--rollups-days= | int | 400 | 90 | Aufbewahrungsfrist für dashboard_daily_rollups |
--snapshots-days= | int | 90 | 30 | Aufbewahrungsfrist für dashboard_leaderboard_snapshots |
--dry-run | flag | – | – | Nur Vorschau, keine Löschung |
Beispiele
# Standard: Rollups > 400 Tage, Snapshots > 90 Tage löschen
php artisan db:prune-historical-data
# Kürzere Aufbewahrung (z.B. bei Speicherproblemen)
php artisan db:prune-historical-data --rollups-days=180 --snapshots-days=60
# Vorschau: wie viele Rows würden gelöscht?
php artisan db:prune-historical-data --dry-run
Interne Logik
- Minimum Retention erzwungen: Rollups mindestens 90 Tage, Snapshots mindestens 30 Tage — verhindert versehentlichen Datenverlust
- Löschung in Chunks (10.000 Rows pro Query) für kurze Transaktionen
dashboard_daily_rollups: Löscht nachdate-Spaltedashboard_leaderboard_snapshots: Löscht nachgenerated_for_date-Spalte
Geschützte Tabellen
| Tabelle | Status | Grund |
|---|---|---|
social_profile_daily_metrics | NIEMALS löschen | Kern-Verlaufsdaten aller Profile |
dashboard_daily_rollups | Wird geprunt | Abgeleitete Aggregate, jederzeit neu berechenbar |
dashboard_leaderboard_snapshots | Wird geprunt | Abgeleitete Snapshots |
Schedule
Täglich um 01:00 UTC.
Location: app/Console/Commands/PruneHistoricalData.php
favorites:cleanup
Entfernt verwaiste Watchers auf Favoriten-Workspaces die kein zugehöriges Favorite-Entry mehr haben. Bidirektionaler Cleanup: Watcher ohne Favorite UND Favorites ohne Watcher.
favorites:cleanup
{--dry-run : Vorschau ohne Löschung}
{--user= : Nur für einen bestimmten User aufräumen}
Optionen
| Option | Typ | Default | Beschreibung |
|---|---|---|---|
--dry-run | flag | – | Nur Vorschau, keine Löschung |
--user= | int | – | Auf bestimmten User einschränken |
Beispiele
# Alle User: verwaiste Watchers/Favorites finden und entfernen
php artisan favorites:cleanup
# Vorschau: was würde aufgeräumt?
php artisan favorites:cleanup --dry-run
# Nur für einen bestimmten User
php artisan favorites:cleanup --user=42
Interne Logik
- Alle "Favoriten"-Workspaces identifizieren (
FavoriteManager::FAVORITES_WORKSPACE_NAME) - Orphan Watchers: Watcher auf Favoriten-Workspace deren Source-Profil nicht in
favorite_social_profilesdes Users existiert - Orphan Favorites:
FavoriteSocialProfile-Einträge ohne korrespondierenden Watcher auf dem Favoriten-Workspace - Transaktionale Löschung (Watcher + Sources zusammen)
Schedule
Manuell (nach Favoriten-Refactoring oder Dateninkonsistenzen).
Location: app/Console/Commands/CleanupOrphanFavoriteWatchers.php
profiles:backfill-descriptions
Parst Profilbeschreibungen die vor dem parsed_links-Feature importiert wurden.
Extrahiert URLs, E-Mail-Adressen und Social-Media-Handles aus dem description-Feld.
profiles:backfill-descriptions
{--platform= : Nur bestimmte Plattform (youtube, instagram)}
{--force : Auch Profile mit vorhandenen parsed_links re-parsen}
{--dry-run : Nur Zählung ohne Parsing}
Optionen
| Option | Typ | Default | Beschreibung |
|---|---|---|---|
--platform= | string | alle | youtube oder instagram |
--force | flag | – | Auch Profile mit existierenden parsed_links erneut parsen |
--dry-run | flag | – | Nur Anzahl betroffener Profile anzeigen |
Beispiele
# Alle Profile ohne parsed_links nachparsen
php artisan profiles:backfill-descriptions
# Nur Instagram-Profile
php artisan profiles:backfill-descriptions --platform=instagram
# Vorschau
php artisan profiles:backfill-descriptions --dry-run
Interne Logik
- Profile mit
description IS NOT NULLundparsed_links IS NULLfinden - Chunks von 500 Profilen verarbeiten
ProfileDescriptionParserService extrahiert URLs, Emails, @handles- Ergebnis in
parsed_linksJSONB-Spalte speichern
Schedule
Manuell (einmaliger Backfill).
Location: app/Console/Commands/BackfillProfileDescriptions.php
profiles:backfill-followers-count
Füllt die followers_count Spalte auf social_profiles aus der letzten täglichen Metrik.
Einmaliger Backfill für Profile die vor Einführung der denormalisierten Spalte importiert wurden.
profiles:backfill-followers-count
{--dry-run : Nur Zählung ohne Update}
Beispiele
# Fehlende followers_count auffüllen
php artisan profiles:backfill-followers-count
# Vorschau: wie viele Profile betroffen?
php artisan profiles:backfill-followers-count --dry-run
Interne Logik
- Single Bulk-Query: PostgreSQL
UPDATE...FROMmitDISTINCT ONSubquery - Füllt
followers_countaus der neuestensocial_profile_daily_metricsRow - Kein N+1 — ein einziger SQL-Statement für alle Profile
Hinweise
- PostgreSQL-spezifisch: Nutzt
DISTINCT ON+UPDATE...FROM(kein SQLite-Support) - Nur Profile mit
followers_count IS NULLwerden aktualisiert
Schedule
Manuell (einmaliger Backfill).
Location: app/Console/Commands/BackfillFollowersCount.php
profiles:backfill-instagram-descriptions
Kopiert die Instagram-Bio aus dem JSON data-Feld in die description-Spalte.
Benötigt für den AI Enhancer, der Beschreibungen aus description liest.
profiles:backfill-instagram-descriptions
{--dry-run : Nur Zählung ohne Update}
Beispiele
# Bios kopieren
php artisan profiles:backfill-instagram-descriptions
# Vorschau
php artisan profiles:backfill-instagram-descriptions --dry-run
Interne Logik
- Instagram-Profile mit
description IS NULLunddata IS NOT NULLfinden - Bio aus
data->biooderdata->biography(Fallback) extrahieren - "mehr"-Normalisierung anwenden (analog zu
sanitizeProfileData) - Chunks von 500 Profilen verarbeiten
Schedule
Manuell (einmaliger Backfill).
Location: app/Console/Commands/BackfillInstagramDescriptions.php
profiles:sync-parsed-links
Migriert parsed_links JSONB-Daten in die relationale social_profile_links Tabelle.
Einmaliger Backfill für die Link-Normalisierung.
profiles:sync-parsed-links
{--platform= : Nur bestimmte Plattform (youtube, instagram)}
{--dry-run : Nur Zählung ohne Sync}
Optionen
| Option | Typ | Default | Beschreibung |
|---|---|---|---|
--platform= | string | alle | youtube oder instagram |
--dry-run | flag | – | Nur Anzahl betroffener Profile anzeigen |
Beispiele
# Alle Profile synchronisieren
php artisan profiles:sync-parsed-links
# Nur YouTube-Profile
php artisan profiles:sync-parsed-links --platform=youtube
# Vorschau
php artisan profiles:sync-parsed-links --dry-run
Interne Logik
- Profile mit
parsed_links IS NOT NULLfinden die noch keinesocial_profile_linksEinträge haben SocialProfileLinkSyncerService erstellt individuelle Link-Rows aus dem JSON- Chunks von 500 Profilen verarbeiten
Schedule
Manuell (einmaliger Backfill).
Location: app/Console/Commands/SyncParsedLinks.php
images:generate-variants
Backfill-Command: Generiert optimierte Bild-Varianten (WebP/AVIF Thumbnail + Medium) für bestehende Profilbilder und Video-Thumbnails. Nutzt ProfileImageProcessor für die Verarbeitung.
images:generate-variants
{--dry-run : Nur zählen, nichts verarbeiten}
{--chunk=500 : Chunk-Größe für Batch-Verarbeitung}
{--profile-id= : Nur ein bestimmtes Profil verarbeiten}
{--videos : Video-Thumbnails statt Profilbilder verarbeiten}
{--force : Auch Bilder mit bestehenden Varianten neu generieren}
{--from= : Nur Bilder ab diesem Datum (YYYY-MM-DD)}
{--to= : Nur Bilder bis zu diesem Datum (YYYY-MM-DD)}
Beispiele
# Dry-Run: Wie viele Profilbilder fehlen Varianten?
php artisan images:generate-variants --dry-run
# Alle fehlenden Varianten generieren
php artisan images:generate-variants
# Einzelnes Profil
php artisan images:generate-variants --profile-id=42
# Video-Thumbnails verarbeiten
php artisan images:generate-variants --videos
# Alle Varianten erzwingen (auch bestehende)
php artisan images:generate-variants --force
# Nur Bilder aus einem Zeitraum
php artisan images:generate-variants --from=2026-01-01 --to=2026-02-01
Schedule
Manuell (einmaliger Backfill oder bei Format-Wechsel).
Location: app/Console/Commands/GenerateImageVariants.php
images:cleanup-orphaned
Findet und entfernt verwaiste Varianten-Dateien auf Disk (_md.webp, _sm.webp, _md.avif, _sm.avif), die keinen zugehörigen DB-Eintrag haben.
images:cleanup-orphaned
{--dry-run : Nur auflisten, nichts löschen}
{--videos : Video-Thumbnails statt Profilbilder prüfen}
Beispiele
# Dry-Run: Verwaiste Dateien auflisten
php artisan images:cleanup-orphaned --dry-run
# Verwaiste Varianten löschen
php artisan images:cleanup-orphaned
# Video-Thumbnails prüfen
php artisan images:cleanup-orphaned --videos
Schedule
Manuell.
Location: app/Console/Commands/CleanupOrphanedImageVariants.php
social:discover-from-links
Backfill-Command zur automatischen Profil-Discovery aus Contact-Link-Handles. Dispatcht EnsureProfileFromContactLink Jobs für Social-Links, die noch nicht entdeckt wurden.
social:discover-from-links
{--platform= : Filter by platform (youtube, instagram)}
{--only-existing : Only discover links where a matching profile already exists}
{--limit=0 : Max number of links to process (0 = unlimited)}
{--batch-size=100 : Chunk size for processing}
{--priority : Process high-follower source profiles first (E7)}
{--retry-failed : Retry previously failed discoveries}
{--dry-run : Show what would be processed without dispatching}
Beispiele
# Alle unentdeckten Links verarbeiten
php artisan social:discover-from-links
# Nur Instagram-Links
php artisan social:discover-from-links --platform=instagram
# Nur existierende Profile verknüpfen (0 API-Quota)
php artisan social:discover-from-links --only-existing
# Max 100 Links, High-Follower zuerst
php artisan social:discover-from-links --limit=100 --priority
# Fehlgeschlagene Discoveries erneut versuchen
php artisan social:discover-from-links --retry-failed --limit=200
# Vorschau ohne Dispatch
php artisan social:discover-from-links --dry-run
| Option | Typ | Default | Beschreibung |
|---|---|---|---|
--platform | string | alle | Filter auf youtube oder instagram |
--only-existing | flag | false | Nur Links verarbeiten, deren Profil schon existiert |
--limit | int | 0 | Max. Links (0 = unbegrenzt) |
--batch-size | int | 100 | Chunk-Größe für chunkById() |
--priority | flag | false | High-Follower-Profile zuerst (E7) |
--retry-failed | flag | false | Nur discovery_status=failed erneut versuchen |
--dry-run | flag | false | Vorschau-Tabelle ohne Dispatch |
Interne Logik:
- Query auf
social_profile_linksmittype=social, importable Plattform, keinlinked_social_profile_id - Filtert nach
discovery_status IS NULL(oderfailedbei--retry-failed) - Bei
--priority: JOIN aufsocial_profilesfür Follower-Sortierung - Dispatcht
EnsureProfileFromContactLinkaufprofile-discoveryQueue
Schedule: Wöchentlich Sonntag 09:00 UTC (--retry-failed --limit=200)
Location: app/Console/Commands/DiscoverProfilesFromLinks.php
Tinker: Related Slider für alle PRO-Accounts neu generieren
Stößt die Generierung aller drei Related-Slider (Instagram Related Profiles, YouTube Related Channels, Cross-Platform Related) für alle PRO-Accounts und Accounts mit erweiterten Video-Statistiken an. Jobs werden auf Standard-Queues dispatched und nutzen ShouldBeUnique (1h Fenster) zur Deduplizierung.
php8.4 artisan tinker --execute="\$ids = \App\Models\SocialProfile::query()->where(function(\$q) { \$q->where('platform', 'youtube')->whereHas('youtubeVideoSync', fn(\$s) => \$s->whereNotNull('video_tracking_start_date')->orWhere('auto_sync_enabled', true)); })->orWhere(function(\$q) { \$q->where('platform', 'instagram')->where('pro_enabled', true); })->pluck('id'); \$c = 0; foreach (\$ids as \$id) { \App\Jobs\GenerateRelatedProfilesSlider::dispatch(\$id); \App\Jobs\GenerateRelatedChannelsSlider::dispatch(\$id); \App\Jobs\GenerateCrossPlatformRelatedSlider::dispatch(\$id); \$c++; } echo \"Dispatched 3 slider jobs each for {\$c} profiles (\" . \$ids->count() . \" total).\\n\";"
Hinweise:
- Kann jederzeit ausgeführt werden — bestehende Slider-Daten werden überschrieben
- Jobs sind
ShouldBeUniquemit 1h Lock, doppelte Ausführung ist sicher - PRO-Kriterien: YouTube via
youtube_video_syncs.video_tracking_start_date IS NOT NULLoderauto_sync_enabled = true, Instagram viasocial_profiles.pro_enabled = true