157 lines
8.3 KiB
PHP
157 lines
8.3 KiB
PHP
<?php
|
|
|
|
namespace App\Controllers;
|
|
|
|
use App\Core\Database;
|
|
use App\Core\Request;
|
|
use App\Core\Response;
|
|
|
|
class ChatController
|
|
{
|
|
public function getConversations(Request $request, Response $response): void
|
|
{
|
|
$identityId = (int)$request->identity_id;
|
|
$contacts = $request->role === 'teacher'
|
|
? Database::select(
|
|
"SELECT DISTINCT ai.id AS user_id, s.uuid AS user_uuid, s.full_name, 'student' AS role, s.grade_level AS specialization
|
|
FROM course_access_passes cap
|
|
JOIN courses c ON c.id = cap.course_id
|
|
JOIN students s ON s.id = cap.student_id
|
|
JOIN auth_identities ai ON ai.id = s.identity_id AND ai.status = 'active'
|
|
WHERE c.teacher_id = ? AND cap.is_active = 1",
|
|
[$request->user_id]
|
|
)
|
|
: Database::select(
|
|
"SELECT DISTINCT ai.id AS user_id, t.uuid AS user_uuid, t.full_name, 'teacher' AS role, t.specialization
|
|
FROM course_access_passes cap
|
|
JOIN courses c ON c.id = cap.course_id
|
|
JOIN teachers t ON t.id = c.teacher_id
|
|
JOIN auth_identities ai ON ai.id = t.identity_id AND ai.status = 'active'
|
|
WHERE cap.student_id = ? AND cap.is_active = 1",
|
|
[$request->user_id]
|
|
);
|
|
|
|
foreach ($contacts as &$contact) {
|
|
$other = (int)$contact['user_id'];
|
|
$last = Database::selectOne(
|
|
"SELECT message, message_type, created_at FROM chat_messages
|
|
WHERE (sender_identity_id = ? AND receiver_identity_id = ?) OR (sender_identity_id = ? AND receiver_identity_id = ?)
|
|
ORDER BY id DESC LIMIT 1",
|
|
[$identityId, $other, $other, $identityId]
|
|
);
|
|
$unread = Database::selectOne(
|
|
"SELECT COUNT(*) AS count_value FROM chat_messages WHERE sender_identity_id = ? AND receiver_identity_id = ? AND is_read = 0",
|
|
[$other, $identityId]
|
|
);
|
|
$contact['last_message'] = $last['message'] ?? '';
|
|
$contact['last_message_type'] = $last['message_type'] ?? '';
|
|
$contact['last_message_at'] = $last['created_at'] ?? null;
|
|
$contact['unread_count'] = (int)($unread['count_value'] ?? 0);
|
|
}
|
|
unset($contact);
|
|
$response->json(['status' => 'success', 'data' => $contacts]);
|
|
}
|
|
|
|
public function getMessages(Request $request, Response $response): void
|
|
{
|
|
$me = (int)$request->identity_id;
|
|
$other = (int)($request->getQuery('other_user_id', $request->getQuery('user_id', 0)));
|
|
if (!$other || !$this->isAllowedContact($request, $other)) {
|
|
$response->status(403)->json(['status' => 'error', 'message' => 'المحادثة غير مصرح بها']);
|
|
return;
|
|
}
|
|
$limit = min(100, max(1, (int)$request->getQuery('limit', 50)));
|
|
$messages = Database::select(
|
|
"SELECT id, uuid, sender_identity_id AS sender_id, receiver_identity_id AS receiver_id,
|
|
course_id, lesson_id, message, message_type, media_url, is_read, read_at, created_at
|
|
FROM chat_messages
|
|
WHERE (sender_identity_id = ? AND receiver_identity_id = ?) OR (sender_identity_id = ? AND receiver_identity_id = ?)
|
|
ORDER BY created_at ASC LIMIT ?",
|
|
[$me, $other, $other, $me, $limit]
|
|
);
|
|
foreach ($messages as &$message) $message['is_mine'] = (int)$message['sender_id'] === $me;
|
|
unset($message);
|
|
Database::query("UPDATE chat_messages SET is_read = 1, read_at = NOW() WHERE sender_identity_id = ? AND receiver_identity_id = ? AND is_read = 0", [$other, $me]);
|
|
$response->json(['status' => 'success', 'data' => ['other_user' => $this->identityProfile($other), 'messages' => $messages]]);
|
|
}
|
|
|
|
public function sendMessage(Request $request, Response $response): void
|
|
{
|
|
$body = $request->getBody();
|
|
$receiver = (int)($body['receiver_id'] ?? 0);
|
|
$message = trim((string)($body['message'] ?? ''));
|
|
$type = (string)($body['message_type'] ?? 'text');
|
|
if (!$receiver || $message === '' || !$this->isAllowedContact($request, $receiver)) {
|
|
$response->status(403)->json(['status' => 'error', 'message' => 'المستلم أو الرسالة غير صالحين لهذه المحادثة']);
|
|
return;
|
|
}
|
|
if (!in_array($type, ['text', 'voice', 'image', 'file'], true)) $type = 'text';
|
|
$uuid = $this->uuid();
|
|
$id = Database::insert(
|
|
"INSERT INTO chat_messages (uuid, sender_identity_id, receiver_identity_id, course_id, lesson_id, message, message_type, media_url)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
|
[$uuid, $request->identity_id, $receiver, !empty($body['course_id']) ? (int)$body['course_id'] : null, !empty($body['lesson_id']) ? (int)$body['lesson_id'] : null, $message, $type, $body['media_url'] ?? null]
|
|
);
|
|
$payload = ['id' => $id, 'uuid' => $uuid, 'sender_id' => $request->identity_id, 'receiver_id' => $receiver, 'message' => $message, 'message_type' => $type, 'media_url' => $body['media_url'] ?? null, 'is_read' => false, 'is_mine' => true, 'created_at' => date('Y-m-d H:i:s')];
|
|
self::pushToWorkerman('push_chat', $receiver, $payload);
|
|
$response->status(201)->json(['status' => 'success', 'data' => $payload]);
|
|
}
|
|
|
|
public function markAsRead(Request $request, Response $response): void
|
|
{
|
|
$sender = (int)($request->getBody()['sender_id'] ?? 0);
|
|
if (!$sender || !$this->isAllowedContact($request, $sender)) {
|
|
$response->status(403)->json(['status' => 'error', 'message' => 'المحادثة غير مصرح بها']);
|
|
return;
|
|
}
|
|
Database::query("UPDATE chat_messages SET is_read = 1, read_at = NOW() WHERE sender_identity_id = ? AND receiver_identity_id = ? AND is_read = 0", [$sender, $request->identity_id]);
|
|
$response->json(['status' => 'success']);
|
|
}
|
|
|
|
public function getUnreadCount(Request $request, Response $response): void
|
|
{
|
|
$row = Database::selectOne("SELECT COUNT(*) AS unread_total FROM chat_messages WHERE receiver_identity_id = ? AND is_read = 0", [$request->identity_id]);
|
|
$response->json(['status' => 'success', 'data' => ['unread_count' => (int)($row['unread_total'] ?? 0)]]);
|
|
}
|
|
|
|
private function isAllowedContact(Request $request, int $otherIdentityId): bool
|
|
{
|
|
if ($request->role === 'teacher') {
|
|
return (bool)Database::selectOne(
|
|
"SELECT 1 FROM course_access_passes cap JOIN courses c ON c.id = cap.course_id JOIN students s ON s.id = cap.student_id
|
|
WHERE c.teacher_id = ? AND s.identity_id = ? AND cap.is_active = 1 LIMIT 1",
|
|
[$request->user_id, $otherIdentityId]
|
|
);
|
|
}
|
|
return (bool)Database::selectOne(
|
|
"SELECT 1 FROM course_access_passes cap JOIN courses c ON c.id = cap.course_id JOIN teachers t ON t.id = c.teacher_id
|
|
WHERE cap.student_id = ? AND t.identity_id = ? AND cap.is_active = 1 LIMIT 1",
|
|
[$request->user_id, $otherIdentityId]
|
|
);
|
|
}
|
|
|
|
private function identityProfile(int $identityId): array
|
|
{
|
|
$profile = Database::selectOne("SELECT id, uuid, full_name, 'student' AS role FROM students WHERE identity_id = ? LIMIT 1", [$identityId]);
|
|
if (!$profile) $profile = Database::selectOne("SELECT id, uuid, full_name, 'teacher' AS role FROM teachers WHERE identity_id = ? LIMIT 1", [$identityId]);
|
|
return $profile ?: [];
|
|
}
|
|
|
|
public static function pushToWorkerman(string $action, int $targetIdentityId, array $payload): bool
|
|
{
|
|
$ch = curl_init('http://127.0.0.1:4241');
|
|
curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_POSTFIELDS => http_build_query(['action' => $action, 'target_user_id' => $targetIdentityId, 'payload' => json_encode($payload, JSON_UNESCAPED_UNICODE)]), CURLOPT_TIMEOUT_MS => 400, CURLOPT_RETURNTRANSFER => true]);
|
|
$ok = curl_exec($ch) !== false;
|
|
curl_close($ch);
|
|
return $ok;
|
|
}
|
|
|
|
private function uuid(): string
|
|
{
|
|
$data = random_bytes(16);
|
|
$data[6] = chr((ord($data[6]) & 0x0f) | 0x40);
|
|
$data[8] = chr((ord($data[8]) & 0x3f) | 0x80);
|
|
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
|
|
}
|
|
}
|