ultimatepos/Modules/Communication/Http/Controllers/ChatController.php

300 lines
12 KiB
PHP

<?php
namespace Modules\Communication\Http\Controllers;
use App\User;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Modules\Communication\Entities\ChatMessage;
use Modules\Communication\Entities\ChatRoom;
use Modules\Communication\Events\MessageSent;
use Modules\Communication\Events\UserTyping;
use Modules\Communication\Services\ChatRoomService;
use Modules\Communication\Services\PresenceService;
class ChatController extends Controller
{
public function __construct(
private ChatRoomService $chatRoomService,
private PresenceService $presenceService
) {}
private function authorizeModule(): int
{
$business_id = request()->session()->get('user.business_id');
if (! auth()->user()->can('communication.access')) {
abort(403, 'Unauthorized action.');
}
return (int) $business_id;
}
public function index()
{
$businessId = $this->authorizeModule();
$this->presenceService->heartbeat($businessId, auth()->id());
return view('communication::spa');
}
public function colleagues()
{
$businessId = $this->authorizeModule();
$users = User::where('business_id', $businessId)
->where('id', '!=', auth()->id())
->orderBy('first_name')
->get(['id', 'surname', 'first_name', 'last_name', 'username', 'email']);
$onlineIds = $this->presenceService->onlineUserIds($businessId, $users->pluck('id')->all());
return response()->json($users->map(function ($user) use ($onlineIds) {
return [
'id' => $user->id,
'surname' => $user->surname,
'first_name' => $user->first_name,
'last_name' => $user->last_name,
'username' => $user->username,
'email' => $user->email,
'is_online' => in_array($user->id, $onlineIds, true),
];
}));
}
public function presence()
{
$businessId = $this->authorizeModule();
$userIds = User::where('business_id', $businessId)->pluck('id')->all();
$onlineIds = $this->presenceService->onlineUserIds($businessId, $userIds);
return response()->json(['online_user_ids' => $onlineIds]);
}
public function heartbeat()
{
$businessId = $this->authorizeModule();
$this->presenceService->heartbeat($businessId, auth()->id());
return response()->json(['success' => true]);
}
public function rooms(Request $request)
{
$businessId = $this->authorizeModule();
$user = auth()->user();
$this->presenceService->heartbeat($businessId, $user->id);
$query = ChatRoom::where('business_id', $businessId)
->whereHas('members', fn ($q) => $q->where('users.id', $user->id))
->with([
'members' => fn ($q) => $q->select('users.id', 'surname', 'first_name', 'last_name', 'username', 'email')->withPivot('last_read_at'),
'latestMessage.user:id,surname,first_name,last_name,username',
]);
if ($request->filled('type') && in_array($request->type, ['direct', 'group'], true)) {
$query->where('type', $request->type);
}
if ($request->filled('q')) {
$search = $request->q;
$query->where(function ($sub) use ($search, $user) {
$sub->where('name', 'like', '%'.$search.'%')
->orWhereHas('members', function ($m) use ($search, $user) {
$m->where('users.id', '!=', $user->id)
->where(function ($n) use ($search) {
$n->where('first_name', 'like', '%'.$search.'%')
->orWhere('last_name', 'like', '%'.$search.'%');
});
})
->orWhereHas('latestMessage', fn ($lm) => $lm->where('body', 'like', '%'.$search.'%'));
});
}
$rooms = $query->orderByDesc('last_message_at')
->orderByDesc('updated_at')
->get();
$memberIds = $rooms->flatMap(fn ($room) => $room->members->pluck('id'))->unique()->all();
$onlineIds = $this->presenceService->onlineUserIds($businessId, $memberIds);
return response()->json($rooms->map(function ($room) use ($user, $onlineIds) {
$myPivot = $room->members->firstWhere('id', $user->id)?->pivot;
$lastReadAt = $myPivot?->last_read_at;
$unread = 0;
if ($room->latestMessage && (int) $room->latestMessage->user_id !== (int) $user->id) {
if (! $lastReadAt || $room->latestMessage->created_at > $lastReadAt) {
$unread = ChatMessage::where('chat_room_id', $room->id)
->when($lastReadAt, fn ($q) => $q->where('created_at', '>', $lastReadAt))
->where('user_id', '!=', $user->id)
->count();
}
}
$otherMember = $room->type === 'direct'
? $room->members->firstWhere('id', '!=', $user->id)
: null;
return [
'id' => $room->id,
'type' => $room->type,
'name' => $room->name,
'created_by' => $room->created_by,
'last_message_at' => $room->last_message_at?->toIso8601String(),
'members' => $room->members->map(fn ($m) => [
'id' => $m->id,
'surname' => $m->surname,
'first_name' => $m->first_name,
'last_name' => $m->last_name,
'username' => $m->username,
'email' => $m->email,
'is_online' => in_array($m->id, $onlineIds, true),
]),
'latest_message' => $room->latestMessage ? [
'body' => $room->latestMessage->body,
'created_at' => $room->latestMessage->created_at?->toIso8601String(),
'user' => $room->latestMessage->user,
] : null,
'unread_count' => $unread,
'is_other_online' => $otherMember ? in_array($otherMember->id, $onlineIds, true) : false,
];
}));
}
public function showRoom(int $roomId)
{
$businessId = $this->authorizeModule();
$room = ChatRoom::where('business_id', $businessId)
->with(['members:id,surname,first_name,last_name,username,email', 'creator:id,surname,first_name,last_name,username'])
->findOrFail($roomId);
$this->chatRoomService->assertRoomMember($room, auth()->user());
$onlineIds = $this->presenceService->onlineUserIds(
$businessId,
$room->members->pluck('id')->all()
);
return response()->json([
'id' => $room->id,
'type' => $room->type,
'name' => $room->name,
'created_by' => $room->created_by,
'members' => $room->members->map(fn ($m) => [
'id' => $m->id,
'surname' => $m->surname,
'first_name' => $m->first_name,
'last_name' => $m->last_name,
'username' => $m->username,
'email' => $m->email,
'is_online' => in_array($m->id, $onlineIds, true),
]),
]);
}
public function createRoom(Request $request)
{
$businessId = $this->authorizeModule();
$validated = $request->validate([
'type' => 'required|in:direct,group',
'user_id' => 'required_if:type,direct|integer|exists:users,id',
'name' => 'required_if:type,group|string|max:255',
'member_ids' => 'required_if:type,group|array|min:1',
'member_ids.*' => 'integer|exists:users,id',
]);
$user = auth()->user();
$room = $validated['type'] === 'direct'
? $this->chatRoomService->findOrCreateDirectRoom($businessId, $user, (int) $validated['user_id'])
: $this->chatRoomService->createGroupRoom($businessId, $user, $validated['name'], $validated['member_ids'] ?? []);
return response()->json($room);
}
public function addMember(Request $request, int $roomId)
{
$businessId = $this->authorizeModule();
$validated = $request->validate(['user_id' => 'required|integer|exists:users,id']);
$room = ChatRoom::where('business_id', $businessId)->findOrFail($roomId);
$room = $this->chatRoomService->addMember($room, $businessId, auth()->user(), (int) $validated['user_id']);
return response()->json($room);
}
public function removeMember(int $roomId, int $userId)
{
$businessId = $this->authorizeModule();
$room = ChatRoom::where('business_id', $businessId)->findOrFail($roomId);
$room = $this->chatRoomService->removeMember($room, auth()->user(), $userId);
return response()->json($room);
}
public function messages(Request $request, int $roomId)
{
$businessId = $this->authorizeModule();
$room = ChatRoom::where('business_id', $businessId)->findOrFail($roomId);
$this->chatRoomService->assertRoomMember($room, auth()->user());
$query = ChatMessage::where('chat_room_id', $room->id)
->with('user:id,surname,first_name,last_name,username,email')
->orderBy('created_at');
if ($request->filled('search')) {
$query->where('body', 'like', '%'.$request->search.'%');
}
return response()->json($query->get());
}
public function sendMessage(Request $request, int $roomId)
{
$businessId = $this->authorizeModule();
$validated = $request->validate([
'body' => 'required|string|max:5000',
'type' => 'sometimes|in:text,file,image',
'metadata' => 'sometimes|array',
]);
$room = ChatRoom::where('business_id', $businessId)->findOrFail($roomId);
$this->chatRoomService->assertRoomMember($room, auth()->user());
$message = ChatMessage::create([
'chat_room_id' => $room->id,
'user_id' => auth()->id(),
'body' => $validated['body'],
'type' => $validated['type'] ?? 'text',
'metadata' => $validated['metadata'] ?? null,
]);
$room->update(['last_message_at' => now()]);
$this->presenceService->heartbeat($businessId, auth()->id());
try {
event(new MessageSent($message->load('user:id,surname,first_name,last_name,username,email'), $businessId));
} catch (\Throwable $e) {
report($e);
}
return response()->json($message->load('user:id,surname,first_name,last_name,username,email'));
}
public function markRead(int $roomId)
{
$businessId = $this->authorizeModule();
$room = ChatRoom::where('business_id', $businessId)->findOrFail($roomId);
$this->chatRoomService->assertRoomMember($room, auth()->user());
$room->members()->updateExistingPivot(auth()->id(), ['last_read_at' => now()]);
return response()->json(['success' => true]);
}
public function typing(int $roomId)
{
$businessId = $this->authorizeModule();
$room = ChatRoom::where('business_id', $businessId)->findOrFail($roomId);
$this->chatRoomService->assertRoomMember($room, auth()->user());
try {
event(new UserTyping($businessId, $room->id, auth()->id(), auth()->user()->user_full_name));
} catch (\Throwable $e) {
report($e);
}
return response()->json(['success' => true]);
}
}