diff --git a/src/main/java/com/lions/dev/dto/response/events/EventCreateResponseDTO.java b/src/main/java/com/lions/dev/dto/response/events/EventCreateResponseDTO.java index 0a0693c..87c69c0 100644 --- a/src/main/java/com/lions/dev/dto/response/events/EventCreateResponseDTO.java +++ b/src/main/java/com/lions/dev/dto/response/events/EventCreateResponseDTO.java @@ -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); } } diff --git a/src/main/java/com/lions/dev/entity/users/Users.java b/src/main/java/com/lions/dev/entity/users/Users.java index 75ed3aa..3bb87a7 100644 --- a/src/main/java/com/lions/dev/entity/users/Users.java +++ b/src/main/java/com/lions/dev/entity/users/Users.java @@ -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 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 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). */ diff --git a/src/main/java/com/lions/dev/repository/UsersRepository.java b/src/main/java/com/lions/dev/repository/UsersRepository.java index 17a143d..4099e36 100644 --- a/src/main/java/com/lions/dev/repository/UsersRepository.java +++ b/src/main/java/com/lions/dev/repository/UsersRepository.java @@ -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 { + @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. * diff --git a/src/main/java/com/lions/dev/resource/EventsResource.java b/src/main/java/com/lions/dev/resource/EventsResource.java index c2c2116..e0739ca 100644 --- a/src/main/java/com/lions/dev/resource/EventsResource.java +++ b/src/main/java/com/lions/dev/resource/EventsResource.java @@ -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 = 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 responseDTOs = events.stream() - .map(EventReadManyByIdResponseDTO::new) + // ✅ Retourner avec reactionsCount et isFavorite pour l'utilisateur actuel + List 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 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