feat: notificaciones en tiempo real para social (likes, comentarios, solicitudes)
- Notifier helper: guarda en BD + broadcast en un solo llamado - AppNotification: notificación genérica para canal database - FriendshipController: notifica al recibir solicitud y al aceptarla - PostController: notifica al autor cuando le dan like o comentan (excepto en propio post) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,12 +4,11 @@ namespace App\Http\Controllers;
|
|||||||
|
|
||||||
use App\Models\Friendship;
|
use App\Models\Friendship;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Notifications\FriendRequestNotification;
|
use App\Support\Notifier;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class FriendshipController extends Controller
|
class FriendshipController extends Controller
|
||||||
{
|
{
|
||||||
// Solicitudes pendientes recibidas
|
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
$requests = auth()->user()
|
$requests = auth()->user()
|
||||||
@@ -22,7 +21,6 @@ class FriendshipController extends Controller
|
|||||||
return view('friendships.index', compact('requests'));
|
return view('friendships.index', compact('requests'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enviar solicitud
|
|
||||||
public function send(User $user)
|
public function send(User $user)
|
||||||
{
|
{
|
||||||
$me = auth()->user();
|
$me = auth()->user();
|
||||||
@@ -31,33 +29,45 @@ class FriendshipController extends Controller
|
|||||||
return back()->with('error', 'No puedes enviarte una solicitud a ti mismo.');
|
return back()->with('error', 'No puedes enviarte una solicitud a ti mismo.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$existing = $me->friendshipWith($user);
|
if ($me->friendshipWith($user)) {
|
||||||
if ($existing) {
|
|
||||||
return back()->with('error', 'Ya existe una relación con este usuario.');
|
return back()->with('error', 'Ya existe una relación con este usuario.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$friendship = Friendship::create([
|
Friendship::create([
|
||||||
'sender_id' => $me->id,
|
'sender_id' => $me->id,
|
||||||
'receiver_id' => $user->id,
|
'receiver_id' => $user->id,
|
||||||
'status' => 'pending',
|
'status' => 'pending',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$user->notify(new FriendRequestNotification($me));
|
Notifier::send(
|
||||||
|
recipient: $user,
|
||||||
|
title: $me->name . ' te envió una solicitud de amistad',
|
||||||
|
icon: 'fa-user-plus',
|
||||||
|
color: 'primary',
|
||||||
|
url: route('friendships.index'),
|
||||||
|
);
|
||||||
|
|
||||||
return back()->with('success', 'Solicitud de amistad enviada.');
|
return back()->with('success', 'Solicitud de amistad enviada.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aceptar solicitud
|
|
||||||
public function accept(Friendship $friendship)
|
public function accept(Friendship $friendship)
|
||||||
{
|
{
|
||||||
abort_unless($friendship->receiver_id === auth()->id(), 403);
|
abort_unless($friendship->receiver_id === auth()->id(), 403);
|
||||||
|
|
||||||
$friendship->update(['status' => 'accepted']);
|
$friendship->update(['status' => 'accepted']);
|
||||||
|
$friendship->load('receiver');
|
||||||
|
|
||||||
|
Notifier::send(
|
||||||
|
recipient: $friendship->sender,
|
||||||
|
title: $friendship->receiver->name . ' aceptó tu solicitud de amistad',
|
||||||
|
icon: 'fa-user-check',
|
||||||
|
color: 'success',
|
||||||
|
url: route('social.profile', $friendship->receiver),
|
||||||
|
);
|
||||||
|
|
||||||
return back()->with('success', 'Solicitud aceptada.');
|
return back()->with('success', 'Solicitud aceptada.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rechazar o cancelar solicitud
|
|
||||||
public function destroy(Friendship $friendship)
|
public function destroy(Friendship $friendship)
|
||||||
{
|
{
|
||||||
$me = auth()->id();
|
$me = auth()->id();
|
||||||
@@ -71,7 +81,7 @@ class FriendshipController extends Controller
|
|||||||
return back()->with('success', 'Solicitud eliminada.');
|
return back()->with('success', 'Solicitud eliminada.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// AJAX: enviar/cancelar solicitud desde búsqueda o perfil
|
// AJAX desde búsqueda/perfil
|
||||||
public function toggle(User $user)
|
public function toggle(User $user)
|
||||||
{
|
{
|
||||||
$me = auth()->user();
|
$me = auth()->user();
|
||||||
@@ -93,7 +103,13 @@ class FriendshipController extends Controller
|
|||||||
'status' => 'pending',
|
'status' => 'pending',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$user->notify(new FriendRequestNotification($me));
|
Notifier::send(
|
||||||
|
recipient: $user,
|
||||||
|
title: $me->name . ' te envió una solicitud de amistad',
|
||||||
|
icon: 'fa-user-plus',
|
||||||
|
color: 'primary',
|
||||||
|
url: route('friendships.index'),
|
||||||
|
);
|
||||||
|
|
||||||
return response()->json(['status' => 'pending']);
|
return response()->json(['status' => 'pending']);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace App\Http\Controllers;
|
|||||||
use App\Models\Post;
|
use App\Models\Post;
|
||||||
use App\Models\PostComment;
|
use App\Models\PostComment;
|
||||||
use App\Models\PostLike;
|
use App\Models\PostLike;
|
||||||
|
use App\Support\Notifier;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
@@ -51,15 +52,28 @@ class PostController extends Controller
|
|||||||
|
|
||||||
public function like(Post $post)
|
public function like(Post $post)
|
||||||
{
|
{
|
||||||
$userId = auth()->id();
|
$me = auth()->user();
|
||||||
$liked = PostLike::where('user_id', $userId)->where('post_id', $post->id)->first();
|
$liked = PostLike::where('user_id', $me->id)->where('post_id', $post->id)->first();
|
||||||
|
|
||||||
if ($liked) {
|
if ($liked) {
|
||||||
$liked->delete();
|
$liked->delete();
|
||||||
$liked = false;
|
$liked = false;
|
||||||
} else {
|
} else {
|
||||||
PostLike::create(['user_id' => $userId, 'post_id' => $post->id]);
|
PostLike::create(['user_id' => $me->id, 'post_id' => $post->id]);
|
||||||
$liked = true;
|
$liked = true;
|
||||||
|
|
||||||
|
// Notify post author (not if liking own post)
|
||||||
|
if ($post->user_id !== $me->id) {
|
||||||
|
$post->load('author');
|
||||||
|
Notifier::send(
|
||||||
|
recipient: $post->author,
|
||||||
|
title: $me->name . ' le dio Me gusta a tu publicación',
|
||||||
|
body: $post->body ? \Str::limit($post->body, 60) : '',
|
||||||
|
icon: 'fa-heart',
|
||||||
|
color: 'danger',
|
||||||
|
url: route('feed') . '#post-' . $post->id,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
@@ -72,14 +86,28 @@ class PostController extends Controller
|
|||||||
{
|
{
|
||||||
$request->validate(['body' => 'required|string|max:500']);
|
$request->validate(['body' => 'required|string|max:500']);
|
||||||
|
|
||||||
|
$me = auth()->user();
|
||||||
$comment = PostComment::create([
|
$comment = PostComment::create([
|
||||||
'user_id' => auth()->id(),
|
'user_id' => $me->id,
|
||||||
'post_id' => $post->id,
|
'post_id' => $post->id,
|
||||||
'body' => $request->body,
|
'body' => $request->body,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$comment->load('author');
|
$comment->load('author');
|
||||||
|
|
||||||
|
// Notify post author (not if commenting on own post)
|
||||||
|
if ($post->user_id !== $me->id) {
|
||||||
|
$post->load('author');
|
||||||
|
Notifier::send(
|
||||||
|
recipient: $post->author,
|
||||||
|
title: $me->name . ' comentó en tu publicación',
|
||||||
|
body: \Str::limit($request->body, 60),
|
||||||
|
icon: 'fa-comment',
|
||||||
|
color: 'info',
|
||||||
|
url: route('feed') . '#post-' . $post->id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'id' => $comment->id,
|
'id' => $comment->id,
|
||||||
'body' => $comment->body,
|
'body' => $comment->body,
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Notifications;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Notifications\Notification;
|
||||||
|
|
||||||
|
class AppNotification extends Notification
|
||||||
|
{
|
||||||
|
use Queueable;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
public readonly string $title,
|
||||||
|
public readonly string $body = '',
|
||||||
|
public readonly ?string $url = null,
|
||||||
|
public readonly string $icon = 'fa-bell',
|
||||||
|
public readonly string $color = 'primary',
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function via(object $notifiable): array
|
||||||
|
{
|
||||||
|
return ['database'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toArray(object $notifiable): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'title' => $this->title,
|
||||||
|
'body' => $this->body,
|
||||||
|
'url' => $this->url,
|
||||||
|
'icon' => $this->icon,
|
||||||
|
'color' => $this->color,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Support;
|
||||||
|
|
||||||
|
use App\Events\NewNotification;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Notifications\AppNotification;
|
||||||
|
|
||||||
|
class Notifier
|
||||||
|
{
|
||||||
|
public static function send(
|
||||||
|
User $recipient,
|
||||||
|
string $title,
|
||||||
|
string $body = '',
|
||||||
|
string $icon = 'fa-bell',
|
||||||
|
string $color = 'primary',
|
||||||
|
?string $url = null,
|
||||||
|
): void {
|
||||||
|
// Persist in DB so it shows on page reload
|
||||||
|
$recipient->notify(new AppNotification($title, $body, $url, $icon, $color));
|
||||||
|
|
||||||
|
// Broadcast in real-time to the bell dropdown
|
||||||
|
broadcast(new NewNotification($recipient->id, $title, $body, $icon, $color, $url));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user