message = model(Message::class); $this->user = model(User::class); $this->config = model(Configuration::class); } public function conversations() { $user = $this->getCurrentUser(); if (!$user) { return $this->respondError('Authentication required', Response::HTTP_UNAUTHORIZED); } $userId = (int) $user->id; $sql = <<<'SQL' SELECT CASE WHEN sender_id = ? THEN recipient_id ELSE sender_id END AS other_user_id, MAX(sent_datetime) AS last_message_time, SUM(CASE WHEN recipient_id = ? AND read_status = 0 THEN 1 ELSE 0 END) AS unread_count, ( SELECT m2.message FROM messages m2 WHERE (m2.sender_id = ? AND m2.recipient_id = CASE WHEN m1.sender_id = ? THEN m1.recipient_id ELSE m1.sender_id END) OR (m2.recipient_id = ? AND m2.sender_id = CASE WHEN m1.sender_id = ? THEN m1.recipient_id ELSE m1.sender_id END) ORDER BY m2.sent_datetime DESC LIMIT 1 ) AS last_message FROM messages m1 WHERE (sender_id = ? OR recipient_id = ?) AND (subject = '' OR subject IS NULL OR subject = 'chat') GROUP BY other_user_id ORDER BY last_message_time DESC SQL; $rows = DB::select($sql, array_fill(0, 8, $userId)); $conversations = []; foreach ($rows as $row) { $otherUser = $this->user->find($row->other_user_id); if (!$otherUser) { continue; } $conversations[] = [ 'user_id' => $row->other_user_id, 'user_name' => trim(($otherUser['firstname'] ?? '') . ' ' . ($otherUser['lastname'] ?? '')), 'user_initials' => strtoupper(($otherUser['firstname'][0] ?? '') . ($otherUser['lastname'][0] ?? '')), 'last_message' => $row->last_message ?? '', 'last_message_time'=> $row->last_message_time ?? null, 'unread_count' => (int) ($row->unread_count ?? 0), ]; } return $this->success($conversations, 'Conversations retrieved successfully'); } public function conversation($userId = null) { $user = $this->getCurrentUser(); if (!$user) { return $this->respondError('Authentication required', Response::HTTP_UNAUTHORIZED); } if (!$userId) { return $this->error('User ID is required', Response::HTTP_BAD_REQUEST); } $currentUserId = (int) $user->id; $otherUserId = (int) $userId; $otherUser = $this->user->find($otherUserId); if (!$otherUser) { return $this->error('User not found', Response::HTTP_NOT_FOUND); } $page = max(1, (int) ($this->request->getGet('page') ?? 1)); $perPage = min(100, max(1, (int) ($this->request->getGet('per_page') ?? 50))); $offset = ($page - 1) * $perPage; $baseQuery = DB::table('messages') ->where(function ($query) use ($currentUserId, $otherUserId) { $query->where('sender_id', $currentUserId) ->where('recipient_id', $otherUserId); }) ->orWhere(function ($query) use ($currentUserId, $otherUserId) { $query->where('sender_id', $otherUserId) ->where('recipient_id', $currentUserId); }) ->whereRaw("(subject = '' OR subject IS NULL OR subject = 'chat')") ->orderBy('sent_datetime', 'ASC'); $total = (clone $baseQuery)->count(); $messages = (clone $baseQuery)->offset($offset)->limit($perPage)->get(); foreach ($messages as $message) { if ($message->recipient_id === $currentUserId && ((int) $message->read_status) === 0) { $this->message->update($message->id, [ 'read_status' => 1, 'read_datetime'=> Carbon::now('UTC')->format('Y-m-d H:i:s'), ]); } } $enriched = []; foreach ($messages as $msg) { $enriched[] = [ 'id' => $msg->id, 'sender_id' => $msg->sender_id, 'recipient_id' => $msg->recipient_id, 'message' => $msg->message, 'sent_datetime' => $msg->sent_datetime, 'read_status' => $msg->read_status, 'read_datetime' => $msg->read_datetime, 'attachment' => $msg->attachment ?? null, 'is_sender' => $msg->sender_id === $currentUserId, ]; } $payload = [ 'messages' => $enriched, 'other_user' => [ 'id' => $otherUser['id'], 'name' => trim(($otherUser['firstname'] ?? '') . ' ' . ($otherUser['lastname'] ?? '')), 'initials' => strtoupper(($otherUser['firstname'][0] ?? '') . ($otherUser['lastname'][0] ?? '')), ], 'pagination' => [ 'current_page' => $page, 'per_page' => $perPage, 'total' => $total, 'total_pages' => (int) ceil($total / $perPage), ], ]; return $this->success($payload, 'Messages retrieved successfully'); } public function send() { $user = $this->getCurrentUser(); if (!$user) { return $this->respondError('Authentication required', Response::HTTP_UNAUTHORIZED); } $payload = $this->payloadData(); if (empty($payload)) { return $this->error('Invalid request data', Response::HTTP_BAD_REQUEST); } $rules = [ 'recipient_id' => 'required|integer', 'message' => 'required|max_length[5000]', ]; $errors = $this->validateRequest($payload, $rules); if (!empty($errors)) { return $this->respondValidationError($errors); } $recipientId = (int) $payload['recipient_id']; if ($recipientId === (int) $user->id) { return $this->error('Cannot send message to yourself', Response::HTTP_BAD_REQUEST); } $recipient = $this->user->find($recipientId); if (!$recipient) { return $this->error('Recipient not found', Response::HTTP_NOT_FOUND); } $schoolYear = $this->config->getConfig('school_year') ?? date('Y'); $semester = $this->config->getConfig('semester') ?? 'Fall'; $maxMessageNumber = $this->message->newQuery() ->selectRaw('COALESCE(MAX(message_number), 0) AS message_number') ->first(); $messageNumber = (int) ($maxMessageNumber->message_number ?? $maxMessageNumber['message_number'] ?? 0) + 1; $messageData = [ 'sender_id' => (int) $user->id, 'recipient_id' => $recipientId, 'subject' => 'chat', 'message' => $payload['message'], 'sent_datetime' => Carbon::now('UTC')->format('Y-m-d H:i:s'), 'read_status' => 0, 'message_number'=> $messageNumber, 'priority' => 'normal', 'status' => 'sent', 'semester' => $semester, 'school_year' => $schoolYear, 'attachment' => $payload['attachment'] ?? null, ]; try { $message = $this->message->create($messageData); return $this->success([ 'id' => $message->id, 'sender_id' => $message->sender_id, 'recipient_id' => $message->recipient_id, 'message' => $message->message, 'sent_datetime' => $message->sent_datetime, 'read_status' => $message->read_status, 'attachment' => $message->attachment, 'is_sender' => true, ], 'Message sent successfully', Response::HTTP_CREATED); } catch (\Throwable $e) { log_message('error', 'Message send error: ' . $e->getMessage()); return $this->respondError('Failed to send message', Response::HTTP_INTERNAL_SERVER_ERROR); } } public function markRead() { $user = $this->getCurrentUser(); if (!$user) { return $this->respondError('Authentication required', Response::HTTP_UNAUTHORIZED); } $payload = $this->payloadData(); if (empty($payload)) { return $this->error('Invalid request data', Response::HTTP_BAD_REQUEST); } $currentUserId = (int) $user->id; try { if (!empty($payload['message_ids']) && is_array($payload['message_ids'])) { $messageIds = array_map('intval', $payload['message_ids']); $updated = $this->message->newQuery() ->whereIn('id', $messageIds) ->where('recipient_id', $currentUserId) ->where('read_status', 0) ->update([ 'read_status' => 1, 'read_datetime'=> Carbon::now('UTC')->format('Y-m-d H:i:s'), ]); return $this->success([ 'updated_count' => $updated, ], 'Messages marked as read'); } if (isset($payload['user_id'])) { $senderId = (int) $payload['user_id']; $updated = $this->message->newQuery() ->where('sender_id', $senderId) ->where('recipient_id', $currentUserId) ->where('read_status', 0) ->update([ 'read_status' => 1, 'read_datetime'=> Carbon::now('UTC')->format('Y-m-d H:i:s'), ]); return $this->success([ 'updated_count' => $updated, ], 'Messages marked as read'); } return $this->error('Either message_ids or user_id is required', Response::HTTP_BAD_REQUEST); } catch (\Throwable $e) { log_message('error', 'Mark read error: ' . $e->getMessage()); return $this->respondError('Failed to mark messages as read', Response::HTTP_INTERNAL_SERVER_ERROR); } } public function unreadCount() { $user = $this->getCurrentUser(); if (!$user) { return $this->respondError('Authentication required', Response::HTTP_UNAUTHORIZED); } try { $count = $this->message->newQuery() ->where('recipient_id', (int) $user->id) ->where('read_status', 0) ->whereRaw("(subject = '' OR subject IS NULL OR subject = 'chat')") ->count(); return $this->success([ 'unread_count' => $count, ], 'Unread count retrieved successfully'); } catch (\Throwable $e) { log_message('error', 'Unread count error: ' . $e->getMessage()); return $this->respondError('Failed to get unread count', Response::HTTP_INTERNAL_SERVER_ERROR); } } public function searchUsers() { $user = $this->getCurrentUser(); if (!$user) { return $this->respondError('Authentication required', Response::HTTP_UNAUTHORIZED); } $query = trim((string) ($this->request->getGet('q') ?? '')); if (strlen($query) < 2) { return $this->error('Search query must be at least 2 characters', Response::HTTP_BAD_REQUEST); } $limit = min(20, max(1, (int) ($this->request->getGet('limit') ?? 10))); try { $users = $this->user->newQuery() ->where(function ($q) use ($query) { $q->where('firstname', 'LIKE', "%{$query}%") ->orWhere('lastname', 'LIKE', "%{$query}%") ->orWhere('email', 'LIKE', "%{$query}%"); }) ->where('id', '!=', (int) $user->id) ->where('status', 'active') ->limit($limit) ->get(); $results = []; foreach ($users as $u) { $results[] = [ 'id' => $u->id, 'name' => trim(($u->firstname ?? '') . ' ' . ($u->lastname ?? '')), 'initials' => strtoupper(($u->firstname[0] ?? '') . ($u->lastname[0] ?? '')), 'email' => $u->email ?? '', ]; } return $this->success($results, 'Users retrieved successfully'); } catch (\Throwable $e) { log_message('error', 'Search users error: ' . $e->getMessage()); return $this->respondError('Failed to search users', Response::HTTP_INTERNAL_SERVER_ERROR); } } public function deleteMessage($id = null) { $user = $this->getCurrentUser(); if (!$user) { return $this->respondError('Authentication required', Response::HTTP_UNAUTHORIZED); } if (!$id) { return $this->error('Message ID is required', Response::HTTP_BAD_REQUEST); } $message = $this->message->find($id); if (!$message) { return $this->error('Message not found', Response::HTTP_NOT_FOUND); } if ($message->sender_id !== (int) $user->id && $message->recipient_id !== (int) $user->id) { return $this->error('Unauthorized to delete this message', Response::HTTP_FORBIDDEN); } $this->message->update($id, [ 'status' => 'deleted', ]); return $this->success(null, 'Message deleted successfully'); } public function deleteConversation($userId = null) { $user = $this->getCurrentUser(); if (!$user) { return $this->respondError('Authentication required', Response::HTTP_UNAUTHORIZED); } if (!$userId) { return $this->error('User ID is required', Response::HTTP_BAD_REQUEST); } $currentUserId = (int) $user->id; $otherUserId = (int) $userId; try { $updated = $this->message->newQuery() ->where(function ($query) use ($currentUserId, $otherUserId) { $query->where('sender_id', $currentUserId) ->where('recipient_id', $otherUserId); }) ->orWhere(function ($query) use ($currentUserId, $otherUserId) { $query->where('sender_id', $otherUserId) ->where('recipient_id', $currentUserId); }) ->whereRaw("(subject = '' OR subject IS NULL OR subject = 'chat')") ->update(['status' => 'deleted']); return $this->success([ 'deleted_count' => $updated, ], 'Conversation deleted successfully'); } catch (\Throwable $e) { log_message('error', 'Delete conversation error: ' . $e->getMessage()); return $this->respondError('Failed to delete conversation', Response::HTTP_INTERNAL_SERVER_ERROR); } } }