package com.lions.dev.service; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import jakarta.transaction.Transactional; import com.lions.dev.dto.request.events.EventCreateRequestDTO; import com.lions.dev.entity.events.Events; import com.lions.dev.entity.friends.Friendship; import com.lions.dev.entity.users.Users; import com.lions.dev.exception.EventNotFoundException; import com.lions.dev.exception.UserNotFoundException; import com.lions.dev.repository.EventsRepository; import com.lions.dev.repository.FriendshipRepository; import com.lions.dev.repository.UsersRepository; import com.lions.dev.repository.EstablishmentRepository; import com.lions.dev.dto.events.NotificationEvent; import org.eclipse.microprofile.reactive.messaging.Channel; import org.eclipse.microprofile.reactive.messaging.Emitter; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import java.time.LocalDateTime; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; /** * Service de gestion des événements. * * Version 2.0 - Architecture refactorée avec nommage standardisé. * Conforme à l'architecture de données AfterWork v2.0 (Ultra-Compétitive). * * Ce service contient la logique métier pour la création, récupération, mise à jour et suppression des événements. * Chaque méthode est loguée pour assurer une traçabilité exhaustive des actions effectuées. */ @ApplicationScoped public class EventService { @Inject EventsRepository eventsRepository; @Inject FriendshipRepository friendshipRepository; @Inject UsersRepository usersRepository; @Inject EstablishmentRepository establishmentRepository; // v2.0 @Inject NotificationService notificationService; @Inject @Channel("notifications") Emitter notificationEmitter; // v2.0 - Publie dans Kafka private static final Logger logger = LoggerFactory.getLogger(EventService.class); /** * Crée un nouvel événement dans le système (v2.0). * * @param eventCreateRequestDTO Le DTO contenant les informations de l'événement à créer. * @param creator L'utilisateur créateur de l'événement. * @return L'événement créé. */ public Events createEvent(EventCreateRequestDTO eventCreateRequestDTO, Users creator) { // Initialisation de l'entité Event avec les détails fournis Events event = new Events(); event.setTitle(eventCreateRequestDTO.getTitle()); event.setDescription(eventCreateRequestDTO.getDescription()); event.setStartDate(eventCreateRequestDTO.getStartDate()); event.setEndDate(eventCreateRequestDTO.getEndDate()); // v2.0 - Établissement au lieu de location if (eventCreateRequestDTO.getEstablishmentId() != null) { com.lions.dev.entity.establishment.Establishment establishment = establishmentRepository.findById(eventCreateRequestDTO.getEstablishmentId()); if (establishment != null) { event.setEstablishment(establishment); } else { logger.warn("[WARN] Établissement non trouvé avec l'ID : {}", eventCreateRequestDTO.getEstablishmentId()); } } event.setCategory(eventCreateRequestDTO.getCategory()); event.setLink(eventCreateRequestDTO.getLink()); event.setImageUrl(eventCreateRequestDTO.getImageUrl()); event.setMaxParticipants(eventCreateRequestDTO.getMaxParticipants()); event.setTags(eventCreateRequestDTO.getTags()); event.setOrganizer(eventCreateRequestDTO.getOrganizer()); event.setParticipationFee(eventCreateRequestDTO.getParticipationFee()); // v2.0 - Nouveaux champs if (eventCreateRequestDTO.getIsPrivate() != null) { event.setIsPrivate(eventCreateRequestDTO.getIsPrivate()); } if (eventCreateRequestDTO.getWaitlistEnabled() != null) { event.setWaitlistEnabled(eventCreateRequestDTO.getWaitlistEnabled()); } event.setPrivacyRules(eventCreateRequestDTO.getPrivacyRules()); event.setTransportInfo(eventCreateRequestDTO.getTransportInfo()); event.setAccommodationInfo(eventCreateRequestDTO.getAccommodationInfo()); event.setAccessibilityInfo(eventCreateRequestDTO.getAccessibilityInfo()); event.setParkingInfo(eventCreateRequestDTO.getParkingInfo()); event.setSecurityProtocol(eventCreateRequestDTO.getSecurityProtocol()); event.setCreator(creator); event.setStatus("OPEN"); // v2.0 - Statut standardisé // Persiste l'événement dans la base de données eventsRepository.persist(event); logger.info("[logger] Événement créé avec succès : {}", event.getTitle()); // Créer des notifications pour tous les amis (v2.0 - avec Kafka) try { List friendships = friendshipRepository.findFriendsByUser(creator, 0, Integer.MAX_VALUE); // v2.0 - Utiliser les nouveaux noms de champs String creatorName = creator.getFirstName() + " " + creator.getLastName(); for (Friendship friendship : friendships) { Users friend = friendship.getUser().equals(creator) ? friendship.getFriend() : friendship.getUser(); String notificationTitle = "Nouvel événement de " + creatorName; String notificationMessage = creatorName + " a créé un nouvel événement : " + event.getTitle(); // Créer notification en base notificationService.createNotification( notificationTitle, notificationMessage, "event", friend.getId(), event.getId() ); // TEMPS RÉEL: Publier dans Kafka (v2.0) try { java.util.Map notificationData = new java.util.HashMap<>(); notificationData.put("eventId", event.getId().toString()); notificationData.put("eventTitle", event.getTitle()); notificationData.put("creatorId", creator.getId().toString()); notificationData.put("creatorName", creatorName); notificationData.put("startDate", event.getStartDate().toString()); NotificationEvent kafkaEvent = new NotificationEvent( friend.getId().toString(), // userId destinataire "event_created", notificationData ); notificationEmitter.send(kafkaEvent); logger.debug("[logger] Événement event_created publié dans Kafka pour: {}", friend.getId()); } catch (Exception kafkaEx) { logger.error("[ERROR] Erreur publication Kafka pour événement {}", event.getId(), kafkaEx); // Ne pas bloquer si Kafka échoue } } logger.info("[logger] Notifications créées pour {} ami(s)", friendships.size()); } catch (Exception e) { logger.error("[ERROR] Erreur lors de la création des notifications : {}", e.getMessage()); } return event; } /** * Récupère un événement par son ID. * * @param id L'ID de l'événement. * @return L'événement trouvé. * @throws EventNotFoundException Si l'événement n'est pas trouvé. */ public Events getEventById(UUID id) { logger.info("[logger] Tentative de récupération de l'événement avec l'ID : {}", id); Events event = eventsRepository.findById(id); if (event == null) { logger.error("[ERROR] Événement non trouvé avec l'ID : {}", id); throw new EventNotFoundException(id); } logger.info("[logger] Événement trouvé avec l'ID : {}", id); return event; } /** * Récupère tous les événements après une date donnée. * * @param startDate La date de début pour filtrer les événements. * @return Une liste d'événements après cette date. */ public List getEventsAfterDate(LocalDateTime startDate) { logger.info("[logger] Récupération des événements après la date : {}", startDate); List events = eventsRepository.findEventsAfterDate(startDate); logger.info("[logger] Nombre d'événements trouvés après la date {} : {}", startDate, events.size()); return events; } /** * Supprime un événement par son ID. * * @param id L'ID de l'événement à supprimer. * @param userId L'ID de l'utilisateur qui tente de supprimer l'événement. * @return true si l'événement a été supprimé, false sinon. * @throws EventNotFoundException Si l'événement n'est pas trouvé. * @throws SecurityException Si l'utilisateur n'est pas le créateur de l'événement. */ @Transactional public boolean deleteEvent(UUID id, UUID userId) { logger.info("[logger] Tentative de suppression de l'événement avec l'ID : {} par l'utilisateur : {}", id, userId); Events event = eventsRepository.findById(id); if (event == null) { logger.warn("[logger] Échec de la suppression : événement avec l'ID {} introuvable.", id); throw new EventNotFoundException(id); } // Vérifier que l'utilisateur est le créateur if (!canModifyEvent(event, userId)) { logger.error("[ERROR] L'utilisateur {} n'a pas les permissions pour supprimer l'événement {}", userId, id); throw new SecurityException("Vous n'avez pas les permissions pour supprimer cet événement"); } boolean deleted = eventsRepository.deleteById(id); if (deleted) { logger.info("[logger] Événement avec l'ID {} supprimé avec succès.", id); } return deleted; } /** * Vérifie si un utilisateur peut modifier un événement. * * @param event L'événement à vérifier. * @param userId L'ID de l'utilisateur. * @return true si l'utilisateur peut modifier l'événement, false sinon. */ public boolean canModifyEvent(Events event, UUID userId) { if (event == null || event.getCreator() == null) { return false; } return event.getCreator().getId().equals(userId); } /** * Met à jour un événement dans le système. * * @param event L'événement contenant les détails mis à jour. * @param userId L'ID de l'utilisateur qui tente de mettre à jour l'événement. * @return L'événement mis à jour. * @throws EventNotFoundException Si l'événement n'est pas trouvé. * @throws SecurityException Si l'utilisateur n'est pas le créateur de l'événement. */ @Transactional public Events updateEvent(Events event, UUID userId) { logger.info("[logger] Tentative de mise à jour de l'événement avec l'ID : {} par l'utilisateur : {}", event.getId(), userId); Events existingEvent = eventsRepository.findById(event.getId()); if (existingEvent == null) { logger.error("[ERROR] Événement non trouvé avec l'ID : {}", event.getId()); throw new EventNotFoundException(event.getId()); } // Vérifier que l'utilisateur est le créateur if (!canModifyEvent(existingEvent, userId)) { logger.error("[ERROR] L'utilisateur {} n'a pas les permissions pour modifier l'événement {}", userId, event.getId()); throw new SecurityException("Vous n'avez pas les permissions pour modifier cet événement"); } // v2.0 - Mettre à jour les détails de l'événement existingEvent.setTitle(event.getTitle()); existingEvent.setDescription(event.getDescription()); existingEvent.setStartDate(event.getStartDate()); existingEvent.setEndDate(event.getEndDate()); // v2.0 - Établissement au lieu de location if (event.getEstablishment() != null) { existingEvent.setEstablishment(event.getEstablishment()); } existingEvent.setCategory(event.getCategory()); existingEvent.setLink(event.getLink()); existingEvent.setImageUrl(event.getImageUrl()); existingEvent.setMaxParticipants(event.getMaxParticipants()); existingEvent.setTags(event.getTags()); existingEvent.setOrganizer(event.getOrganizer()); existingEvent.setParticipationFee(event.getParticipationFee()); // v2.0 - Nouveaux champs if (event.getIsPrivate() != null) { existingEvent.setIsPrivate(event.getIsPrivate()); } if (event.getWaitlistEnabled() != null) { existingEvent.setWaitlistEnabled(event.getWaitlistEnabled()); } existingEvent.setPrivacyRules(event.getPrivacyRules()); existingEvent.setTransportInfo(event.getTransportInfo()); existingEvent.setAccommodationInfo(event.getAccommodationInfo()); existingEvent.setAccessibilityInfo(event.getAccessibilityInfo()); existingEvent.setParkingInfo(event.getParkingInfo()); existingEvent.setSecurityProtocol(event.getSecurityProtocol()); existingEvent.setStatus(event.getStatus()); // Persiste les modifications dans la base de données eventsRepository.persist(existingEvent); logger.info("[logger] Événement mis à jour avec succès : {}", existingEvent.getTitle()); return existingEvent; } /** * Récupère les événements par catégorie. * * @param category La catégorie des événements. * @return La liste des événements dans cette catégorie. */ public List findEventsByCategory(String category) { logger.info("[logger] Récupération des événements dans la catégorie : {}", category); List events = eventsRepository.find("category", category).list(); logger.info("[logger] Nombre d'événements trouvés dans la catégorie '{}' : {}", category, events.size()); return events; } /** * Recherche des événements par mot-clé dans le titre ou la description. * * @param keyword Le mot-clé à rechercher. * @return La liste des événements correspondant au mot-clé. */ public List searchEvents(String keyword) { logger.info("[logger] Recherche d'événements avec le mot-clé : {}", keyword); List events = eventsRepository.find("title like ?1 or description like ?1", "%" + keyword + "%").list(); logger.info("[logger] Nombre d'événements trouvés pour le mot-clé '{}' : {}", keyword, events.size()); return events; } /** * Récupère les événements auxquels un utilisateur participe. * * @param user L'utilisateur pour lequel récupérer les événements. * @return La liste des événements auxquels l'utilisateur participe. */ public List findEventsByUser(Users user) { return findEventsByUser(user, 0, Integer.MAX_VALUE); } /** * Récupère les événements auxquels un utilisateur participe avec pagination. * * @param user L'utilisateur pour lequel récupérer les événements. * @param page Le numéro de la page (0-indexé) * @param size La taille de la page * @return La liste paginée des événements auxquels l'utilisateur participe. */ public List findEventsByUser(Users user, int page, int size) { logger.info("[logger] Récupération des événements pour l'utilisateur avec l'ID : {} (page: {}, size: {})", user.getId(), page, size); List events = eventsRepository.find("participants", user) .page(page, size) .list(); logger.info("[logger] Nombre d'événements pour l'utilisateur avec l'ID {} : {}", user.getId(), events.size()); return events; } /** * Récupère les événements par statut. * * @param status Le statut des événements (en cours, fermé, etc.). * @return La liste des événements ayant ce statut. */ public List findEventsByStatus(String status) { logger.info("[logger] Récupération des événements avec le statut : {}", status); List events = eventsRepository.find("status", status).list(); logger.info("[logger] Nombre d'événements avec le statut '{}' : {}", status, events.size()); return events; } /** * Récupère les événements qui se déroulent entre deux dates spécifiques. * * @param startDate La date de début. * @param endDate La date de fin. * @return La liste des événements entre ces deux dates. */ public List findEventsBetweenDates(LocalDateTime startDate, LocalDateTime endDate) { logger.info("[logger] Récupération des événements entre les dates : {} et {}", startDate, endDate); // Vérifie la validité des dates fournies if (startDate == null || endDate == null || endDate.isBefore(startDate)) { logger.error("[ERROR] Dates invalides fournies : startDate={}, endDate={}", startDate, endDate); throw new IllegalArgumentException("Les dates sont invalides ou mal formatées."); } List events = eventsRepository.findEventsBetweenDates(startDate, endDate); logger.info("[logger] Nombre d'événements trouvés entre les dates : {}", events.size()); return events; } /** * Récupère les événements futurs. * * @return Une liste d'événements à venir. */ public List findUpcomingEvents() { logger.info("[logger] Récupération des événements futurs."); LocalDateTime now = LocalDateTime.now(); List events = eventsRepository.find("startDate > ?1", now).list(); logger.info("[logger] Nombre d'événements futurs trouvés : " + events.size()); return events; } /** * Récupère les événements passés. * * @return Une liste d'événements passés. */ public List findPastEvents() { logger.info("[logger] Récupération des événements passés."); LocalDateTime now = LocalDateTime.now(); List events = eventsRepository.find("endDate < ?1", now).list(); logger.info("[logger] Nombre d'événements passés trouvés : " + events.size()); return events; } /** * Récupère les événements par localisation (ville ou adresse de l'établissement). * v2.0 : plus de colonne location ; recherche sur establishment.address et establishment.city. * * @param location Fragment de localisation (ville ou adresse). * @return La liste des événements dont l'établissement matche. */ public List findEventsByLocation(String location) { logger.info("[logger] Récupération des événements pour la localisation : " + location); List events = eventsRepository.findEventsByEstablishmentLocation(location); logger.info("[logger] Nombre d'événements trouvés pour la localisation '" + location + "' : " + events.size()); return events; } /** * Récupère les événements populaires en fonction du nombre de participants. * * @return Une liste d'événements populaires. */ public List findPopularEvents() { logger.info("[logger] Récupération des événements populaires."); List events = eventsRepository.listAll().stream() .sorted((e1, e2) -> Integer.compare(e2.getNumberOfParticipants(), e1.getNumberOfParticipants())) .limit(10) .toList(); logger.info("[logger] Nombre d'événements populaires trouvés : " + events.size()); return events; } /** * Recommande des événements pour un utilisateur spécifique. * * @param user L'utilisateur pour lequel recommander des événements. * @return La liste des événements recommandés. */ public List recommendEventsForUser(Users user) { logger.info("[logger] Recommandation d'événements pour l'utilisateur : " + user.getEmail()); // v2.0 - Utiliser preferences pour preferredCategory String preferredCategory = user.getPreferredCategory(); // Méthode qui utilise preferences JSONB List events = preferredCategory != null ? eventsRepository.find("category", preferredCategory).list() : eventsRepository.findAll().list(); logger.info("[logger] Nombre d'événements recommandés pour l'utilisateur : " + events.size()); return events; } /** * Récupère les événements de l'utilisateur et de ses amis (relations d'amitié acceptées). * * @param userId L'ID de l'utilisateur * @param page Le numéro de la page (0-indexé) * @param size La taille de la page * @return Liste paginée des événements de l'utilisateur et de ses amis * @throws UserNotFoundException Si l'utilisateur n'existe pas */ public List getEventsByFriends(UUID userId, int page, int size) { logger.info("[logger] Récupération des événements des amis pour l'utilisateur ID : " + userId); Users user = usersRepository.findById(userId); if (user == null) { logger.error("[ERROR] Utilisateur non trouvé avec l'ID : " + userId); throw new UserNotFoundException("Utilisateur non trouvé avec l'ID : " + userId); } // Récupérer toutes les relations d'amitié acceptées List friendships = friendshipRepository.findFriendsByUser(user, 0, Integer.MAX_VALUE); // Extraire les IDs des amis List friendIds = friendships.stream() .map(friendship -> { // L'ami est soit dans 'user' soit dans 'friend', selon qui a initié la relation return friendship.getUser().equals(user) ? friendship.getFriend().getId() : friendship.getUser().getId(); }) .distinct() .collect(Collectors.toList()); logger.info("[logger] " + friendIds.size() + " ami(s) trouvé(s) pour l'utilisateur ID : " + userId); // Récupérer les événements de l'utilisateur et de ses amis return eventsRepository.findEventsByFriends(userId, friendIds, page, size); } }