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
| Method | Parameter | Return | Beschreibung |
|---|---|---|---|
getOrCreateFavoritesWorkspace() | User $user | Workspace | Favoriten-Workspace holen oder erstellen |
isFavorite() | User $user, SocialProfile $profile | bool | Prüfen ob Profil als Favorit markiert ist |
favorite() | User $user, SocialProfile $profile | void | Profil als Favorit markieren |
unfavorite() | User $user, SocialProfile $profile | void | Favorit-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
WorkspaceModel,WatcherModel,WatcherSourceModel,FavoriteWatcherSourceModel
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
| Method | Parameter | Return | Beschreibung |
|---|---|---|---|
moveWatchers() | array $watcherIds, Workspace $targetWorkspace, User $user | array | Watcher 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:
WatcherSourcedes Quell-Watchers wird zum bestehenden Ziel-Watcher verschoben- Quell-Watcher wird gelöscht (keine verwaisten Einträge)
- Custom-Name des Quell-Watchers wird beibehalten falls
is_name_custom = true
Abhängigkeiten
WatcherModel,WatcherSourceModel,WorkspaceModel
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
| Method | Parameter | Return | Beschreibung |
|---|---|---|---|
isAdminWorkspaceId() | ?int $workspaceId | bool | Prüfen ob ID dem Admin-Workspace gehört |
isAdminWorkspace() | ?Workspace $workspace | bool | Prüfen ob Workspace der Admin-Workspace ist |
ensureAdminWorkspace() | -- | ?Workspace | Admin-Workspace erstellen falls nicht vorhanden |
appendAdminWorkspace() | Collection $workspaces, User $user | Collection | Admin-Workspace zur Workspace-Liste hinzufügen (nur für Admins) |
ensureProfileWatcher() | SocialProfile $profile | bool | Watcher für Profil im Admin-Workspace sicherstellen |
removeProfileFromAdminWorkspace() | SocialProfile $profile | void | Profil 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
WorkspaceModel,WatcherModel,WatcherSourceModel,UserModel
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
| Key | Default | Beschreibung |
|---|---|---|
postbox.onboarding_starter_by_followers | 10 | Anzahl Top-Profile nach Followern pro Plattform |
postbox.onboarding_starter_by_score | 15 | Anzahl Top-Profile nach Postbox Score pro Plattform |
postbox.onboarding_max_per_category | 3 | Max Profile pro Explore-Kategorie (Score-Phase) |
Public Methods
| Method | Parameter | Return | Beschreibung |
|---|---|---|---|
ensureGeneralWorkspace() | User $user | Workspace | General-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]
- Phase 1 — Top by Followers: Pro Plattform die Top N Profile nach letztem Follower-Count. Nur Profile mit
tracking_enabled = trueundnotExcluded()(nicht blocked/sanitized/archived). - Phase 2 — Top by Score: Pro Plattform die Top N Profile nach höchstem Postbox Score (
stableoderpreliminary). Profile aus Phase 1 werden ausgeschlossen. Graceful Fallback bei leerer Score-Tabelle. - 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.
- Watcher-Erstellung: Für jedes Profil einen Watcher + WatcherSource erstellen (Duplikat-Prüfung auf Workspace-Ebene).
- 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,ExploreProfileMetricDashboardSnapshotRefresher
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