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]); } }