300 lines
12 KiB
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]);
|
|
}
|
|
}
|