package com.lions.dev.service; import com.lions.dev.dto.events.PresenceEvent; import com.lions.dev.entity.users.Users; import com.lions.dev.repository.UsersRepository; import io.quarkus.logging.Log; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.context.control.ActivateRequestContext; import jakarta.inject.Inject; import jakarta.transaction.Transactional; import org.eclipse.microprofile.reactive.messaging.Channel; import org.eclipse.microprofile.reactive.messaging.Emitter; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.*; /** * Service pour gérer la présence des utilisateurs (online/offline). * * Ce service gère: * - Le marquage des utilisateurs comme en ligne/hors ligne * - Le heartbeat pour maintenir le statut online * - La diffusion de la présence aux amis via Kafka → WebSocket * * Architecture v2.0: * PresenceService → Kafka Topic (presence.updates) → PresenceKafkaBridge → WebSocket → Client */ @ApplicationScoped public class PresenceService { @Inject UsersRepository usersRepository; @Inject @Channel("presence") Emitter presenceEmitter; // v2.0 - Publie dans Kafka /** * Marque un utilisateur comme en ligne et broadcast sa présence. * * @param userId L'ID de l'utilisateur */ @Transactional public void setUserOnline(UUID userId) { Users user = usersRepository.findById(userId); if (user != null) { user.updatePresence(); usersRepository.persist(user); // Broadcast présence aux autres utilisateurs broadcastPresenceToAll(userId, true, user.getLastSeen()); Log.debug("[PRESENCE] Utilisateur " + userId + " marqué online"); } } /** * Marque un utilisateur comme hors ligne. * * @param userId L'ID de l'utilisateur */ @Transactional public void setUserOffline(UUID userId) { Users user = usersRepository.findById(userId); if (user != null) { user.setOffline(); usersRepository.persist(user); // Broadcast présence aux autres utilisateurs broadcastPresenceToAll(userId, false, user.getLastSeen()); Log.debug("[PRESENCE] Utilisateur " + userId + " marqué offline"); } } /** * Met à jour le heartbeat d'un utilisateur (keep-alive). * Cette méthode est appelée depuis un thread worker, donc elle doit activer le contexte de requête. * * @param userId L'ID de l'utilisateur */ @ActivateRequestContext @Transactional public void heartbeat(UUID userId) { Users user = usersRepository.findById(userId); if (user != null) { user.updatePresence(); usersRepository.persist(user); Log.debug("[PRESENCE] Heartbeat reçu pour utilisateur " + userId); } } /** * Broadcast la présence d'un utilisateur via Kafka (v2.0). * Le PresenceKafkaBridge consommera depuis Kafka et enverra via WebSocket. * * @param userId L'ID de l'utilisateur * @param isOnline Le statut online * @param lastSeen La dernière fois que l'utilisateur était en ligne */ private void broadcastPresenceToAll(UUID userId, boolean isOnline, LocalDateTime lastSeen) { try { // Convertir LocalDateTime en timestamp (milliseconds) Long lastSeenTimestamp = lastSeen != null ? lastSeen.toInstant(ZoneOffset.UTC).toEpochMilli() : null; // Créer l'événement de présence PresenceEvent presenceEvent = new PresenceEvent( userId.toString(), isOnline ? "online" : "offline", lastSeenTimestamp ); // Publier dans Kafka (le bridge s'occupera de l'envoi WebSocket) presenceEmitter.send(presenceEvent); Log.debug("[PRESENCE] Événement de présence publié dans Kafka: " + userId + " -> " + (isOnline ? "online" : "offline")); } catch (Exception e) { Log.error("[PRESENCE] Erreur lors de la publication de présence dans Kafka", e); } } /** * Récupère le statut de présence d'un utilisateur. * * @param userId L'ID de l'utilisateur * @return Map contenant isOnline et lastSeen */ public Map getUserPresence(UUID userId) { Users user = usersRepository.findById(userId); Map presence = new HashMap<>(); if (user != null) { presence.put("userId", userId.toString()); presence.put("isOnline", user.isOnline()); presence.put("lastSeen", user.getLastSeen() != null ? user.getLastSeen().toString() : null); } return presence; } }