morphMany(Reaction::class, 'reactable'); } /** * Get reaction counts grouped by type */ public function getReactionCountsAttribute(): array { return $this->reactions ->groupBy('type') ->map(function ($reactions) { return $reactions->count(); }) ->toArray(); } /** * Get total reaction count */ public function getTotalReactionsAttribute(): int { return $this->reactions->count(); } /** * Check if user has reacted to this model */ public function hasReactionFromUser(?User $user = null, ?string $type = null): bool { if (!$user) { return false; } $query = $this->reactions()->where('user_id', $user->id); if ($type) { $query->where('type', $type); } return $query->exists(); } /** * Get user's reaction to this model */ public function getUserReaction(?User $user = null): ?Reaction { if (!$user) { return null; } return $this->reactions()->where('user_id', $user->id)->first(); } /** * Add or update a reaction from a user */ public function addReaction(User $user, string $type): Reaction { return $this->reactions()->updateOrCreate( ['user_id' => $user->id], ['type' => $type] ); } /** * Remove a reaction from a user */ public function removeReaction(User $user, ?string $type = null): bool { $query = $this->reactions()->where('user_id', $user->id); if ($type) { $query->where('type', $type); } return $query->delete() > 0; } /** * Toggle a reaction from a user * Returns: 'added', 'updated', or 'removed' */ public function toggleReaction(User $user, string $type): string { $existingReaction = $this->getUserReaction($user); if ($existingReaction) { if ($existingReaction->type === $type) { // Same reaction - remove it $existingReaction->delete(); return 'removed'; } else { // Different reaction - update it $existingReaction->update(['type' => $type]); return 'updated'; } } else { // No existing reaction - create new one $this->addReaction($user, $type); return 'added'; } } /** * Get reactions grouped by type with user information */ public function getReactionsWithUsersAttribute(): array { return $this->reactions() ->with('user:id,name') ->get() ->groupBy('type') ->map(function ($reactions) { return $reactions->map(function ($reaction) { return [ 'user_id' => $reaction->user->id, 'user_name' => $reaction->user->name, 'created_at' => $reaction->created_at->diffForHumans(), ]; }); }) ->toArray(); } /** * Get the most popular reaction type */ public function getMostPopularReactionAttribute(): ?string { $counts = $this->reaction_counts; if (empty($counts)) { return null; } return array_keys($counts, max($counts))[0]; } /** * Scope to order by reaction count */ public function scopeOrderByReactions($query, string $direction = 'desc') { return $query->withCount('reactions') ->orderBy('reactions_count', $direction); } /** * Scope to filter models with reactions */ public function scopeWithReactions($query) { return $query->has('reactions'); } /** * Scope to filter models by specific reaction type */ public function scopeWithReactionType($query, string $type) { return $query->whereHas('reactions', function ($q) use ($type) { $q->where('type', $type); }); } }