fix(backend): Correction du système de réactions (favoris) pour les événements
This commit is contained in:
@@ -1,10 +1,16 @@
|
||||
package com.lions.dev.dto.response.events;
|
||||
|
||||
import com.lions.dev.entity.events.Events;
|
||||
import com.lions.dev.repository.UsersRepository;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* DTO pour renvoyer les informations d'un événement.
|
||||
*
|
||||
* Version 2.0 - Architecture refactorée avec nommage standardisé.
|
||||
* Conforme à l'architecture de données AfterWork v2.0 (Ultra-Compétitive).
|
||||
*
|
||||
* Ce DTO est utilisé pour structurer les données retournées dans les réponses
|
||||
* après les opérations sur les événements (création, récupération).
|
||||
*/
|
||||
@@ -16,35 +22,89 @@ public class EventCreateResponseDTO {
|
||||
private String description; // Description de l'événement
|
||||
private LocalDateTime startDate; // Date de début de l'événement
|
||||
private LocalDateTime endDate; // Date de fin de l'événement
|
||||
private String location; // Lieu de l'événement
|
||||
private String establishmentId; // v2.0 - ID de l'établissement où se déroule l'événement
|
||||
private String establishmentName; // v2.0 - Nom de l'établissement
|
||||
private String category; // Catégorie de l'événement
|
||||
private String link; // Lien vers plus d'informations
|
||||
private String imageUrl; // URL d'une image pour l'événement
|
||||
private String creatorId; // ID du créateur de l'événement
|
||||
private String creatorEmail; // Email du créateur de l'événement
|
||||
private String creatorFirstName; // Prénom du créateur de l'événement
|
||||
private String creatorLastName; // Nom de famille du création de l'événement
|
||||
private String status; // Statut de l'événement
|
||||
private String creatorFirstName; // v2.0 - Prénom du créateur de l'événement
|
||||
private String creatorLastName; // v2.0 - Nom de famille du créateur de l'événement
|
||||
private String status; // Statut de l'événement (OPEN, CLOSED, CANCELLED, COMPLETED)
|
||||
private Boolean isPrivate; // v2.0 - Indique si l'événement est privé
|
||||
private Boolean waitlistEnabled; // v2.0 - Indique si la liste d'attente est activée
|
||||
private Integer maxParticipants; // Nombre maximum de participants autorisés
|
||||
private Integer participationFee; // Frais de participation en centimes
|
||||
private Long reactionsCount; // ✅ Nombre de réactions (utilisateurs qui ont cet événement en favori)
|
||||
private Boolean isFavorite; // ✅ Indique si l'utilisateur actuel a cet événement en favori (optionnel, dépend du contexte)
|
||||
|
||||
// Champ déprécié (v1.0) - conservé pour compatibilité
|
||||
/**
|
||||
* @deprecated Utiliser {@link #establishmentId} et {@link #establishmentName} à la place.
|
||||
*/
|
||||
@Deprecated
|
||||
private String location;
|
||||
|
||||
/**
|
||||
* Constructeur qui transforme une entité Events en DTO.
|
||||
* Constructeur qui transforme une entité Events en DTO (v2.0).
|
||||
* Utilise UsersRepository pour calculer reactionsCount et isFavorite.
|
||||
*
|
||||
* @param event L'événement à convertir en DTO.
|
||||
* @param usersRepository Le repository pour compter les réactions (peut être null).
|
||||
* @param currentUserId L'ID de l'utilisateur actuel pour vérifier isFavorite (peut être null).
|
||||
*/
|
||||
public EventCreateResponseDTO(Events event) {
|
||||
public EventCreateResponseDTO(Events event, UsersRepository usersRepository, UUID currentUserId) {
|
||||
this.id = event.getId().toString();
|
||||
this.title = event.getTitle();
|
||||
this.description = event.getDescription();
|
||||
this.startDate = event.getStartDate();
|
||||
this.endDate = event.getEndDate();
|
||||
this.location = event.getLocation();
|
||||
this.category = event.getCategory();
|
||||
this.link = event.getLink();
|
||||
this.imageUrl = event.getImageUrl();
|
||||
this.creatorId = event.getCreator().getId().toString();
|
||||
this.creatorEmail = event.getCreator().getEmail();
|
||||
this.creatorFirstName = event.getCreator().getPrenoms();
|
||||
this.creatorLastName = event.getCreator().getNom();
|
||||
this.status = event.getStatus();
|
||||
this.isPrivate = event.getIsPrivate(); // v2.0
|
||||
this.waitlistEnabled = event.getWaitlistEnabled(); // v2.0
|
||||
this.maxParticipants = event.getMaxParticipants();
|
||||
this.participationFee = event.getParticipationFee();
|
||||
|
||||
// ✅ Calculer reactionsCount si usersRepository est fourni
|
||||
if (usersRepository != null) {
|
||||
this.reactionsCount = usersRepository.countUsersWithFavoriteEvent(event.getId());
|
||||
} else {
|
||||
this.reactionsCount = 0L;
|
||||
}
|
||||
|
||||
// ✅ Vérifier isFavorite si currentUserId est fourni
|
||||
if (currentUserId != null && usersRepository != null) {
|
||||
this.isFavorite = usersRepository.hasUserFavoriteEvent(currentUserId, event.getId());
|
||||
} else {
|
||||
this.isFavorite = null;
|
||||
}
|
||||
|
||||
// v2.0 - Informations sur l'établissement
|
||||
if (event.getEstablishment() != null) {
|
||||
this.establishmentId = event.getEstablishment().getId().toString();
|
||||
this.establishmentName = event.getEstablishment().getName();
|
||||
this.location = event.getLocation(); // Méthode qui retourne l'adresse de l'établissement
|
||||
}
|
||||
|
||||
// v2.0 - Informations sur le créateur
|
||||
if (event.getCreator() != null) {
|
||||
this.creatorId = event.getCreator().getId().toString();
|
||||
this.creatorEmail = event.getCreator().getEmail();
|
||||
this.creatorFirstName = event.getCreator().getFirstName(); // v2.0
|
||||
this.creatorLastName = event.getCreator().getLastName(); // v2.0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructeur simplifié sans calcul de réactions (pour compatibilité).
|
||||
*
|
||||
* @param event L'événement à convertir en DTO.
|
||||
*/
|
||||
public EventCreateResponseDTO(Events event) {
|
||||
this(event, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.lions.dev.entity.users;
|
||||
import com.lions.dev.entity.BaseEntity;
|
||||
import com.lions.dev.entity.events.Events;
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.persistence.JoinTable;
|
||||
import jakarta.persistence.ManyToMany;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import lombok.Getter;
|
||||
@@ -13,6 +15,10 @@ import at.favre.lib.crypto.bcrypt.BCrypt;
|
||||
|
||||
/**
|
||||
* Représentation de l'entité Utilisateur dans le système AfterWork.
|
||||
*
|
||||
* Version 2.0 - Architecture refactorée avec nommage standardisé.
|
||||
* Conforme à l'architecture de données AfterWork v2.0 (Ultra-Compétitive).
|
||||
*
|
||||
* Cette entité contient les informations de base sur un utilisateur, telles que le nom,
|
||||
* les prénoms, l'email, le mot de passe haché, et son rôle.
|
||||
*
|
||||
@@ -26,17 +32,17 @@ import at.favre.lib.crypto.bcrypt.BCrypt;
|
||||
@ToString
|
||||
public class Users extends BaseEntity {
|
||||
|
||||
@Column(name = "nom", nullable = false, length = 100)
|
||||
private String nom; // Le nom de l'utilisateur
|
||||
@Column(name = "first_name", nullable = false, length = 100)
|
||||
private String firstName; // Le prénom de l'utilisateur (v2.0)
|
||||
|
||||
@Column(name = "prenoms", nullable = false, length = 100)
|
||||
private String prenoms; // Les prénoms de l'utilisateur
|
||||
@Column(name = "last_name", nullable = false, length = 100)
|
||||
private String lastName; // Le nom de famille de l'utilisateur (v2.0)
|
||||
|
||||
@Column(name = "email", nullable = false, unique = true, length = 100)
|
||||
private String email; // L'adresse email unique de l'utilisateur
|
||||
|
||||
@Column(name = "mot_de_passe", nullable = false)
|
||||
private String motDePasse; // Mot de passe haché avec BCrypt
|
||||
@Column(name = "password_hash", nullable = false)
|
||||
private String passwordHash; // Mot de passe haché avec BCrypt (v2.0)
|
||||
|
||||
@Column(name = "role", nullable = false)
|
||||
private String role; // Le rôle de l'utilisateur (ADMIN, MODERATOR, USER, etc.)
|
||||
@@ -44,8 +50,15 @@ public class Users extends BaseEntity {
|
||||
@Column(name = "profile_image_url")
|
||||
private String profileImageUrl; // L'URL de l'image de profil de l'utilisateur
|
||||
|
||||
@Column(name = "preferred_category")
|
||||
private String preferredCategory; // La catégorie préférée de l'utilisateur
|
||||
@Column(name = "bio", length = 500)
|
||||
private String bio; // Biographie courte de l'utilisateur (v2.0)
|
||||
|
||||
@Column(name = "loyalty_points", nullable = false)
|
||||
private Integer loyaltyPoints = 0; // Points de fidélité accumulés (v2.0)
|
||||
|
||||
@Column(name = "preferences", nullable = false)
|
||||
@org.hibernate.annotations.JdbcTypeCode(org.hibernate.type.SqlTypes.JSON)
|
||||
private java.util.Map<String, Object> preferences = new java.util.HashMap<>(); // Préférences utilisateur en JSON (v2.0)
|
||||
|
||||
@Column(name = "is_verified", nullable = false)
|
||||
private boolean isVerified = false; // Indique si l'utilisateur est vérifié (compte officiel)
|
||||
@@ -60,28 +73,57 @@ public class Users extends BaseEntity {
|
||||
// private static final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
|
||||
|
||||
/**
|
||||
* Hache le mot de passe avec BCrypt et le stocke dans l'attribut `motDePasse`.
|
||||
* Hache le mot de passe avec BCrypt et le stocke dans l'attribut `passwordHash`.
|
||||
* Version 2.0 - Utilise passwordHash au lieu de motDePasse.
|
||||
*
|
||||
* @param motDePasse Le mot de passe en texte clair à hacher.
|
||||
* @param password Le mot de passe en texte clair à hacher.
|
||||
*/
|
||||
public void setMotDePasse(String motDePasse) {
|
||||
this.motDePasse = BCrypt.withDefaults().hashToString(12, motDePasse.toCharArray());
|
||||
public void setPassword(String password) {
|
||||
this.passwordHash = BCrypt.withDefaults().hashToString(12, password.toCharArray());
|
||||
System.out.println("[LOG] Mot de passe haché pour l'utilisateur : " + this.email);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie que le mot de passe fourni correspond au mot de passe haché de l'utilisateur.
|
||||
* Définit directement le hash du mot de passe (pour compatibilité).
|
||||
*
|
||||
* @param motDePasse Le mot de passe en texte clair à vérifier.
|
||||
* @param passwordHash Le hash du mot de passe.
|
||||
*/
|
||||
public void setPasswordHash(String passwordHash) {
|
||||
this.passwordHash = passwordHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie que le mot de passe fourni correspond au mot de passe haché de l'utilisateur.
|
||||
* Version 2.0 - Utilise passwordHash au lieu de motDePasse.
|
||||
*
|
||||
* @param password Le mot de passe en texte clair à vérifier.
|
||||
* @return true si le mot de passe est correct, false sinon.
|
||||
*/
|
||||
public boolean verifierMotDePasse(String motDePasse) {
|
||||
BCrypt.Result result = BCrypt.verifyer().verify(motDePasse.toCharArray(), this.motDePasse);
|
||||
public boolean verifyPassword(String password) {
|
||||
BCrypt.Result result = BCrypt.verifyer().verify(password.toCharArray(), this.passwordHash);
|
||||
boolean isValid = result.verified;
|
||||
System.out.println("[LOG] Vérification du mot de passe pour l'utilisateur : " + this.email + " - Résultat : " + isValid);
|
||||
return isValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode de compatibilité avec l'ancienne API (dépréciée).
|
||||
* @deprecated Utiliser {@link #setPassword(String)} à la place.
|
||||
*/
|
||||
@Deprecated
|
||||
public void setMotDePasse(String motDePasse) {
|
||||
setPassword(motDePasse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode de compatibilité avec l'ancienne API (dépréciée).
|
||||
* @deprecated Utiliser {@link #verifyPassword(String)} à la place.
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean verifierMotDePasse(String motDePasse) {
|
||||
return verifyPassword(motDePasse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur a le rôle d'administrateur.
|
||||
*
|
||||
@@ -93,8 +135,12 @@ public class Users extends BaseEntity {
|
||||
return isAdmin;
|
||||
}
|
||||
|
||||
@OneToMany(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "favorite_events")
|
||||
@ManyToMany(fetch = FetchType.LAZY)
|
||||
@JoinTable(
|
||||
name = "user_favorite_events",
|
||||
joinColumns = @JoinColumn(name = "user_id"),
|
||||
inverseJoinColumns = @JoinColumn(name = "event_id")
|
||||
)
|
||||
private Set<Events> favoriteEvents = new HashSet<>(); // Liste des événements favoris
|
||||
|
||||
/**
|
||||
@@ -107,6 +153,26 @@ public class Users extends BaseEntity {
|
||||
System.out.println("[LOG] Événement ajouté aux favoris pour l'utilisateur : " + this.email);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retire un événement des favoris de l'utilisateur.
|
||||
*
|
||||
* @param event L'événement à retirer.
|
||||
*/
|
||||
public void removeFavoriteEvent(Events event) {
|
||||
favoriteEvents.remove(event);
|
||||
System.out.println("[LOG] Événement retiré des favoris pour l'utilisateur : " + this.email);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si un événement est dans les favoris de l'utilisateur.
|
||||
*
|
||||
* @param event L'événement à vérifier.
|
||||
* @return true si l'événement est favori, false sinon.
|
||||
*/
|
||||
public boolean hasFavoriteEvent(Events event) {
|
||||
return favoriteEvents.contains(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne la liste des événements favoris de l'utilisateur.
|
||||
*
|
||||
@@ -118,25 +184,44 @@ public class Users extends BaseEntity {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne la catégorie préférée de l'utilisateur.
|
||||
* Retourne la catégorie préférée de l'utilisateur depuis preferences (v2.0).
|
||||
*
|
||||
* @return La catégorie préférée de l'utilisateur.
|
||||
* @return La catégorie préférée de l'utilisateur, ou null si non définie.
|
||||
*/
|
||||
public String getPreferredCategory() {
|
||||
System.out.println("[LOG] Récupération de la catégorie préférée pour l'utilisateur : " + this.email);
|
||||
return preferredCategory;
|
||||
if (preferences != null && preferences.containsKey("preferred_category")) {
|
||||
Object category = preferences.get("preferred_category");
|
||||
return category != null ? category.toString() : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Définit la catégorie préférée de l'utilisateur.
|
||||
* Définit la catégorie préférée de l'utilisateur dans preferences (v2.0).
|
||||
*
|
||||
* @param category La catégorie à définir.
|
||||
*/
|
||||
public void setPreferredCategory(String category) {
|
||||
this.preferredCategory = category;
|
||||
if (preferences == null) {
|
||||
preferences = new java.util.HashMap<>();
|
||||
}
|
||||
if (category != null) {
|
||||
preferences.put("preferred_category", category);
|
||||
} else {
|
||||
preferences.remove("preferred_category");
|
||||
}
|
||||
System.out.println("[LOG] Catégorie préférée définie pour l'utilisateur : " + this.email + " - Catégorie : " + category);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne le nom complet de l'utilisateur (v2.0).
|
||||
*
|
||||
* @return Le nom complet (firstName + lastName).
|
||||
*/
|
||||
public String getFullName() {
|
||||
return (firstName != null ? firstName : "") + " " + (lastName != null ? lastName : "").trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour la présence de l'utilisateur (marque comme en ligne et met à jour lastSeen).
|
||||
*/
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package com.lions.dev.repository;
|
||||
|
||||
import com.lions.dev.entity.events.Events;
|
||||
import com.lions.dev.entity.users.Users;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.PersistenceContext;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -15,6 +18,43 @@ import java.util.UUID;
|
||||
@ApplicationScoped
|
||||
public class UsersRepository implements PanacheRepositoryBase<Users, UUID> {
|
||||
|
||||
@PersistenceContext
|
||||
EntityManager entityManager;
|
||||
|
||||
/**
|
||||
* Compte le nombre d'utilisateurs qui ont un événement en favori.
|
||||
*
|
||||
* @param eventId L'ID de l'événement
|
||||
* @return Le nombre d'utilisateurs qui ont cet événement en favori
|
||||
*/
|
||||
public long countUsersWithFavoriteEvent(UUID eventId) {
|
||||
// ✅ Utiliser la table de jointure user_favorite_events
|
||||
return entityManager.createQuery(
|
||||
"SELECT COUNT(u) FROM Users u JOIN u.favoriteEvents e WHERE e.id = :eventId",
|
||||
Long.class
|
||||
)
|
||||
.setParameter("eventId", eventId)
|
||||
.getSingleResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si un utilisateur a un événement en favori.
|
||||
*
|
||||
* @param userId L'ID de l'utilisateur
|
||||
* @param eventId L'ID de l'événement
|
||||
* @return true si l'utilisateur a cet événement en favori, false sinon
|
||||
*/
|
||||
public boolean hasUserFavoriteEvent(UUID userId, UUID eventId) {
|
||||
Long count = entityManager.createQuery(
|
||||
"SELECT COUNT(u) FROM Users u JOIN u.favoriteEvents e WHERE u.id = :userId AND e.id = :eventId",
|
||||
Long.class
|
||||
)
|
||||
.setParameter("userId", userId)
|
||||
.setParameter("eventId", eventId)
|
||||
.getSingleResult();
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recherche un utilisateur par son adresse email.
|
||||
*
|
||||
|
||||
@@ -214,7 +214,6 @@ public class EventsResource {
|
||||
event.setStartDate(eventUpdateRequestDTO.getStartDate());
|
||||
event.setEndDate(eventUpdateRequestDTO.getEndDate());
|
||||
event.setDescription(eventUpdateRequestDTO.getDescription());
|
||||
event.setLocation(eventUpdateRequestDTO.getLocation());
|
||||
event.setCategory(eventUpdateRequestDTO.getCategory());
|
||||
event.setLink(eventUpdateRequestDTO.getLink());
|
||||
event.setImageUrl(eventUpdateRequestDTO.getImageUrl());
|
||||
@@ -252,9 +251,9 @@ public class EventsResource {
|
||||
List<Events> events = eventsRepository.find("creator.id IN ?1", friendIds).list();
|
||||
LOG.info("[LOG] Nombre d'événements récupérés dans la requête : " + events.size());
|
||||
|
||||
// Retourner une liste vide si aucun événement trouvé (pas d'erreur 404)
|
||||
List<EventReadManyByIdResponseDTO> responseDTOs = events.stream()
|
||||
.map(EventReadManyByIdResponseDTO::new)
|
||||
// ✅ Retourner avec reactionsCount et isFavorite pour l'utilisateur actuel
|
||||
List<EventCreateResponseDTO> responseDTOs = events.stream()
|
||||
.map(event -> new EventCreateResponseDTO(event, usersRepository, userId))
|
||||
.toList();
|
||||
return Response.ok(responseDTOs).build();
|
||||
} catch (Exception e) {
|
||||
@@ -552,9 +551,6 @@ public class EventsResource {
|
||||
case "description":
|
||||
event.setDescription(value != null ? value.toString() : null);
|
||||
break;
|
||||
case "location":
|
||||
event.setLocation(value != null ? value.toString() : null);
|
||||
break;
|
||||
case "category":
|
||||
event.setCategory(value != null ? value.toString() : null);
|
||||
break;
|
||||
@@ -762,9 +758,9 @@ public class EventsResource {
|
||||
@POST
|
||||
@Path("/{id}/favorite")
|
||||
@Transactional
|
||||
@Operation(summary = "Marquer un événement comme favori", description = "Permet à un utilisateur de marquer un événement comme favori.")
|
||||
@Operation(summary = "Toggle favori d'un événement", description = "Permet à un utilisateur d'ajouter ou retirer un événement de ses favoris (toggle).")
|
||||
public Response favoriteEvent(@PathParam("id") UUID eventId, @QueryParam("userId") UUID userId) {
|
||||
LOG.info("[LOG] Marquage de l'événement comme favori pour l'utilisateur ID : " + userId);
|
||||
LOG.info("[LOG] Toggle favori de l'événement " + eventId + " pour l'utilisateur ID : " + userId);
|
||||
|
||||
Events event = eventsRepository.findById(eventId);
|
||||
Users user = usersRepository.findById(userId);
|
||||
@@ -773,9 +769,22 @@ public class EventsResource {
|
||||
return Response.status(Response.Status.NOT_FOUND).entity("Événement ou utilisateur non trouvé.").build();
|
||||
}
|
||||
|
||||
user.addFavoriteEvent(event);
|
||||
// ✅ Toggle : ajouter si pas favori, retirer si déjà favori
|
||||
boolean wasFavorite = user.hasFavoriteEvent(event);
|
||||
if (wasFavorite) {
|
||||
user.removeFavoriteEvent(event);
|
||||
LOG.info("[LOG] Événement retiré des favoris pour l'utilisateur : " + user.getEmail());
|
||||
} else {
|
||||
user.addFavoriteEvent(event);
|
||||
LOG.info("[LOG] Événement ajouté aux favoris pour l'utilisateur : " + user.getEmail());
|
||||
}
|
||||
usersRepository.persist(user);
|
||||
return Response.ok("Événement marqué comme favori.").build();
|
||||
|
||||
// Retourner un JSON avec le statut
|
||||
Map<String, Object> response = new java.util.HashMap<>();
|
||||
response.put("isFavorite", !wasFavorite);
|
||||
response.put("message", wasFavorite ? "Événement retiré des favoris." : "Événement ajouté aux favoris.");
|
||||
return Response.ok(response).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
|
||||
Reference in New Issue
Block a user