Files
mic-after-work-server-impl-…/src/main/java/com/lions/dev/websocket/ChatWebSocket.java
dahoud 093d04c224 feat(backend): Ajout complet des fonctionnalités Chat, Social, Story et Notifications
Implémentation complète de toutes les fonctionnalités backend :

## Nouvelles Fonctionnalités

### Chat (Messagerie Instantanée)
- Entities : Conversation, Message
- DTOs : ConversationResponseDTO, MessageResponseDTO, SendMessageRequestDTO
- Resources : MessageResource (endpoints REST)
- Services : MessageService (logique métier)
- Repositories : ConversationRepository, MessageRepository
- WebSocket : ChatWebSocket (temps réel)

### Social (Publications Sociales)
- Entities : SocialPost, SocialComment, SocialLike
- DTOs : SocialPostResponseDTO, CreateSocialPostRequestDTO
- Resources : SocialPostResource
- Services : SocialPostService
- Repositories : SocialPostRepository

### Story (Stories temporaires)
- Entities : Story, StoryView
- DTOs : StoryResponseDTO, CreateStoryRequestDTO
- Resources : StoryResource
- Services : StoryService
- Repositories : StoryRepository

### Notifications (Temps Réel)
- Entities : Notification
- DTOs : NotificationResponseDTO
- Resources : NotificationResource
- Services : NotificationService, PresenceService
- Repositories : NotificationRepository
- WebSocket : NotificationWebSocket (temps réel)

## Améliorations

### Users & Friendship
- Mise à jour UserResponseDTO avec nouveaux champs
- Amélioration FriendshipResource avec séparation demandes envoyées/reçues
- FriendSuggestionResponseDTO pour suggestions d'amis
- Optimisations dans UsersService et FriendshipService

### Events
- Améliorations EventsResource et EventService
- Optimisations EventsRepository

### Configuration
- Mise à jour application.properties
- Configuration docker-compose.yml
- Dockerfile pour développement

## Fichiers Modifiés
- .dockerignore, .gitignore
- README.md
- docker-compose.yml
- Configuration Maven wrapper
2026-01-10 10:39:58 +00:00

362 lines
12 KiB
Java

