Zum Hauptinhalt springen

Favorites & Workspaces

Die Services in diesem Bereich verwalten Workspace-Operationen, Favoriten-Logik und die Admin-Workspace-Spiegelung. Sie bilden die organisatorische Schicht über den Watcher/Profile-Daten.

FavoriteManager

Location: app/Services/Favorites/FavoriteManager.php

Verwaltet den Favoriten-Workspace eines Users. Jeder User hat maximal einen Favoriten-Workspace, der automatisch erstellt wird. Profile können per Toggle als Favorit markiert werden, was eine FavoriteWatcherSource in diesem speziellen Workspace erstellt.

Public Methods

MethodParameterReturnBeschreibung
getOrCreateFavoritesWorkspace()User $userWorkspaceFavoriten-Workspace holen oder erstellen
isFavorite()User $user, SocialProfile $profileboolPrüfen ob Profil als Favorit markiert ist
favorite()User $user, SocialProfile $profilevoidProfil als Favorit markieren
unfavorite()User $user, SocialProfile $profilevoidFavorit-Markierung entfernen

Favoriten-Workspace

Der Favoriten-Workspace ist ein spezieller Workspace mit dem Flag is_favorites = true. Pro User existiert genau einer. Wird automatisch bei der ersten Favoriten-Aktion erstellt:

$workspace = Workspace::firstOrCreate(
['owner_id' => $user->id, 'is_favorites' => true],
['name' => 'Favoriten']
);

Favorite/Unfavorite-Logik

Bei favorite() wird ein Watcher mit zugehöriger WatcherSource im Favoriten-Workspace erstellt. Das Profil wird dadurch im Favorites-Kontext sichtbar. unfavorite() entfernt den Watcher und die Source wieder.

Die favorites_count in social_profile_explore_metrics wird durch den ExploreMetricsCalculator aggregiert und für die Relevanz-Bewertung im Explore und Cross-Platform-Matching verwendet.

Abhängigkeiten

  • Workspace Model, Watcher Model, WatcherSource Model, FavoriteWatcherSource Model

Verwendet von

  • Explore Profile Cards (Favorit-Toggle), Profil-Detailansicht, Dashboard

WatcherMover

Location: app/Services/Watchers/WatcherMover.php

Verschiebt Watcher zwischen Workspaces. Handhabt dabei die Deduplizierung: Wenn ein Watcher in den Ziel-Workspace verschoben wird und dort bereits ein Watcher mit demselben Profil existiert, werden die Einträge gemergt.

Public Methods

MethodParameterReturnBeschreibung
moveWatchers()array $watcherIds, Workspace $targetWorkspace, User $userarrayWatcher verschieben, Duplikate mergen

Return-Struktur

[
'moved' => 5, // Erfolgreich verschoben
'merged' => 2, // Mit bestehendem Watcher gemergt
'skipped' => 0, // Übersprungen (bereits im Ziel-Workspace)
]

Merge-Logik

Wenn im Ziel-Workspace bereits ein Watcher mit demselben social_profile_id existiert:

  1. WatcherSource des Quell-Watchers wird zum bestehenden Ziel-Watcher verschoben
  2. Quell-Watcher wird gelöscht (keine verwaisten Einträge)
  3. Custom-Name des Quell-Watchers wird beibehalten falls is_name_custom = true

Abhängigkeiten

  • Watcher Model, WatcherSource Model, Workspace Model

Verwendet von

  • Watchers Index (Bulk-Move-Aktion), Workspace-Management

AdminWorkspaceManager

Location: app/Services/Workspaces/AdminWorkspaceManager.php

Verwaltet den Admin-Workspace (ID: 999999999999), der alle Social Profiles des Systems spiegelt. Jedes Profil erhält automatisch einen Watcher in diesem speziellen Workspace, sodass Admins eine Gesamtübersicht haben.

Konstanten

const ADMIN_WORKSPACE_ID = 999999999999;
const ADMIN_WORKSPACE_NAME = 'Admin-View';
const ADMIN_OWNER_ID = 1;

Public Methods

MethodParameterReturnBeschreibung
isAdminWorkspaceId()?int $workspaceIdboolPrüfen ob ID dem Admin-Workspace gehört
isAdminWorkspace()?Workspace $workspaceboolPrüfen ob Workspace der Admin-Workspace ist
ensureAdminWorkspace()--?WorkspaceAdmin-Workspace erstellen falls nicht vorhanden
appendAdminWorkspace()Collection $workspaces, User $userCollectionAdmin-Workspace zur Workspace-Liste hinzufügen (nur für Admins)
ensureProfileWatcher()SocialProfile $profileboolWatcher für Profil im Admin-Workspace sicherstellen
removeProfileFromAdminWorkspace()SocialProfile $profilevoidProfil aus Admin-Workspace entfernen

Spiegelungs-Logik

