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
362 lines
12 KiB
Java
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();
|
|
}
|
|
}
|