package com.lions.dev.websocket;
import com.lions.dev.dto.request.chat.SendMessageRequestDTO;
import com.lions.dev.dto.response.chat.MessageResponseDTO;
import com.lions.dev.entity.chat.Message;
import com.lions.dev.service.MessageService;
import io.quarkus.logging.Log;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.websocket.*;
import jakarta.websocket.server.PathParam;
import jakarta.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* WebSocket endpoint pour le chat en temps réel.
*
* Ce endpoint gère:
* - La connexion/déconnexion des utilisateurs
* - L'envoi et la réception de messages en temps réel
* - Les indicateurs de frappe (typing indicators)
* - Les confirmations de lecture (read receipts)
*
* URL: ws://localhost:8080/chat/ws/{userId}
*/
@ServerEndpoint("/chat/ws/{userId}")
@ApplicationScoped
public class ChatWebSocket {
@Inject
MessageService messageService;
// Map pour stocker les sessions WebSocket des utilisateurs connectés
private static final Map<UUID, Session> sessions = new ConcurrentHashMap<>();
/**
* Appelé lorsqu'un utilisateur se connecte.
*
* @param session La session WebSocket
* @param userId L'ID de l'utilisateur
*/
@OnOpen
public void onOpen(Session session, @PathParam("userId") String userId) {
try {
UUID userUUID = UUID.fromString(userId);
sessions.put(userUUID, session);
Log.info("[LOG] WebSocket ouvert pour l'utilisateur ID : " + userId);
// Envoyer un message de confirmation
sendToUser(userUUID, "{\"type\":\"connected\",\"message\":\"Connecté au chat\"}");
} catch (Exception e) {
Log.error("[ERROR] Erreur lors de la connexion WebSocket : " + e.getMessage(), e);
}
}
/**
* Appelé lorsqu'un message est reçu.
*
* @param message Le message reçu (au format JSON)
* @param userId L'ID de l'utilisateur qui envoie le message
*/
@OnMessage
public void onMessage(String message, @PathParam("userId") String userId) {
try {
Log.info("[LOG] Message reçu de l'utilisateur " + userId + " : " + message);
// Parser le message JSON
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
Map<String, Object> messageData = mapper.readValue(message, Map.class);
String type = (String) messageData.get("type");
switch (type) {
case "message":
handleChatMessage(messageData, userId);
break;
case "typing":
handleTypingIndicator(messageData, userId);
break;
case "read":
handleReadReceipt(messageData, userId);
break;
default:
Log.warn("[WARN] Type de message inconnu : " + type);
}
} catch (Exception e) {
Log.error("[ERROR] Erreur lors du traitement du message : " + e.getMessage(), e);
}
}
/**
* Appelé lorsqu'une erreur se produit.
*
* @param session La session WebSocket
* @param error L'erreur
*/
@OnError
public void onError(Session session, Throwable error) {
Log.error("[ERROR] Erreur WebSocket : " + error.getMessage(), error);
}
/**
* Appelé lorsqu'un utilisateur se déconnecte.
*
* @param session La session WebSocket
* @param userId L'ID de l'utilisateur
*/
@OnClose
public void onClose(Session session, @PathParam("userId") String userId) {
try {
UUID userUUID = UUID.fromString(userId);
sessions.remove(userUUID);
Log.info("[LOG] WebSocket fermé pour l'utilisateur ID : " + userId);
} catch (Exception e) {
Log.error("[ERROR] Erreur lors de la fermeture WebSocket : " + e.getMessage(), e);
}
}
/**
* Gère l'envoi d'un message de chat.
*/
private void handleChatMessage(Map<String, Object> messageData, String senderId) {
try {
UUID senderUUID = UUID.fromString(senderId);
UUID recipientUUID = UUID.fromString((String) messageData.get("recipientId"));
String content = (String) messageData.get("content");
String messageType = messageData.getOrDefault("messageType", "text").toString();
String mediaUrl = (String) messageData.get("mediaUrl");
// Enregistrer le message dans la base de données
Message message = messageService.sendMessage(
senderUUID,
recipientUUID,
content,
messageType,
mediaUrl
);
// Créer le DTO de réponse
MessageResponseDTO response = new MessageResponseDTO(message);
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
String responseJson = mapper.writeValueAsString(Map.of(
"type", "message",
"data", response
));
// Envoyer le message au destinataire s'il est connecté
sendToUser(recipientUUID, responseJson);
// Envoyer une confirmation à l'expéditeur
sendToUser(senderUUID, responseJson);
Log.info("[LOG] Message envoyé de " + senderId + " à " + recipientUUID);
} catch (Exception e) {
Log.error("[ERROR] Erreur lors de l'envoi du message : " + e.getMessage(), e);
}
}
/**
* Gère les indicateurs de frappe.
*/
private void handleTypingIndicator(Map<String, Object> messageData, String userId) {
try {
UUID recipientUUID = UUID.fromString((String) messageData.get("recipientId"));
boolean isTyping = (boolean) messageData.getOrDefault("isTyping", false);
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
String response = mapper.writeValueAsString(Map.of(
"type", "typing",
"userId", userId,
"isTyping", isTyping
));
sendToUser(recipientUUID, response);
Log.info("[LOG] Indicateur de frappe envoyé de " + userId + " à " + recipientUUID);
} catch (Exception e) {
Log.error("[ERROR] Erreur lors de l'envoi de l'indicateur de frappe : " + e.getMessage(), e);
}
}
/**
* Gère les confirmations de lecture.
*/
private void handleReadReceipt(Map<String, Object> messageData, String userId) {
try {
UUID messageUUID = UUID.fromString((String) messageData.get("messageId"));
// Marquer le message comme lu
Message message = messageService.markMessageAsRead(messageUUID);
// Notifier l'expéditeur que le message a été lu
UUID senderUUID = message.getSender().getId();
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
String response = mapper.writeValueAsString(Map.of(
"type", "read",
"messageId", messageUUID.toString(),
"readBy", userId
));
sendToUser(senderUUID, response);
Log.info("[LOG] Confirmation de lecture envoyée pour le message " + messageUUID);
} catch (Exception e) {
Log.error("[ERROR] Erreur lors de l'envoi de la confirmation de lecture : " + e.getMessage(), e);
}
}
/**
* Envoie un message à un utilisateur spécifique.
*
* @param userId L'ID de l'utilisateur
* @param message Le message à envoyer
*/
private void sendToUser(UUID userId, String message) {
Session session = sessions.get(userId);
if (session != null && session.isOpen()) {
try {
session.getAsyncRemote().sendText(message);
} catch (Exception e) {
Log.error("[ERROR] Erreur lors de l'envoi du message à l'utilisateur " + userId + " : " + e.getMessage(), e);
}
} else {
Log.warn("[WARN] Utilisateur " + userId + " non connecté ou session fermée");
}
}
/**
* Diffuse un message à tous les utilisateurs connectés.
*
* @param message Le message à diffuser
*/
public void broadcast(String message) {
sessions.values().forEach(session -> {
if (session.isOpen()) {
try {
session.getAsyncRemote().sendText(message);
} catch (Exception e) {
Log.error("[ERROR] Erreur lors de la diffusion : " + e.getMessage(), e);
}
}
});
}
/**
* Envoie un message chat à un utilisateur spécifique via WebSocket.
*
* Cette méthode est statique pour permettre son appel depuis les services
* (comme MessageService) sans nécessiter une instance de ChatWebSocket.
*
* @param userId L'ID de l'utilisateur destinataire
* @param messageData Les données du message (id, conversationId, content, etc.)
*/
public static void sendMessageToUser(UUID userId, Map<String, Object> messageData) {
Session session = sessions.get(userId);
if (session == null || !session.isOpen()) {
Log.warn("[CHAT-WS] Utilisateur " + userId + " non connecté, message non envoyé");
return;
}
try {
// Construire le message JSON au format attendu par le frontend
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
Map<String, Object> envelope = Map.of(
"type", "message",
"data", messageData
);
String json = mapper.writeValueAsString(envelope);
session.getAsyncRemote().sendText(json);
Log.info("[CHAT-WS] Message envoyé à l'utilisateur " + userId);
} catch (Exception e) {
Log.error("[CHAT-WS] Erreur lors de l'envoi du message à " + userId + " : " + e.getMessage(), e);
}
}
/**
* Envoie une confirmation de délivrance à l'expéditeur via WebSocket.
*
* Cette méthode est appelée lorsqu'un message est délivré au destinataire
* pour notifier l'expéditeur que le message a bien été reçu.
*
* @param userId L'ID de l'utilisateur (expéditeur) à notifier
* @param deliveryData Les données de confirmation (messageId, isDelivered, timestamp)
*/
public static void sendDeliveryConfirmation(UUID userId, Map<String, Object> deliveryData) {
Session session = sessions.get(userId);
if (session == null || !session.isOpen()) {
Log.warn("[CHAT-WS] Utilisateur " + userId + " non connecté, confirmation de délivrance non envoyée");
return;
}
try {
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
Map<String, Object> envelope = Map.of(
"type", "delivered",
"data", deliveryData
);
String json = mapper.writeValueAsString(envelope);
session.getAsyncRemote().sendText(json);
Log.info("[CHAT-WS] Confirmation de délivrance envoyée à l'utilisateur " + userId);
} catch (Exception e) {
Log.error("[CHAT-WS] Erreur lors de l'envoi de la confirmation de délivrance : " + e.getMessage(), e);
}
}
/**
* Envoie une confirmation de lecture à l'expéditeur via WebSocket.
*
* @param userId L'ID de l'utilisateur expéditeur
* @param readData Les données de confirmation de lecture
*/
public static void sendReadConfirmation(UUID userId, Map<String, Object> readData) {
Session session = sessions.get(userId);
if (session == null || !session.isOpen()) {
Log.warn("[CHAT-WS] Utilisateur " + userId + " non connecté, confirmation de lecture non envoyée");
return;
}
try {
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
Map<String, Object> envelope = Map.of(
"type", "read",
"data", readData
);
String json = mapper.writeValueAsString(envelope);
session.getAsyncRemote().sendText(json);
Log.info("[CHAT-WS] Confirmation de lecture envoyée à l'utilisateur " + userId);
} catch (Exception e) {
Log.error("[CHAT-WS] Erreur lors de l'envoi de la confirmation de lecture : " + e.getMessage(), e);
}
}
/**
* Récupère le nombre d'utilisateurs connectés.
*
* @return Le nombre d'utilisateurs connectés
*/
public static int getConnectedUsersCount() {
return sessions.size();
}
}