ensureProfileWatcher() prüft ob bereits ein Watcher mit der social_profile_id im Admin-Workspace existiert. Falls nicht, wird ein neuer Watcher + WatcherSource erstellt:

$watcher = Watcher::create([
'workspace_id' => self::ADMIN_WORKSPACE_ID,
'created_by' => self::ADMIN_OWNER_ID,
'name' => $profile->title ?: $profile->handle,
'is_name_custom' => false,
]);

Schreibschutz

Sowohl der Favoriten-Workspace als auch der Admin-View-Workspace sind geschuetzte System-Workspaces. Auf der Workspace-Verwaltungsseite (/workspaces) sind "Bearbeiten"- und "Loeschen"-Buttons ausgeblendet. Server-seitige Guards in Workspaces\Index::startEdit(), saveEdit(), confirmDelete() und delete() verhindern Manipulation auch bei direktem Aufruf. Beide System-Workspaces werden auf der Verwaltungsseite nach unten sortiert.

Backfill-Strategie

Der BackfillAdminWorkspaceWatchers-Command läuft stündlich und spiegelt alle Profile, die noch keinen Watcher im Admin-Workspace haben. Bei Prune-Aktionen wird der Admin-Workspace zuerst bereinigt.

Abhängigkeiten

  • Workspace Model, Watcher Model, WatcherSource Model, User Model

Verwendet von

  • BackfillAdminWorkspaceWatchers-Command (stündlich), InstagramWatcherImportProcessor, EnsureSocialProfileFromUrl, Prune-Commands

DefaultWorkspaceProvisioner

Location: app/Services/Workspaces/DefaultWorkspaceProvisioner.php

Erstellt den Default-Workspace ("General") für neue User und befüllt ihn mit bis zu 50 Starter-Watchern. Zweistufige Selektion: Top-Profile nach Follower-Count + Top-Profile nach Postbox Score, mit Kategorie-Diversität.

Konstanten

const DEFAULT_WORKSPACE_NAME = 'General';

Config-Keys

KeyDefaultBeschreibung
postbox.onboarding_starter_by_followers10Anzahl Top-Profile nach Followern pro Plattform
postbox.onboarding_starter_by_score15Anzahl Top-Profile nach Postbox Score pro Plattform
postbox.onboarding_max_per_category3Max Profile pro Explore-Kategorie (Score-Phase)

Public Methods

MethodParameterReturnBeschreibung
ensureGeneralWorkspace()User $userWorkspaceGeneral-Workspace erstellen und mit Startern befüllen

Starter-Watcher-Logik (3 Phasen)

graph TD
A[User registriert sich] --> B[ensureGeneralWorkspace]
B --> C[General Workspace erstellen]
C --> D{Plattformen vorhanden?}
D -- Nein --> E[Fertig - leerer Workspace]
D -- Ja --> F[Pro Plattform durchlaufen]
F --> G[Phase 1: Top N nach Followern]
G --> H[Phase 2: Top N nach Postbox Score]
H --> I[Phase 3: Kategorie-Diversität filtern]
I --> J[Watcher + WatcherSource erstellen]
J --> K[DashboardSnapshotRefresher triggern]
  1. Phase 1 — Top by Followers: Pro Plattform die Top N Profile nach letztem Follower-Count. Nur Profile mit tracking_enabled = true und notExcluded() (nicht blocked/sanitized/archived).
  2. Phase 2 — Top by Score: Pro Plattform die Top N Profile nach höchstem Postbox Score (stable oder preliminary). Profile aus Phase 1 werden ausgeschlossen. Graceful Fallback bei leerer Score-Tabelle.
  3. Phase 3 — Kategorie-Diversität: Score-basierte Profile werden auf max N pro Explore-Kategorie begrenzt (z.B. max 3 Gaming-Profile). Profile ohne Kategorie werden nicht gekappt.
  4. Watcher-Erstellung: Für jedes Profil einen Watcher + WatcherSource erstellen (Duplikat-Prüfung auf Workspace-Ebene).
  5. Dashboard-Refresh: DashboardSnapshotRefresher::queueIfMissingSnapshots() nach Commit triggern.

Ausschluss-Filter (alle Queries)

->where('tracking_enabled', true)
->notExcluded() // whereNull blocked_at + sanitized_at + archived_at

Abhängigkeiten

  • Workspace, Watcher, WatcherSource, SocialProfile, SocialProfileDailyMetric, SocialProfileScore, ExploreProfileMetric
  • DashboardSnapshotRefresher

Verwendet von

  • UserObserver::created() — automatisch bei User-Registration

Tests

16 Tests in tests/Feature/Services/DefaultWorkspaceProvisionerTest.php:

  • Workspace-Erstellung, Idempotenz, Follower/Score-Selektion, alle 4 Ausschluss-Filter, Duplikat-Vermeidung, Kategorie-Diversität, leere DB, Multi-Plattform