Refactoring
This commit is contained in:
@@ -5,6 +5,7 @@ import com.lions.dev.core.errors.exceptions.EventNotFoundException;
|
||||
import com.lions.dev.core.errors.exceptions.NotFoundException;
|
||||
import com.lions.dev.core.errors.exceptions.ServerException;
|
||||
import com.lions.dev.core.errors.exceptions.UnauthorizedException;
|
||||
import com.lions.dev.exception.UserNotFoundException;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.ext.ExceptionMapper;
|
||||
import jakarta.ws.rs.ext.Provider;
|
||||
@@ -30,6 +31,9 @@ public class GlobalExceptionHandler implements ExceptionMapper<Throwable> {
|
||||
if (exception instanceof BadRequestException) {
|
||||
logger.warn("BadRequestException intercepted: " + exception.getMessage());
|
||||
return buildResponse(Response.Status.BAD_REQUEST, exception.getMessage());
|
||||
} else if (exception instanceof UserNotFoundException) {
|
||||
logger.warn("UserNotFoundException (404): " + exception.getMessage());
|
||||
return buildResponse(Response.Status.NOT_FOUND, exception.getMessage());
|
||||
} else if (exception instanceof EventNotFoundException || exception instanceof NotFoundException) {
|
||||
logger.warn("NotFoundException intercepted: " + exception.getMessage());
|
||||
return buildResponse(Response.Status.NOT_FOUND, exception.getMessage());
|
||||
|
||||
@@ -26,6 +26,8 @@ public class ConversationResponseDTO {
|
||||
private LocalDateTime lastMessageTimestamp;
|
||||
private int unreadCount;
|
||||
private boolean isTyping;
|
||||
/** Indique si le participant (l'autre utilisateur) est actuellement en ligne (WebSocket notifications). */
|
||||
private boolean participantIsOnline;
|
||||
|
||||
/**
|
||||
* Constructeur depuis une entité Conversation.
|
||||
@@ -44,6 +46,7 @@ public class ConversationResponseDTO {
|
||||
this.participantFirstName = otherUser.getFirstName();
|
||||
this.participantLastName = otherUser.getLastName();
|
||||
this.participantProfileImageUrl = otherUser.getProfileImageUrl();
|
||||
this.participantIsOnline = otherUser.isOnline();
|
||||
}
|
||||
|
||||
this.lastMessage = conversation.getLastMessageContent();
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.lions.dev.exception;
|
||||
|
||||
import jakarta.ws.rs.WebApplicationException;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
|
||||
/**
|
||||
* Exception levée lorsqu'on tente de supprimer un établissement qui a encore
|
||||
* des dépendances (événements, réservations). Renvoie une réponse HTTP 409 (Conflict).
|
||||
*/
|
||||
public class EstablishmentHasDependenciesException extends WebApplicationException {
|
||||
|
||||
public EstablishmentHasDependenciesException(String message) {
|
||||
super(message, Response.Status.CONFLICT);
|
||||
}
|
||||
}
|
||||
@@ -13,4 +13,15 @@ public class BookingRepository implements PanacheRepositoryBase<Booking, UUID> {
|
||||
public List<Booking> findByUserId(UUID userId) {
|
||||
return list("user.id", userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compte le nombre de réservations d'un établissement.
|
||||
* Utilisé pour refuser la suppression si des réservations existent (règle métier).
|
||||
*
|
||||
* @param establishmentId L'ID de l'établissement
|
||||
* @return Nombre de réservations
|
||||
*/
|
||||
public long countByEstablishmentId(UUID establishmentId) {
|
||||
return count("establishment.id", establishmentId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +109,27 @@ public class EventsRepository implements PanacheRepositoryBase<Events, UUID> {
|
||||
return list("startDate >= ?1 AND startDate <= ?2", from, to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compte le nombre d'événements d'un établissement.
|
||||
* Utilisé pour refuser la suppression si des événements existent (règle métier).
|
||||
*
|
||||
* @param establishmentId L'ID de l'établissement
|
||||
* @return Nombre d'événements
|
||||
*/
|
||||
public long countByEstablishmentId(UUID establishmentId) {
|
||||
return count("establishment.id", establishmentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime tous les événements d'un établissement (réservé à un usage interne / migration).
|
||||
*
|
||||
* @param establishmentId L'ID de l'établissement
|
||||
* @return Nombre d'événements supprimés
|
||||
*/
|
||||
public long deleteByEstablishmentId(UUID establishmentId) {
|
||||
return delete("establishment.id = ?1", establishmentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compte le nombre total de participants dans les événements ouverts et à venir
|
||||
* d'un établissement (pour calcul des places restantes).
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.lions.dev.dto.response.establishment.BusinessHoursResponseDTO;
|
||||
import com.lions.dev.dto.response.establishment.EstablishmentAmenityResponseDTO;
|
||||
import com.lions.dev.dto.response.establishment.EstablishmentResponseDTO;
|
||||
import com.lions.dev.entity.establishment.Establishment;
|
||||
import com.lions.dev.exception.EstablishmentHasDependenciesException;
|
||||
import com.lions.dev.entity.users.Users;
|
||||
import com.lions.dev.repository.BusinessHoursRepository;
|
||||
import com.lions.dev.repository.EstablishmentAmenityRepository;
|
||||
@@ -25,6 +26,7 @@ import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -367,15 +369,20 @@ public class EstablishmentResource {
|
||||
try {
|
||||
establishmentService.deleteEstablishment(id);
|
||||
return Response.noContent().build();
|
||||
} catch (EstablishmentHasDependenciesException e) {
|
||||
LOG.warn("[WARN] " + e.getMessage());
|
||||
return Response.status(Response.Status.CONFLICT)
|
||||
.entity(Map.of("message", e.getMessage()))
|
||||
.build();
|
||||
} catch (RuntimeException e) {
|
||||
LOG.error("[ERROR] " + e.getMessage());
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(e.getMessage())
|
||||
.entity(Map.of("message", e.getMessage() != null ? e.getMessage() : "Établissement non trouvé"))
|
||||
.build();
|
||||
} catch (Exception e) {
|
||||
LOG.error("[ERROR] Erreur lors de la suppression de l'établissement", e);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
.entity("Erreur lors de la suppression de l'établissement")
|
||||
.entity(Map.of("message", "Erreur lors de la suppression de l'établissement"))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.jboss.resteasy.reactive.RestForm;
|
||||
import org.jboss.resteasy.reactive.multipart.FileUpload;
|
||||
import jakarta.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -176,28 +177,50 @@ public class FileUploadResource {
|
||||
return baseUri + "/media/files/" + fileName;
|
||||
}
|
||||
|
||||
/** PNG 1x1 transparent (placeholder quand le fichier image est introuvable). */
|
||||
private static final byte[] PLACEHOLDER_IMAGE_PNG = Base64.getDecoder().decode(
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==");
|
||||
|
||||
/**
|
||||
* Indique si le nom de fichier correspond à une image (extension).
|
||||
*/
|
||||
private boolean isImageFileName(String fileName) {
|
||||
if (fileName == null) return false;
|
||||
String lower = fileName.toLowerCase();
|
||||
return lower.endsWith(".jpg") || lower.endsWith(".jpeg")
|
||||
|| lower.endsWith(".png") || lower.endsWith(".gif") || lower.endsWith(".webp");
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/files/{fileName}")
|
||||
@Produces(MediaType.APPLICATION_OCTET_STREAM)
|
||||
public Response getFile(@PathParam("fileName") String fileName) {
|
||||
try {
|
||||
java.nio.file.Path filePath = java.nio.file.Paths.get("/tmp/uploads/", fileName);
|
||||
|
||||
|
||||
if (!java.nio.file.Files.exists(filePath)) {
|
||||
LOG.warnf("Fichier non trouvé: %s", fileName);
|
||||
// Pour les images : retourner un placeholder pour que l'affichage reste correct (pas de 404)
|
||||
if (isImageFileName(fileName)) {
|
||||
return Response.ok(PLACEHOLDER_IMAGE_PNG)
|
||||
.type("image/png")
|
||||
.header("Content-Disposition", "inline; filename=\"" + fileName + "\"")
|
||||
.header("X-Placeholder", "true")
|
||||
.build();
|
||||
}
|
||||
return Response.status(Response.Status.NOT_FOUND)
|
||||
.entity(createErrorResponse("Fichier non trouvé"))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
// Déterminer le content-type
|
||||
String contentType = determineContentType(fileName);
|
||||
|
||||
|
||||
return Response.ok(filePath.toFile())
|
||||
.type(contentType)
|
||||
.header("Content-Disposition", "inline; filename=\"" + fileName + "\"")
|
||||
.build();
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.errorf(e, "Erreur lors de la récupération du fichier: %s", fileName);
|
||||
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||
|
||||
@@ -2,7 +2,10 @@ package com.lions.dev.service;
|
||||
|
||||
import com.lions.dev.entity.establishment.Establishment;
|
||||
import com.lions.dev.entity.users.Users;
|
||||
import com.lions.dev.exception.EstablishmentHasDependenciesException;
|
||||
import com.lions.dev.repository.BookingRepository;
|
||||
import com.lions.dev.repository.EstablishmentRepository;
|
||||
import com.lions.dev.repository.EventsRepository;
|
||||
import com.lions.dev.repository.UsersRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
@@ -23,6 +26,12 @@ public class EstablishmentService {
|
||||
@Inject
|
||||
EstablishmentRepository establishmentRepository;
|
||||
|
||||
@Inject
|
||||
EventsRepository eventsRepository;
|
||||
|
||||
@Inject
|
||||
BookingRepository bookingRepository;
|
||||
|
||||
@Inject
|
||||
UsersRepository usersRepository;
|
||||
|
||||
@@ -154,6 +163,19 @@ public class EstablishmentService {
|
||||
LOG.error("[ERROR] Établissement non trouvé avec l'ID : " + id);
|
||||
throw new RuntimeException("Établissement non trouvé avec l'ID : " + id);
|
||||
}
|
||||
// Règle métier : refuser la suppression si des événements ou réservations existent
|
||||
long eventsCount = eventsRepository.countByEstablishmentId(id);
|
||||
if (eventsCount > 0) {
|
||||
LOG.warn("[WARN] Impossible de supprimer l'établissement : " + eventsCount + " événement(s) associé(s)");
|
||||
throw new EstablishmentHasDependenciesException(
|
||||
"Impossible de supprimer l'établissement : des événements y sont encore associés. Annulez ou déplacez les événements avant de supprimer l'établissement.");
|
||||
}
|
||||
long bookingsCount = bookingRepository.countByEstablishmentId(id);
|
||||
if (bookingsCount > 0) {
|
||||
LOG.warn("[WARN] Impossible de supprimer l'établissement : " + bookingsCount + " réservation(s) associée(s)");
|
||||
throw new EstablishmentHasDependenciesException(
|
||||
"Impossible de supprimer l'établissement : des réservations y sont encore associées.");
|
||||
}
|
||||
establishmentRepository.delete(establishment);
|
||||
LOG.info("[LOG] Établissement supprimé avec succès : " + establishment.getName());
|
||||
}
|
||||
|
||||
@@ -10,7 +10,10 @@ import com.lions.dev.repository.NotificationRepository;
|
||||
import com.lions.dev.repository.UsersRepository;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Status;
|
||||
import jakarta.transaction.Synchronization;
|
||||
import jakarta.transaction.Transactional;
|
||||
import jakarta.transaction.TransactionManager;
|
||||
import org.eclipse.microprofile.reactive.messaging.Channel;
|
||||
import org.eclipse.microprofile.reactive.messaging.Emitter;
|
||||
import org.jboss.logging.Logger;
|
||||
@@ -43,6 +46,36 @@ public class NotificationService {
|
||||
@Channel("notifications")
|
||||
Emitter<NotificationEvent> notificationEmitter;
|
||||
|
||||
@Inject
|
||||
TransactionManager transactionManager;
|
||||
|
||||
/**
|
||||
* Envoie un événement Kafka après le commit de la transaction courante.
|
||||
* Évite "Commit invoked while multiple threads active" quand Kafka publie sur un autre thread.
|
||||
*/
|
||||
private void publishToKafkaAfterCommit(NotificationEvent event) {
|
||||
try {
|
||||
transactionManager.getTransaction().registerSynchronization(new Synchronization() {
|
||||
@Override
|
||||
public void beforeCompletion() {}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(int status) {
|
||||
if (status == Status.STATUS_COMMITTED) {
|
||||
try {
|
||||
notificationEmitter.send(event);
|
||||
logger.debug("[NotificationService] Événement publié dans Kafka après commit pour : " + event.getUserId());
|
||||
} catch (Exception e) {
|
||||
logger.warn("[NotificationService] Publication Kafka après commit : " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
logger.warn("[NotificationService] Enregistrement synchronisation JTA : " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère toutes les notifications d'un utilisateur.
|
||||
*
|
||||
@@ -124,43 +157,28 @@ public class NotificationService {
|
||||
notificationRepository.persist(notification);
|
||||
logger.info("[NotificationService] Notification créée avec succès : " + notification.getId());
|
||||
|
||||
// Publication temps réel via Kafka → WebSocket
|
||||
publishToKafka(notification, user);
|
||||
// Publication temps réel via Kafka → WebSocket après commit (évite "multiple threads active" JTA)
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("notificationId", notification.getId().toString());
|
||||
data.put("title", notification.getTitle());
|
||||
data.put("message", notification.getMessage());
|
||||
data.put("isRead", notification.isRead());
|
||||
data.put("createdAt", notification.getCreatedAt() != null
|
||||
? notification.getCreatedAt().toString() : null);
|
||||
if (notification.getEvent() != null) {
|
||||
data.put("eventId", notification.getEvent().getId().toString());
|
||||
data.put("eventTitle", notification.getEvent().getTitle());
|
||||
}
|
||||
NotificationEvent event = new NotificationEvent(
|
||||
user.getId().toString(),
|
||||
notification.getType(),
|
||||
data
|
||||
);
|
||||
publishToKafkaAfterCommit(event);
|
||||
|
||||
return notification;
|
||||
}
|
||||
|
||||
/**
|
||||
* Publie une notification dans Kafka pour livraison temps réel via WebSocket.
|
||||
*/
|
||||
private void publishToKafka(Notification notification, Users user) {
|
||||
try {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("notificationId", notification.getId().toString());
|
||||
data.put("title", notification.getTitle());
|
||||
data.put("message", notification.getMessage());
|
||||
data.put("isRead", notification.isRead());
|
||||
data.put("createdAt", notification.getCreatedAt() != null
|
||||
? notification.getCreatedAt().toString() : null);
|
||||
|
||||
if (notification.getEvent() != null) {
|
||||
data.put("eventId", notification.getEvent().getId().toString());
|
||||
data.put("eventTitle", notification.getEvent().getTitle());
|
||||
}
|
||||
|
||||
NotificationEvent event = new NotificationEvent(
|
||||
user.getId().toString(),
|
||||
notification.getType(),
|
||||
data
|
||||
);
|
||||
|
||||
notificationEmitter.send(event);
|
||||
logger.debug("[NotificationService] Notification publiée dans Kafka pour temps réel : " + notification.getId());
|
||||
} catch (Exception e) {
|
||||
logger.warn("[NotificationService] Échec publication Kafka (non bloquant) : " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marque une notification comme lue.
|
||||
*
|
||||
|
||||
@@ -85,7 +85,7 @@ public class UsersService {
|
||||
public Users updateUser(UUID id, UserCreateRequestDTO userCreateRequestDTO) {
|
||||
Users existingUser = usersRepository.findById(id);
|
||||
if (existingUser == null) {
|
||||
logger.error("Utilisateur non trouvé avec l'ID : " + id);
|
||||
logger.warn("Utilisateur non trouvé avec l'ID : " + id);
|
||||
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + id);
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ public class UsersService {
|
||||
public Users updateUserProfileImage(UUID id, String profileImageUrl) {
|
||||
Users existingUser = usersRepository.findById(id);
|
||||
if (existingUser == null) {
|
||||
logger.error("Utilisateur non trouvé avec l'ID : " + id);
|
||||
logger.warn("Utilisateur non trouvé avec l'ID : " + id);
|
||||
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + id);
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ public class UsersService {
|
||||
public Users getUserById(UUID id) {
|
||||
Users user = usersRepository.findById(id);
|
||||
if (user == null) {
|
||||
logger.error("Utilisateur non trouvé avec l'ID : " + id);
|
||||
logger.warn("Utilisateur non trouvé avec l'ID : " + id);
|
||||
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + id);
|
||||
}
|
||||
logger.debug("Utilisateur trouvé avec l'ID : " + id);
|
||||
@@ -213,7 +213,7 @@ public class UsersService {
|
||||
public void resetPassword(UUID id, String newPassword) {
|
||||
Users user = usersRepository.findById(id);
|
||||
if (user == null) {
|
||||
logger.error("Utilisateur non trouvé avec l'ID : " + id);
|
||||
logger.warn("Utilisateur non trouvé avec l'ID : " + id);
|
||||
throw new UserNotFoundException("Utilisateur non trouvé.");
|
||||
}
|
||||
|
||||
@@ -248,7 +248,7 @@ public class UsersService {
|
||||
public Users getUserByEmail(String email) {
|
||||
Optional<Users> userOptional = usersRepository.findByEmail(email);
|
||||
if (userOptional.isEmpty()) {
|
||||
logger.error("Utilisateur non trouvé avec l'email : " + email);
|
||||
logger.warn("Utilisateur non trouvé avec l'email : " + email);
|
||||
throw new UserNotFoundException("Utilisateur non trouvé avec l'email : " + email);
|
||||
}
|
||||
logger.debug("Utilisateur trouvé avec l'email : " + email);
|
||||
@@ -279,6 +279,7 @@ public class UsersService {
|
||||
public Users assignRole(UUID userId, String newRole) {
|
||||
Users user = usersRepository.findById(userId);
|
||||
if (user == null) {
|
||||
logger.warn("Utilisateur non trouvé avec l'ID : " + userId);
|
||||
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + userId);
|
||||
}
|
||||
user.setRole(newRole);
|
||||
@@ -300,6 +301,7 @@ public class UsersService {
|
||||
public Users setUserActive(UUID userId, boolean active) {
|
||||
Users user = usersRepository.findById(userId);
|
||||
if (user == null) {
|
||||
logger.warn("Utilisateur non trouvé avec l'ID : " + userId);
|
||||
throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + userId);
|
||||
}
|
||||
user.setActive(active);
|
||||
|
||||
@@ -224,13 +224,15 @@ public class ChatWebSocketNext {
|
||||
sessions.remove(userId);
|
||||
Log.debug("[CHAT-WS-NEXT] Connexion périmée supprimée pour " + userId);
|
||||
}
|
||||
Log.debug("[CHAT-WS-NEXT] Utilisateur " + userId + " non connecté (sessions actives: " + sessions.size() + ")");
|
||||
Log.info("[CHAT-WS-NEXT] Destinataire " + userId + " non connecté, message non délivré (sessions: " + sessions.size() + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
String preview = message.length() > 300 ? message.substring(0, 300) + "..." : message;
|
||||
Log.info("[CHAT-WS-NEXT] Envoi vers " + userId + " (" + message.length() + " car): " + preview);
|
||||
connection.sendText(message);
|
||||
Log.debug("[CHAT-WS-NEXT] Message envoyé à l'utilisateur: " + userId);
|
||||
Log.info("[CHAT-WS-NEXT] Message délivré à l'utilisateur: " + userId);
|
||||
} catch (Exception e) {
|
||||
Log.error("[CHAT-WS-NEXT] Erreur lors de l'envoi à " + userId + ", connexion supprimée", e);
|
||||
sessions.remove(userId);
|
||||
|
||||
@@ -11,6 +11,7 @@ import jakarta.inject.Inject;
|
||||
|
||||
import com.lions.dev.service.PresenceService;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
@@ -59,6 +60,10 @@ public class NotificationWebSocketNext {
|
||||
|
||||
// Marquer l'utilisateur comme en ligne
|
||||
presenceService.setUserOnline(userUUID);
|
||||
|
||||
// Envoyer au client la liste des utilisateurs déjà en ligne (snapshot)
|
||||
// pour qu'il affiche correctement "En ligne" sans attendre les events Kafka
|
||||
sendPresenceSnapshotTo(connection);
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.error("[WS-NEXT] UUID invalide: " + userId, e);
|
||||
@@ -216,6 +221,27 @@ public class NotificationWebSocketNext {
|
||||
" sessions sur " + totalSessions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoie à une connexion la liste de tous les utilisateurs actuellement en ligne.
|
||||
* Appelé à l'onOpen pour que le client affiche tout de suite le bon statut.
|
||||
*/
|
||||
private static void sendPresenceSnapshotTo(WebSocketConnection connection) {
|
||||
if (!connection.isOpen()) return;
|
||||
try {
|
||||
for (UUID onlineUserId : userConnections.keySet()) {
|
||||
Map<String, Object> presenceData = new HashMap<>();
|
||||
presenceData.put("userId", onlineUserId.toString());
|
||||
presenceData.put("isOnline", true);
|
||||
presenceData.put("timestamp", System.currentTimeMillis());
|
||||
String json = buildJsonMessage("presence", presenceData);
|
||||
connection.sendText(json);
|
||||
}
|
||||
Log.debug("[WS-NEXT] Snapshot présence envoyé (" + userConnections.size() + " utilisateur(s) en ligne)");
|
||||
} catch (Exception e) {
|
||||
Log.error("[WS-NEXT] Erreur envoi snapshot présence", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast une mise à jour de présence à tous les utilisateurs connectés.
|
||||
*
|
||||
|
||||
@@ -36,9 +36,9 @@ public class ChatKafkaBridge {
|
||||
public CompletionStage<Void> processChatMessage(Message<ChatMessageEvent> message) {
|
||||
try {
|
||||
ChatMessageEvent event = message.getPayload();
|
||||
|
||||
Log.debug("[CHAT-BRIDGE] Événement reçu: " + event.getEventType() +
|
||||
" de " + event.getSenderId() + " à " + event.getRecipientId());
|
||||
// Log INFO pour confirmer la consommation Kafka (diagnostic "aucun log dans kafka")
|
||||
Log.info("[CHAT-BRIDGE] Kafka consommé: type=" + event.getEventType() +
|
||||
" de " + event.getSenderId() + " vers " + event.getRecipientId());
|
||||
|
||||
UUID recipientId = UUID.fromString(event.getRecipientId());
|
||||
|
||||
|
||||
@@ -8,8 +8,10 @@ import jakarta.enterprise.context.ApplicationScoped;
|
||||
import org.eclipse.microprofile.reactive.messaging.Incoming;
|
||||
import org.eclipse.microprofile.reactive.messaging.Message;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
/**
|
||||
@@ -42,8 +44,8 @@ public class NotificationKafkaBridge {
|
||||
try {
|
||||
NotificationEvent event = message.getPayload();
|
||||
|
||||
Log.debug("[KAFKA-BRIDGE] Événement reçu: " + event.getType() +
|
||||
" pour utilisateur: " + event.getUserId());
|
||||
Log.info("[KAFKA-BRIDGE] Événement reçu: type=" + event.getType() +
|
||||
" userId=" + event.getUserId());
|
||||
|
||||
UUID userId = UUID.fromString(event.getUserId());
|
||||
|
||||
@@ -69,18 +71,26 @@ public class NotificationKafkaBridge {
|
||||
|
||||
/**
|
||||
* Construit le message JSON pour WebSocket à partir de l'événement Kafka.
|
||||
* Le champ "data" inclut "timestamp" et "type" pour compatibilité client Flutter (SystemNotification.fromJson).
|
||||
*/
|
||||
private String buildWebSocketMessage(NotificationEvent event) {
|
||||
try {
|
||||
com.fasterxml.jackson.databind.ObjectMapper mapper =
|
||||
com.fasterxml.jackson.databind.ObjectMapper mapper =
|
||||
new com.fasterxml.jackson.databind.ObjectMapper();
|
||||
|
||||
java.util.Map<String, Object> wsMessage = java.util.Map.of(
|
||||
|
||||
long ts = event.getTimestamp() != null ? event.getTimestamp() : System.currentTimeMillis();
|
||||
Map<String, Object> data = event.getData() != null
|
||||
? new HashMap<>(event.getData())
|
||||
: new HashMap<>();
|
||||
data.put("timestamp", Instant.ofEpochMilli(ts).toString());
|
||||
data.put("type", event.getType());
|
||||
|
||||
Map<String, Object> wsMessage = Map.of(
|
||||
"type", event.getType(),
|
||||
"data", event.getData() != null ? event.getData() : java.util.Map.of(),
|
||||
"timestamp", event.getTimestamp() != null ? event.getTimestamp() : System.currentTimeMillis()
|
||||
"data", data,
|
||||
"timestamp", ts
|
||||
);
|
||||
|
||||
|
||||
return mapper.writeValueAsString(wsMessage);
|
||||
} catch (Exception e) {
|
||||
Log.error("[KAFKA-BRIDGE] Erreur construction message WebSocket", e);
|
||||
|
||||
Reference in New Issue
Block a user