<?phpnamespace App\Controller\Api;use App\Entity\Core\Notifications;use App\Entity\Core\Users;use App\Entity\Cvs\Candidates;use App\Entity\Messenger\Groups;use App\Entity\Messenger\Messages;use App\Services\Dossiers;use App\Services\NotificationService;use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;use Symfony\Component\EventDispatcher\EventDispatcherInterface;use Symfony\Component\HttpFoundation\Request;use Symfony\Component\HttpFoundation\Response;use Symfony\Component\Routing\Annotation\Route;use Symfony\Component\HttpFoundation\JsonResponse;use Doctrine\ORM\EntityManagerInterface;use Knp\Component\Pager\PaginatorInterface;use Symfony\Component\Form\Extension\Core\Type\CheckboxType;use Symfony\Component\Form\Extension\Core\Type\ChoiceType;use Symfony\Component\Form\Extension\Core\Type\DateTimeType;use Symfony\Component\Form\Extension\Core\Type\EmailType;use Symfony\Component\Form\Extension\Core\Type\HiddenType;use Symfony\Component\Form\Extension\Core\Type\NumberType;use Symfony\Component\Form\Extension\Core\Type\SubmitType;use Symfony\Component\Form\Extension\Core\Type\TextareaType;use Symfony\Component\Form\Extension\Core\Type\TextType;use Symfony\Component\HttpFoundation\Session\Session;class ConversationsController extends AbstractController{ private $em; private $paginator; private $dossier; private $notificationService; public function __construct(EntityManagerInterface $em, Dossiers $dossier, PaginatorInterface $paginator, NotificationService $notificationService ) { $this->em = $em; $this->paginator = $paginator; $this->dossier = $dossier; $this->notificationService = $notificationService; } /** * Liste des conversations récentes de l'utilisateur connecté */ public function recents(Request $request): JsonResponse { $user = $this->getUser(); $limit = $request->query->get('limit', 20); $conversations = $this->em->getRepository(Groups::class)->recents($user, $limit); $array = []; foreach ($conversations as $conversation) { $otherUser = $conversation->getOtherUser($user); if (!$otherUser) { continue; } $countMessages = $this->em->getRepository(Messages::class)->countByConversation($conversation); $fullname = $otherUser->getName()." ". $otherUser->getLastname(); $avatar = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgdmlld0JveD0iMCAwIDIwMCAyMDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPGRlZnM+CiAgICA8bGluZWFyR3JhZGllbnQgaWQ9ImdyYWQiIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjEwMCUiPgogICAgICA8c3RvcCBvZmZzZXQ9IjAlIiBzdHlsZT0ic3RvcC1jb2xvcjojNjY3ZWVhO3N0b3Atb3BhY2l0eToxIiAvPgogICAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0eWxlPSJzdG9wLWNvbG9yOiM3NjRiYTI7c3RvcC1vcGFjaXR5OjEiIC8+CiAgICA8L2xpbmVhckdyYWRpZW50PgogIDwvZGVmcz4KICA8Y2lyY2xlIGN4PSIxMDAiIGN5PSIxMDAiIHI9IjEwMCIgZmlsbD0idXJsKCNncmFkKSIvPgogIDxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEwMCwgMTAwKSI+CiAgICA8Y2lyY2xlIGN4PSIwIiBjeT0iLTE1IiByPSIzNSIgZmlsbD0iI2ZmZmZmZiIgb3BhY2l0eT0iMC45Ii8+CiAgICA8cGF0aCBkPSJNIC01MCA2NSBRIC01MCAyNSAtMzUgMTAgUSAtMTUgLTUgMCAtNSBRIDE1IC01IDM1IDEwIFEgNTAgMjUgNTAgNjUgWiIgZmlsbD0iI2ZmZmZmZiIgb3BhY2l0eT0iMC45Ii8+CiAgPC9nPgo8L3N2Zz4K"; if ($otherUser->getImage()) { $avatar = $otherUser->getImageBase64(); } $array[] = [ "id" => (string)$conversation->getId(), "conversationHash" => $conversation->getConversationHash(), "title" => $conversation->getTitle() ?: 'Conversation avec ' . $fullname, "fullname" => $fullname, "userId" => $otherUser->getId(), "userEmail" => $otherUser->getEmail(), "avatar" => $avatar, "count_messages" => $countMessages, "open" => false, "lastmessage" => $conversation->getLastMessageContent() ?? '', "lastMessageAt" => $conversation->getLastMessageAt() ? $conversation->getLastMessageAt()->format('c') : null, "createdAt" => $conversation->getCreatedAt() ? $conversation->getCreatedAt()->format('c') : null, ]; } return new JsonResponse($array); } /** * Obtenir la configuration pour une conversation */ public function getConfig(int $id): JsonResponse { $user = $this->getUser(); if (!$user) { return new JsonResponse(['error' => 'Non authentifié'], Response::HTTP_UNAUTHORIZED); } $conversation = $this->em->getRepository(Groups::class)->find($id); if (!$conversation) { return new JsonResponse(['error' => 'Conversation non trouvée'], Response::HTTP_NOT_FOUND); } if (!$conversation->hasUser($user)) { return new JsonResponse(['error' => 'Accès interdit'], Response::HTTP_FORBIDDEN); } $otherUser = $conversation->getOtherUser($user); if (!$otherUser) { return new JsonResponse(['error' => 'Interlocuteur introuvable'], Response::HTTP_NOT_FOUND); } $fullname = $otherUser->getName()." ".$otherUser->getLastname(); $avatar = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgdmlld0JveD0iMCAwIDIwMCAyMDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPGRlZnM+CiAgICA8bGluZWFyR3JhZGllbnQgaWQ9ImdyYWQiIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjEwMCUiPgogICAgICA8c3RvcCBvZmZzZXQ9IjAlIiBzdHlsZT0ic3RvcC1jb2xvcjojNjY3ZWVhO3N0b3Atb3BhY2l0eToxIiAvPgogICAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0eWxlPSJzdG9wLWNvbG9yOiM3NjRiYTI7c3RvcC1vcGFjaXR5OjEiIC8+CiAgICA8L2xpbmVhckdyYWRpZW50PgogIDwvZGVmcz4KICA8Y2lyY2xlIGN4PSIxMDAiIGN5PSIxMDAiIHI9IjEwMCIgZmlsbD0idXJsKCNncmFkKSIvPgogIDxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEwMCwgMTAwKSI+CiAgICA8Y2lyY2xlIGN4PSIwIiBjeT0iLTE1IiByPSIzNSIgZmlsbD0iI2ZmZmZmZiIgb3BhY2l0eT0iMC45Ii8+CiAgICA8cGF0aCBkPSJNIC01MCA2NSBRIC01MCAyNSAtMzUgMTAgUSAtMTUgLTUgMCAtNSBRIDE1IC01IDM1IDEwIFEgNTAgMjUgNTAgNjUgWiIgZmlsbD0iI2ZmZmZmZiIgb3BhY2l0eT0iMC45Ii8+CiAgPC9nPgo8L3N2Zz4K"; if ($otherUser->getImage()) { $avatar = $otherUser->getImageBase64(); } $config = [ 'groupId' => (string) $conversation->getId(), 'userId' => $user->getId(), 'conversationHash' => $conversation->getConversationHash(), 'title' => $conversation->getTitle() ?: 'Conversation avec ' . $fullname, 'otherUser' => [ 'id' => $otherUser->getId(), 'name' => $otherUser->getName(), 'lastname' => $otherUser->getLastname(), 'fullname' => $fullname, 'email' => $otherUser->getEmail(), 'avatar' => $avatar, ], 'urls' => [ 'api' => $_ENV["WEBSOCKET_MESSENGER_WS"], 'ws' => $_ENV["WEBSOCKET_MESSENGER_WS"], ], 'createdAt' => $conversation->getCreatedAt() ? $conversation->getCreatedAt()->format('c') : null, 'lastMessageAt' => $conversation->getLastMessageAt() ? $conversation->getLastMessageAt()->format('c') : null, ]; error_log('✅ [Config] Configuration envoyée pour conversation ' . $conversation->getId()); error_log(' User ID: ' . $user->getId()); error_log(' WS URL: ' . $config['urls']['ws']); return new JsonResponse($config); } /** * Afficher les messages d'une conversation */ public function show(Request $request, int $id): JsonResponse { $user = $this->getUser(); $conversation = $this->em->getRepository(Groups::class)->find($id); if (!$conversation) { return new JsonResponse(['error' => 'Conversation non trouvée'], Response::HTTP_NOT_FOUND); } if (!$conversation->hasUser($user)) { return new JsonResponse(['error' => 'Accès interdit'], Response::HTTP_FORBIDDEN); } $limit = $request->query->get('limit', 100); $offset = $request->query->get('offset', 0); $messages = $this->em->getRepository(Messages::class)->findByConversation($conversation, $limit, $offset); $array = []; foreach ($messages as $message) { $isOwn = $message->getUser()->getId() === $user->getId(); $fullname = $message->getUser()->getName()." ". $message->getUser()->getLastname(); $avatar = $this->getDefaultAvatar(); if ($message->getUser()->getImage()) { $avatar = $message->getUser()->getImageBase64(); } $array[] = [ "id" => (string)$message->getId(), "message" => (string)$message->getMessage(), "isOwn" => $isOwn, "userId" => $message->getUser()->getId(), "userName" => $fullname, "avatar" => $avatar, "createdAt" => $message->getCreatedAt() ? $message->getCreatedAt()->format('c') : null, ]; } return new JsonResponse($array); } /** * Envoyer un message */ public function sendMessage(Request $request, int $id): JsonResponse { $user = $this->getUser(); $conversation = $this->em->getRepository(Groups::class)->find($id); if (!$conversation) { return new JsonResponse(['error' => 'Conversation non trouvée'], Response::HTTP_NOT_FOUND); } if (!$conversation->hasUser($user)) { return new JsonResponse(['error' => 'Accès interdit'], Response::HTTP_FORBIDDEN); } $data = json_decode($request->getContent(), true); $messageText = $data['message'] ?? null; if (empty($messageText)) { return new JsonResponse(['error' => 'Message vide'], Response::HTTP_BAD_REQUEST); } $emetteur = $user; $destinataire = null; if($conversation->getUserOne() !== $user) { $destinataire = $conversation->getUserOne(); } if($conversation->getUserTwo() !== $user) { $destinataire = $conversation->getUserTwo(); } // Créer le message $message = new Messages(); $message->setMessage($messageText); $message->setUser($user); $message->setGroup($conversation); $message->setCreatedAt(new \DateTime()); $this->em->persist($message); $this->em->flush(); // Mettre à jour la conversation (avec le nouveau champ lastMessageSenderId) $conversation->setLastMessageContent(substr($messageText, 0, 100)); $conversation->setLastMessageId($message->getId()); $conversation->setLastMessageAt(new \DateTime()); $conversation->setLastMessageSenderId($user->getId()); // ← NOUVEAU $this->em->persist($conversation); $this->em->flush(); // Vers le destinataire $notification = new Notifications(); $notification->setViewed(false); $notification->setOnlyAdmin(false); $notification->setTitle("message"); if($conversation->getUserOne()->getLanguage() == "fr") { $notification->setDescription("Message reçu par ".$emetteur->getName()." ".$emetteur->getLastname()); $this->notificationService->sendToUser( $destinataire, 'Nouveau message', $this->cleanMessageNotification($message->getMessage()), ['type' => 'message', 'conversationId' => $conversation->getId()], ); } else { $this->notificationService->sendToUser( $destinataire, 'New message', $this->cleanMessageNotification($message->getMessage()), ['type' => 'message', 'conversationId' => $conversation->getId()], ); $notification->setDescription("Message received by ".$emetteur->getName()." ".$emetteur->getLastname()); } $notification->setType("default"); $notification->setUser($conversation->getUserTwo()); $this->em->persist($notification); $this->em->flush(); $fullname = $user->getName()." ".$user->getLastname(); $avatar = $this->getDefaultAvatar(); if ($user->getImage()) { $avatar = $user->getImageBase64(); } try { $client = new \GuzzleHttp\Client([ 'timeout' => 5.0, 'verify' => false, ]); $websocketUrl = $_ENV["WEBSOCKET_HTTP_URL"].'/broadcast'; error_log("╔═══════════════════════════════════════════════════════╗"); error_log("║ 📡 BROADCAST WEBSOCKET - ENVOI MESSAGE ║"); error_log("╚═══════════════════════════════════════════════════════╝"); error_log("🔡 [WebSocket] URL cible : " . $websocketUrl); error_log("🔡 [WebSocket] Group ID : " . $conversation->getId()); error_log("🔡 [WebSocket] Message ID: " . $message->getId()); error_log("🔡 [WebSocket] User ID : " . $user->getId()); error_log("🔡 [WebSocket] Fullname : " . $fullname); $broadcastData = [ 'groupId' => (int) $conversation->getId(), 'message' => [ 'id' => (int) $message->getId(), 'message' => $message->getMessage(), 'isOwn' => false, 'userId' => $user->getId(), 'userName' => $fullname, 'avatar' => $avatar, 'createdAt' => $message->getCreatedAt()->format('c'), ] ]; error_log("📤 [WebSocket] Données envoyées: " . json_encode($broadcastData)); $response = $client->post($websocketUrl, [ 'json' => $broadcastData, 'headers' => [ 'Content-Type' => 'application/json', ] ]); $statusCode = $response->getStatusCode(); $body = $response->getBody()->getContents(); $bodyData = json_decode($body, true); error_log("✅ [WebSocket] Notification envoyée - Status: " . $statusCode); error_log("✅ [WebSocket] Réponse: " . $body); if (isset($bodyData['clientsNotified'])) { $clientsNotified = $bodyData['clientsNotified']; error_log("📊 [WebSocket] Clients notifiés: " . $clientsNotified); if ($clientsNotified === 0) { error_log("⚠️ [WebSocket] ATTENTION: Aucun client n'a reçu le message !"); error_log(" Raisons possibles:"); error_log(" 1. Aucun navigateur connecté à cette conversation"); error_log(" 2. Les clients ne sont pas dans la bonne room"); error_log(" 3. Les clients n'ont pas appelé joinGroup()"); } } error_log("═══════════════════════════════════════════════════════"); } catch (\GuzzleHttp\Exception\ConnectException $e) { error_log("╔═══════════════════════════════════════════════════════╗"); error_log("║ ❌ ERREUR DE CONNEXION WEBSOCKET ║"); error_log("╚═══════════════════════════════════════════════════════╝"); error_log("❌ [WebSocket] Impossible de se connecter au serveur"); error_log(" URL tentée: " . ($websocketUrl ?? 'undefined')); error_log(" Message: " . $e->getMessage()); error_log(" ⚠️ Le serveur WebSocket est-il démarré ?"); error_log(" Commande: pm2 status"); error_log("═══════════════════════════════════════════════════════"); } catch (\GuzzleHttp\Exception\RequestException $e) { error_log("╔═══════════════════════════════════════════════════════╗"); error_log("║ ❌ ERREUR REQUÊTE WEBSOCKET ║"); error_log("╚═══════════════════════════════════════════════════════╝"); error_log("❌ [WebSocket] Erreur lors de la requête"); error_log(" Message: " . $e->getMessage()); if ($e->hasResponse()) { error_log(" Status: " . $e->getResponse()->getStatusCode()); error_log(" Body: " . $e->getResponse()->getBody()->getContents()); } error_log("═══════════════════════════════════════════════════════"); } catch (\Exception $e) { error_log("╔═══════════════════════════════════════════════════════╗"); error_log("║ ❌ ERREUR GÉNÉRALE WEBSOCKET ║"); error_log("╚═══════════════════════════════════════════════════════╝"); error_log("❌ [WebSocket] Erreur: " . $e->getMessage()); error_log(" Type: " . get_class($e)); error_log(" File: " . $e->getFile() . ":" . $e->getLine()); error_log("⚠️ Le message a été enregistré mais la notification temps réel n'a pas pu être envoyée"); error_log("═══════════════════════════════════════════════════════"); } return new JsonResponse([ 'id' => (string)$message->getId(), 'message' => $message->getMessage(), 'isOwn' => true, 'userId' => $user->getId(), 'userName' => $fullname, 'avatar' => $avatar, 'createdAt' => $message->getCreatedAt() ? $message->getCreatedAt()->format('c') : null, ], Response::HTTP_CREATED); } /** * Récupérer les nouveaux messages depuis un certain ID (polling) */ public function getNewMessages(Request $request, int $id): JsonResponse { $user = $this->getUser(); $conversation = $this->em->getRepository(Groups::class)->find($id); if (!$conversation) { return new JsonResponse(['error' => 'Conversation non trouvée'], Response::HTTP_NOT_FOUND); } if (!$conversation->hasUser($user)) { return new JsonResponse(['error' => 'Accès interdit'], Response::HTTP_FORBIDDEN); } $lastMessageId = $request->query->get('lastMessageId', 0); $messages = $this->em->getRepository(Messages::class)->findAfterMessage($conversation, $lastMessageId); $array = []; foreach ($messages as $message) { $isOwn = $message->getUser()->getId() === $user->getId(); $fullname = $message->getUser()->getName()." ".$message->getUser()->getLastname(); $avatar = $this->getDefaultAvatar(); if ($message->getUser()->getImage()) { $avatar = $message->getUser()->getImageBase64(); } $array[] = [ "id" => (string)$message->getId(), "message" => (string)$message->getMessage(), "isOwn" => $isOwn, "userId" => $message->getUser()->getId(), "userName" => $fullname, "avatar" => $avatar, "createdAt" => $message->getCreatedAt() ? $message->getCreatedAt()->format('c') : null, ]; } return new JsonResponse($array); } /** * Rechercher dans les conversations */ public function search(Request $request): JsonResponse { $user = $this->getUser(); $searchTerm = $request->query->get('q', ''); if (empty(trim($searchTerm))) { return new JsonResponse(['error' => 'Terme de recherche requis'], Response::HTTP_BAD_REQUEST); } $conversations = $this->em->getRepository(Groups::class)->searchConversations($user, $searchTerm); $array = []; foreach ($conversations as $conversation) { $otherUser = $conversation->getOtherUser($user); if (!$otherUser) { continue; } $countMessages = $this->em->getRepository(Messages::class)->countByConversation($conversation); $fullname = $otherUser->getName()." ".$otherUser->getLastname(); $avatar = $this->getDefaultAvatar(); if ($otherUser->getImage()) { $avatar = $otherUser->getImageBase64(); } $array[] = [ "id" => (string)$conversation->getId(), "conversationHash" => $conversation->getConversationHash(), "title" => $conversation->getTitle() ?: 'Conversation avec ' . $fullname, "fullname" => $fullname, "userId" => $otherUser->getId(), "avatar" => $avatar, "count_messages" => $countMessages, "lastmessage" => $conversation->getLastMessageContent() ?? '', "lastMessageAt" => $conversation->getLastMessageAt() ? $conversation->getLastMessageAt()->format('c') : null, ]; } return new JsonResponse($array); } /** * Supprimer une conversation (soft delete ou hard delete selon votre besoin) */ public function deleteConversation(int $id): JsonResponse { $user = $this->getUser(); $conversation = $this->em->getRepository(Groups::class)->find($id); if (!$conversation) { return new JsonResponse(['error' => 'Conversation non trouvée'], Response::HTTP_NOT_FOUND); } if (!$conversation->hasUser($user)) { return new JsonResponse(['error' => 'Accès interdit'], Response::HTTP_FORBIDDEN); } return new JsonResponse(['message' => 'Conversation supprimée avec succès']); } /** * Démarrer une conversation */ public function startConversation(Request $request, int $userId): JsonResponse { $user = $this->getUser(); if (!$user) { return new JsonResponse(['error' => 'Non authentifié'], Response::HTTP_UNAUTHORIZED); } $otherUser = $this->em->getRepository(Users::class)->find($userId); if (!$otherUser) { return new JsonResponse(['error' => 'Utilisateur non trouvé'], Response::HTTP_NOT_FOUND); } if ($user->getId() === $otherUser->getId()) { return new JsonResponse(['error' => 'Impossible de démarrer une conversation avec soi-même'], Response::HTTP_BAD_REQUEST); } $conversation = $this->em->getRepository(Groups::class)->findOrCreateConversation($user, $otherUser); $fullname = $otherUser->getName() . " " . $otherUser->getLastname(); return new JsonResponse([ 'id' => (string)$conversation->getId(), 'conversationHash' => $conversation->getConversationHash(), 'fullname' => $fullname, 'userId' => $otherUser->getId(), ], Response::HTTP_CREATED); } // ======================================== // NOUVELLES MÉTHODES — V2 avec lu/non lu // ======================================== /** * Liste des conversations récentes V2 — avec lastMessageSenderId et unread_count * Utilise le vrai compteur de messages non lus (basé sur readAt) */ public function recentsV2(Request $request): JsonResponse { $user = $this->getUser(); if (!$user) { return new JsonResponse(['error' => 'Non authentifié'], Response::HTTP_UNAUTHORIZED); } $limit = $request->query->get('limit', 20); $conversations = $this->em->getRepository(Groups::class)->recents($user, $limit); $messagesRepo = $this->em->getRepository(Messages::class); $array = []; foreach ($conversations as $conversation) { $otherUser = $conversation->getOtherUser($user); if (!$otherUser) { continue; } // Compteur de messages NON LUS (messages de l'autre que je n'ai pas lus) $unreadCount = $messagesRepo->countUnreadByConversation($conversation, $user); $fullname = $otherUser->getName() . " " . $otherUser->getLastname(); $avatar = $this->getDefaultAvatar(); if ($otherUser->getImage()) { $avatar = $otherUser->getImageBase64(); } $array[] = [ "id" => (string)$conversation->getId(), "conversationHash" => $conversation->getConversationHash(), "title" => $conversation->getTitle() ?: 'Conversation avec ' . $fullname, "fullname" => $fullname, "userId" => $otherUser->getId(), "userEmail" => $otherUser->getEmail(), "avatar" => $avatar, "count_messages" => $unreadCount, // ← maintenant = non lus uniquement "lastMessageSenderId" => $conversation->getLastMessageSenderId(), // ← NOUVEAU "open" => false, "lastmessage" => $conversation->getLastMessageContent() ?? '', "lastMessageAt" => $conversation->getLastMessageAt() ? $conversation->getLastMessageAt()->format('c') : null, "createdAt" => $conversation->getCreatedAt() ? $conversation->getCreatedAt()->format('c') : null, ]; } return new JsonResponse($array); } /** * Marquer tous les messages d'une conversation comme lus * Appelé quand l'utilisateur ouvre une conversation */ public function markAsRead(int $id): JsonResponse { $user = $this->getUser(); if (!$user) { return new JsonResponse(['error' => 'Non authentifié'], Response::HTTP_UNAUTHORIZED); } $conversation = $this->em->getRepository(Groups::class)->find($id); if (!$conversation) { return new JsonResponse(['error' => 'Conversation non trouvée'], Response::HTTP_NOT_FOUND); } if (!$conversation->hasUser($user)) { return new JsonResponse(['error' => 'Accès interdit'], Response::HTTP_FORBIDDEN); } // Marquer comme lus tous les messages de l'autre utilisateur $markedCount = $this->em->getRepository(Messages::class) ->markAsReadByConversation($conversation, $user); error_log("✅ [MarkAsRead] Conv {$id} — {$markedCount} messages marqués comme lus pour user {$user->getId()}"); return new JsonResponse([ 'success' => true, 'markedCount' => $markedCount, ]); } /** * Obtenir le nombre total de messages non lus (pour le badge de l'onglet Messages) */ public function totalUnread(): JsonResponse { $user = $this->getUser(); if (!$user) { return new JsonResponse(['error' => 'Non authentifié'], Response::HTTP_UNAUTHORIZED); } $totalUnread = $this->em->getRepository(Messages::class) ->countTotalUnreadForUser($user); return new JsonResponse([ 'totalUnread' => $totalUnread, ]); } // ======================================== // HELPERS PRIVÉS // ======================================== private function cleanMessageNotification($texte, $longueur = 20) { if (mb_strlen($texte) <= $longueur) { return $texte; } return mb_substr($texte, 0, $longueur) . '...'; } /** * Avatar SVG par défaut */ private function getDefaultAvatar(): string { return "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgdmlld0JveD0iMCAwIDIwMCAyMDAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPGRlZnM+CiAgICA8bGluZWFyR3JhZGllbnQgaWQ9ImdyYWQiIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjEwMCUiPgogICAgICA8c3RvcCBvZmZzZXQ9IjAlIiBzdHlsZT0ic3RvcC1jb2xvcjojNjY3ZWVhO3N0b3Atb3BhY2l0eToxIiAvPgogICAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0eWxlPSJzdG9wLWNvbG9yOiM3NjRiYTI7c3RvcC1vcGFjaXR5OjEiIC8+CiAgICA8L2xpbmVhckdyYWRpZW50PgogIDwvZGVmcz4KICA8Y2lyY2xlIGN4PSIxMDAiIGN5PSIxMDAiIHI9IjEwMCIgZmlsbD0idXJsKCNncmFkKSIvPgogIDxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDEwMCwgMTAwKSI+CiAgICA8Y2lyY2xlIGN4PSIwIiBjeT0iLTE1IiByPSIzNSIgZmlsbD0iI2ZmZmZmZiIgb3BhY2l0eT0iMC45Ii8+CiAgICA8cGF0aCBkPSJNIC01MCA2NSBRIC01MCAyNSAtMzUgMTAgUSAtMTUgLTUgMCAtNSBRIDE1IC01IDM1IDEwIFEgNTAgMjUgNTAgNjUgWiIgZmlsbD0iI2ZmZmZmZiIgb3BhY2l0eT0iMC45Ii8+CiAgPC9nPgo8L3N2Zz4K"; }}