Zum Hauptinhalt springen

Favoriten

Das Favoriten-System erlaubt es Usern, Profile plattformuebergreifend als Favorit zu markieren. Favoriten werden global gespeichert und zusaetzlich in einen dedizierten "Favoriten"-Workspace gespiegelt.

Datenmodell

ModelTabelleBeschreibung
FavoriteSocialProfilefavorite_social_profilesGlobaler Favorit-Marker (user_id + social_profile_id)
Workspace (Name: "Favoriten")workspacesDedizierter Workspace pro User

Location: app/Models/FavoriteSocialProfile.php, app/Services/Favorites/FavoriteManager.php

FavoriteManager

Der FavoriteManager ist der zentrale Service fuer alle Favoriten-Operationen:

favorite(User $user, SocialProfile $profile, ?Watcher $sourceWatcher = null)

  1. Erstellt einen FavoriteSocialProfile-Eintrag (idempotent via firstOrCreate)
  2. Erstellt oder findet den "Favoriten"-Workspace (getOrCreateFavoritesWorkspace())
  3. Prueft, ob das Profil bereits im Favoriten-Workspace existiert
  4. Erstellt Watcher + WatcherSource im Favoriten-Workspace
$watcher = Watcher::create([
'workspace_id' => $favoritesWorkspace->id,
'created_by' => $user->id,
'name' => $sourceWatcher?->name ?? $profile->title ?? $profile->handle,
'is_name_custom' => false,
]);

unfavorite(User $user, SocialProfile $profile)

  1. Loescht den FavoriteSocialProfile-Eintrag
  2. Findet den Favoriten-Workspace
  3. Loescht zugehoerige WatcherSource und Watcher aus dem Favoriten-Workspace

Beide Methoden laufen in einer DB-Transaction.

getOrCreateFavoritesWorkspace(User $user)

Erstellt oder findet den dedizierten Workspace:

public const FAVORITES_WORKSPACE_NAME = 'Favoriten';

return Workspace::query()->firstOrCreate(
['owner_id' => $user->id, 'name' => self::FAVORITES_WORKSPACE_NAME],
['description' => null]
);

isFavorite(User $user, SocialProfile $profile)

Prueft, ob ein Profil vom User favorisiert ist (einfacher exists()-Check).

isFavoritesWorkspace(Workspace $workspace)

Identifiziert den Favoriten-Workspace anhand des kanonischen Namens.

Location: app/Services/Favorites/FavoriteManager.php

Watcher + WatcherSource Spiegelung

Der Favoriten-Workspace verhaelt sich wie ein normaler Workspace. Jeder Favorit erzeugt einen vollstaendigen Watcher mit WatcherSource. Das bedeutet:

  • Favoriten erscheinen in der normalen Watcher-Liste
  • Dashboard-Metriken gelten auch fuer den Favoriten-Workspace
  • Alle Features (Charts, Related, Score) funktionieren identisch

Delete-Verhalten

Watcher im Favoriten-Workspace loeschen

Wenn ein Watcher innerhalb des Favoriten-Workspaces geloescht wird, wird automatisch auch der Favorit-Marker entfernt:

if ($user && $favorites->isFavoritesWorkspace($workspace)) {
foreach ($sources as $source) {
if ($source->profile) {
$favorites->unfavorite($user, $source->profile);
}
}
}

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

Unfavorite ueber Star-Toggle

Beim Entfernen des Favorits ueber den Star-Button wird der Watcher im Favoriten-Workspace automatisch mit entfernt (ueber FavoriteManager::unfavorite()).

UI: Star-Icons

Star-Icons erscheinen an drei Stellen:

Watcher-Liste (Watchers\Index)

Kleine Stern-Icons neben Watchern, deren primaeres Profil favorisiert ist. Die Favoriten-IDs werden in einer einzelnen Query geladen:

$favoriteProfileIds = FavoriteSocialProfile::query()
->where('user_id', $user->id)
->pluck('social_profile_id')
->all();

Location: resources/views/livewire/watchers/index.blade.php

Dashboard (Dashboard\Index)

Favoriten-Rows enthalten das is_favorite-Flag fuer Star-Rendering. Zusaetzlich gibt es einen Favoriten-Filter (all / favorites), der zur Laufzeit auf die Snapshot-Daten angewendet wird.

Location: app/Livewire/Dashboard/Index.php, resources/views/livewire/dashboard/index.blade.php

Watcher-Detail (Watchers\Show)

Star-Toggle neben dem Profil-Handle. Toggle-Aktion ueber toggleFavorite():

public function toggleFavorite(int $sourceId, FavoriteManager $favorites): void
{
// Reload source to avoid stale relations
$source = WatcherSource::query()
->whereKey($sourceId)
->where('watcher_id', $this->watcher->id)
->with('profile')
->first();

if ($favorites->isFavorite($user, $profile)) {
$favorites->unfavorite($user, $profile);
} else {
$favorites->favorite($user, $profile, $this->watcher);
}

// Reload favorites for immediate UI feedback
$this->favoriteProfileIds = $this->loadFavoriteProfileIds();
}

Location: app/Livewire/Watchers/Show.php

Workspace-Schutz

Der Favoriten-Workspace hat besondere Einschraenkungen:

  • Kein Import: Multi-Import in den Favoriten-Workspace ist gesperrt
  • Kein Move-Ziel: Watcher koennen nicht in den Favoriten-Workspace verschoben werden
  • Nur Favoriten: Neue Eintraege entstehen ausschliesslich ueber FavoriteManager::favorite()
if ($workspace->name === FavoriteManager::FAVORITES_WORKSPACE_NAME) {
$this->addError($errorKey, 'Import in den Favoriten-Workspace ist nicht moeglich.');
return;
}

Daily Scrape Priority

Favorisierte Profile koennen bei der taeglichen Datenaktualisierung priorisiert werden, damit ihre Metriken stets aktuell sind.

Tests

Die Favoriten-Logik wird durch dedizierte Tests abgedeckt:

php artisan test --filter=WatcherFavoritesTest

Getestete Szenarien:

  • Favorisieren und Unfavorisieren
  • Automatisches Entfernen beim Loeschen aus dem Favoriten-Workspace
  • Toggle-Cleanup bei Watcher-Delete
  • is_favorite-Flag im Dashboard

Location: tests/Feature/Watchers/WatcherFavoritesTest.php, tests/Feature/DashboardRollupTest.php