feat: message event
This commit is contained in:
parent
31e119ea07
commit
a75444a395
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use App\Http\Resources\Messaging\MessageResource;
|
||||
use App\Models\Message;
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class MessageSent implements ShouldBroadcast
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
public $message;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*/
|
||||
public function __construct(Message $message)
|
||||
{
|
||||
$this->message = $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the channels the event should broadcast on.
|
||||
*
|
||||
* @return array<int, \Illuminate\Broadcasting\Channel>
|
||||
*/
|
||||
public function broadcastOn(): array
|
||||
{
|
||||
return [
|
||||
new PrivateChannel('conversation.' . $this->message->conversation_id),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Data to broadcast.
|
||||
*/
|
||||
public function broadcastWith(): array
|
||||
{
|
||||
return [
|
||||
'message' => new MessageResource($this->message->load('user')),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\LoginRequest;
|
||||
use App\Http\Requests\RegisterRequest;
|
||||
use App\Http\Requests\Auth\LoginRequest;
|
||||
use App\Http\Requests\Auth\RegisterRequest;
|
||||
use App\Http\Requests\UpdateUserRequest;
|
||||
use App\Http\Resources\UserResource;
|
||||
use App\Models\User;
|
||||
|
|
@ -33,6 +33,7 @@ class AuthController extends Controller
|
|||
|
||||
return response()->json([
|
||||
'user' => new UserResource($user),
|
||||
'token' => $token,
|
||||
], 201)->cookie('token', $token, 60 * 24 * 30);
|
||||
}
|
||||
|
||||
|
|
@ -52,6 +53,7 @@ class AuthController extends Controller
|
|||
|
||||
return response()->json([
|
||||
'user' => new UserResource($user),
|
||||
'token' => $token,
|
||||
])->cookie('token', $token, 60 * 24 * 30);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,64 +2,96 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\Messaging\ConversationRequest;
|
||||
use App\Http\Resources\Messaging\ConversationResource;
|
||||
use App\Models\Conversation;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
|
||||
class ConversationController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index()
|
||||
public function index(Request $request): AnonymousResourceCollection
|
||||
{
|
||||
//
|
||||
}
|
||||
$conversations = $request->user()->conversations()
|
||||
->with(['users', 'latestMessage'])
|
||||
->latest()
|
||||
->get();
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
//
|
||||
return ConversationResource::collection($conversations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
public function store(ConversationRequest $request): ConversationResource
|
||||
{
|
||||
//
|
||||
$validated = $request->validated();
|
||||
|
||||
$conversation = Conversation::create([
|
||||
'label' => $validated['label'] ?? null,
|
||||
'is_group' => $validated['is_group'] ?? false,
|
||||
]);
|
||||
|
||||
$participants = $validated['participants'];
|
||||
if (!in_array($request->user()->id, $participants)) {
|
||||
$participants[] = $request->user()->id;
|
||||
}
|
||||
|
||||
$conversation->users()->sync($participants);
|
||||
|
||||
return new ConversationResource($conversation->load('users'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(Conversation $conversation)
|
||||
public function show(Request $request, Conversation $conversation): ConversationResource
|
||||
{
|
||||
//
|
||||
}
|
||||
// Check if user is participant
|
||||
if (!$conversation->participants()->where('user_id', $request->user()->id)->exists()) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(Conversation $conversation)
|
||||
{
|
||||
//
|
||||
return new ConversationResource($conversation->load(['users', 'latestMessage']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, Conversation $conversation)
|
||||
public function update(ConversationRequest $request, Conversation $conversation): ConversationResource
|
||||
{
|
||||
//
|
||||
if (!$conversation->participants()->where('user_id', $request->user()->id)->exists()) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$validated = $request->validated();
|
||||
|
||||
$conversation->update([
|
||||
'label' => $validated['label'] ?? $conversation->label,
|
||||
'is_group' => $validated['is_group'] ?? $conversation->is_group,
|
||||
]);
|
||||
|
||||
if (isset($validated['participants'])) {
|
||||
$conversation->users()->sync($validated['participants']);
|
||||
}
|
||||
|
||||
return new ConversationResource($conversation->load('users'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(Conversation $conversation)
|
||||
public function destroy(Request $request, Conversation $conversation)
|
||||
{
|
||||
//
|
||||
if (!$conversation->participants()->where('user_id', $request->user()->id)->exists()) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$conversation->delete();
|
||||
|
||||
return response()->noContent();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,64 +2,88 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\Messaging\MessageRequest;
|
||||
use App\Http\Resources\Messaging\MessageResource;
|
||||
use App\Models\Conversation;
|
||||
use App\Models\Message;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
|
||||
class MessageController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
* Display a listing of the resource for a conversation.
|
||||
*/
|
||||
public function index()
|
||||
public function index(Request $request, Conversation $conversation): AnonymousResourceCollection
|
||||
{
|
||||
//
|
||||
}
|
||||
if (!$conversation->participants()->where('user_id', $request->user()->id)->exists()) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
//
|
||||
$messages = $conversation->messages()
|
||||
->with('user')
|
||||
->oldest()
|
||||
->get();
|
||||
|
||||
return MessageResource::collection($messages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
public function store(MessageRequest $request, Conversation $conversation): MessageResource
|
||||
{
|
||||
//
|
||||
}
|
||||
if (!$conversation->participants()->where('user_id', $request->user()->id)->exists()) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(Message $message)
|
||||
{
|
||||
//
|
||||
}
|
||||
$validated = $request->validated();
|
||||
|
||||
$user = $request->user();
|
||||
|
||||
$message = $conversation->messages()->create([
|
||||
'user_id' => $user->id,
|
||||
'sender_name' => $user->name,
|
||||
'sender_avatar_url' => $user->avatar_url,
|
||||
'body' => $validated['body'],
|
||||
]);
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*/
|
||||
public function edit(Message $message)
|
||||
{
|
||||
//
|
||||
}
|
||||
broadcast(new \App\Events\MessageSent($message))->toOthers();
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, Message $message)
|
||||
{
|
||||
//
|
||||
return new MessageResource($message->load('user'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(Message $message)
|
||||
public function destroy(Request $request, Message $message)
|
||||
{
|
||||
//
|
||||
if ($message->user_id !== $request->user()->id) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$message->delete();
|
||||
|
||||
return response()->noContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a message as read.
|
||||
*/
|
||||
public function markAsRead(Request $request, Message $message)
|
||||
{
|
||||
$conversation = $message->conversation;
|
||||
if (!$conversation->participants()->where('user_id', $request->user()->id)->exists()) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
if ($message->user_id === $request->user()->id) {
|
||||
// Can't mark your own message as read by you (well you could but it's redundant)
|
||||
// But let's say anyone in the conversation can mark any message as read.
|
||||
}
|
||||
|
||||
$message->update(['read_at' => now()]);
|
||||
|
||||
return new MessageResource($message->load('user'));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Http\Resources\UserResource;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of all users except the authenticated one.
|
||||
*/
|
||||
public function index(Request $request): AnonymousResourceCollection
|
||||
{
|
||||
// On retourne tous les utilisateurs sauf soi-même pour faciliter la création de conversations
|
||||
$users = User::all();
|
||||
|
||||
return UserResource::collection($users);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
namespace App\Http\Requests\Auth;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
namespace App\Http\Requests\Auth;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Messaging;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ConversationRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'label' => 'nullable|string|max:255',
|
||||
'is_group' => 'boolean',
|
||||
'participants' => 'required|array|min:1',
|
||||
'participants.*' => 'exists:users,id',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Messaging;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class MessageRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'body' => 'required|string',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Resources\Messaging;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use App\Http\Resources\UserResource;
|
||||
|
||||
class ConversationResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'label' => $this->label,
|
||||
'isGroup' => $this->is_group,
|
||||
'participants' => UserResource::collection($this->whenLoaded('users')),
|
||||
'lastMessage' => new MessageResource($this->whenLoaded('latestMessage')),
|
||||
'createdAt' => $this->created_at,
|
||||
'updatedAt' => $this->updated_at,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Resources\Messaging;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use App\Http\Resources\UserResource;
|
||||
|
||||
class MessageResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* Transform the resource into an array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'senderId' => $this->user_id,
|
||||
'senderName' => $this->sender_name,
|
||||
'senderAvatarUrl' => $this->sender_avatar_url,
|
||||
'body' => $this->body,
|
||||
'readAt' => $this->read_at,
|
||||
'createdAt' => $this->created_at,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -16,9 +16,10 @@ class UserResource extends JsonResource
|
|||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'email' => $this->email,
|
||||
'avatar_url' => $this->avatar_url ? asset(Storage::url($this->avatar_url)) : null,
|
||||
'avatarUrl' => $this->avatar_url ? asset(Storage::url($this->avatar_url)) : null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ class todoResource extends JsonResource
|
|||
'name' => $this->name,
|
||||
'status' => $this->status,
|
||||
'description' => $this->description,
|
||||
'image_url' => $this->image_url ? asset(Storage::url($this->image_url)): null,
|
||||
'due_date' => $this->due_date,
|
||||
'imageUrl' => $this->image_url ? asset(Storage::url($this->image_url)): null,
|
||||
'dueDate' => $this->due_date,
|
||||
'priority' => $this->priority,
|
||||
'created_at' => $this->created_at,
|
||||
'updated_at' => $this->updated_at,
|
||||
|
|
|
|||
|
|
@ -35,6 +35,11 @@ class Conversation extends Model
|
|||
return $this->hasMany(Message::class);
|
||||
}
|
||||
|
||||
public function latestMessage(): \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
{
|
||||
return $this->hasOne(Message::class)->latestOfMany();
|
||||
}
|
||||
|
||||
public function users(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(User::class, 'participants')
|
||||
|
|
|
|||
|
|
@ -57,6 +57,18 @@ class User extends Authenticatable implements FilamentUser
|
|||
return $this->hasMany(Todo::class);
|
||||
}
|
||||
|
||||
public function conversations(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Conversation::class, 'participants')
|
||||
->withTimestamps()
|
||||
->using(Participant::class);
|
||||
}
|
||||
|
||||
public function messages(): HasMany
|
||||
{
|
||||
return $this->hasMany(Message::class);
|
||||
}
|
||||
|
||||
public function isAdmin()
|
||||
{
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -21,3 +21,14 @@ Route::middleware('auth:sanctum')->prefix('todos')->group(function (): void {
|
|||
Route::patch('/{todo}', [todoController::class, 'update']);
|
||||
Route::delete('/{todo}', [todoController::class, 'destroy']);
|
||||
});
|
||||
|
||||
Route::middleware('auth:sanctum')->group(function (): void {
|
||||
Route::get('users', [\App\Http\Controllers\UserController::class, 'index']);
|
||||
Route::apiResource('conversations', \App\Http\Controllers\ConversationController::class);
|
||||
|
||||
Route::get('conversations/{conversation}/messages', [\App\Http\Controllers\MessageController::class, 'index']);
|
||||
Route::post('conversations/{conversation}/messages', [\App\Http\Controllers\MessageController::class, 'store']);
|
||||
|
||||
Route::delete('messages/{message}', [\App\Http\Controllers\MessageController::class, 'destroy']);
|
||||
Route::patch('messages/{message}/read', [\App\Http\Controllers\MessageController::class, 'markAsRead']);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,5 +3,12 @@
|
|||
use Illuminate\Support\Facades\Broadcast;
|
||||
|
||||
Broadcast::channel('App.Models.User.{id}', function ($user, $id) {
|
||||
return (int) $user->id === (int) $id;
|
||||
return (string) $user->id === (string) $id;
|
||||
});
|
||||
|
||||
Broadcast::channel('conversation.{conversationId}', function ($user, $conversationId) {
|
||||
return \App\Models\Conversation::find($conversationId)
|
||||
?->participants()
|
||||
->where('user_id', $user->id)
|
||||
->exists();
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue