fix(build): switch from uber-jar to fast-jar for Docker compatibility
- Change quarkus.package.type from uber-jar to fast-jar - Add EventShare entity and migration for share tracking - Add establishment capacity field - Improve event and establishment services - Add comprehensive tests Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2
pom.xml
2
pom.xml
@@ -13,7 +13,7 @@
|
|||||||
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
|
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
|
||||||
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
|
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
|
||||||
<quarkus.platform.version>3.16.3</quarkus.platform.version>
|
<quarkus.platform.version>3.16.3</quarkus.platform.version>
|
||||||
<quarkus.package.type>uber-jar</quarkus.package.type>
|
<quarkus.package.type>fast-jar</quarkus.package.type>
|
||||||
<skipITs>true</skipITs>
|
<skipITs>true</skipITs>
|
||||||
<surefire-plugin.version>3.5.0</surefire-plugin.version>
|
<surefire-plugin.version>3.5.0</surefire-plugin.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|||||||
@@ -41,6 +41,11 @@ public class EstablishmentResponseDTO {
|
|||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
private LocalDateTime updatedAt;
|
private LocalDateTime updatedAt;
|
||||||
|
|
||||||
|
/** Nombre maximum de places dans l'établissement (optionnel). */
|
||||||
|
private Integer capacity;
|
||||||
|
/** Places restantes (capacity - participants des événements ouverts/à venir). Null si capacity non défini. */
|
||||||
|
private Integer remainingPlaces;
|
||||||
|
|
||||||
// Champs dépréciés (v1.0) - conservés pour compatibilité
|
// Champs dépréciés (v1.0) - conservés pour compatibilité
|
||||||
/**
|
/**
|
||||||
* @deprecated Utiliser {@link #averageRating} à la place.
|
* @deprecated Utiliser {@link #averageRating} à la place.
|
||||||
@@ -60,12 +65,6 @@ public class EstablishmentResponseDTO {
|
|||||||
@Deprecated
|
@Deprecated
|
||||||
private String imageUrl;
|
private String imageUrl;
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Supprimé en v2.0.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
private Integer capacity;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Supprimé en v2.0 (utiliser establishment_amenities à la place).
|
* @deprecated Supprimé en v2.0 (utiliser establishment_amenities à la place).
|
||||||
*/
|
*/
|
||||||
@@ -130,14 +129,26 @@ public class EstablishmentResponseDTO {
|
|||||||
this.createdAt = establishment.getCreatedAt();
|
this.createdAt = establishment.getCreatedAt();
|
||||||
this.updatedAt = establishment.getUpdatedAt();
|
this.updatedAt = establishment.getUpdatedAt();
|
||||||
|
|
||||||
|
this.capacity = establishment.getCapacity();
|
||||||
|
this.remainingPlaces = null; // Sera renseigné via le constructeur avec occupiedPlaces si besoin
|
||||||
|
|
||||||
// Compatibilité v1.0 - valeurs null pour les champs dépréciés
|
// Compatibilité v1.0 - valeurs null pour les champs dépréciés
|
||||||
this.rating = null;
|
this.rating = null;
|
||||||
this.email = null;
|
this.email = null;
|
||||||
this.imageUrl = null;
|
this.imageUrl = null;
|
||||||
this.capacity = null;
|
|
||||||
this.amenities = null;
|
this.amenities = null;
|
||||||
this.openingHours = null;
|
this.openingHours = null;
|
||||||
this.totalRatingsCount = this.totalReviewsCount; // Alias pour compatibilité
|
this.totalRatingsCount = this.totalReviewsCount; // Alias pour compatibilité
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructeur avec calcul des places restantes (capacity - participants des événements ouverts/à venir).
|
||||||
|
*/
|
||||||
|
public EstablishmentResponseDTO(Establishment establishment, Integer occupiedPlaces) {
|
||||||
|
this(establishment);
|
||||||
|
if (this.capacity != null && occupiedPlaces != null) {
|
||||||
|
this.remainingPlaces = Math.max(0, this.capacity - occupiedPlaces);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ public class EventCreateResponseDTO {
|
|||||||
private Boolean waitlistEnabled; // v2.0 - Indique si la liste d'attente est activée
|
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 maxParticipants; // Nombre maximum de participants autorisés
|
||||||
private Integer participationFee; // Frais de participation en centimes
|
private Integer participationFee; // Frais de participation en centimes
|
||||||
|
private Integer participantsCount; // ✅ Nombre actuel de participants (event.getParticipants().size())
|
||||||
|
private Integer commentsCount; // ✅ Nombre de commentaires (event.getComments().size())
|
||||||
|
private Integer sharesCount; // ✅ Nombre de partages (event.getShares().size())
|
||||||
private Long reactionsCount; // ✅ Nombre de réactions (utilisateurs qui ont cet événement en favori)
|
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)
|
private Boolean isFavorite; // ✅ Indique si l'utilisateur actuel a cet événement en favori (optionnel, dépend du contexte)
|
||||||
|
|
||||||
@@ -68,6 +71,9 @@ public class EventCreateResponseDTO {
|
|||||||
this.waitlistEnabled = event.getWaitlistEnabled(); // v2.0
|
this.waitlistEnabled = event.getWaitlistEnabled(); // v2.0
|
||||||
this.maxParticipants = event.getMaxParticipants();
|
this.maxParticipants = event.getMaxParticipants();
|
||||||
this.participationFee = event.getParticipationFee();
|
this.participationFee = event.getParticipationFee();
|
||||||
|
this.participantsCount = event.getParticipants() != null ? event.getParticipants().size() : 0;
|
||||||
|
this.commentsCount = event.getComments() != null ? event.getComments().size() : 0;
|
||||||
|
this.sharesCount = event.getShares() != null ? event.getShares().size() : 0;
|
||||||
|
|
||||||
// ✅ Calculer reactionsCount si usersRepository est fourni
|
// ✅ Calculer reactionsCount si usersRepository est fourni
|
||||||
if (usersRepository != null) {
|
if (usersRepository != null) {
|
||||||
|
|||||||
@@ -72,6 +72,10 @@ public class Establishment extends BaseEntity {
|
|||||||
@Column(name = "longitude")
|
@Column(name = "longitude")
|
||||||
private Double longitude; // Longitude pour la géolocalisation
|
private Double longitude; // Longitude pour la géolocalisation
|
||||||
|
|
||||||
|
/** Nombre maximum de places dans l'établissement (optionnel). Utilisé pour le calcul des places restantes. */
|
||||||
|
@Column(name = "capacity")
|
||||||
|
private Integer capacity;
|
||||||
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY)
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
@JoinColumn(name = "manager_id", nullable = false)
|
@JoinColumn(name = "manager_id", nullable = false)
|
||||||
private Users manager; // Le responsable de l'établissement
|
private Users manager; // Le responsable de l'établissement
|
||||||
|
|||||||
41
src/main/java/com/lions/dev/entity/events/EventShare.java
Normal file
41
src/main/java/com/lions/dev/entity/events/EventShare.java
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package com.lions.dev.entity.events;
|
||||||
|
|
||||||
|
import com.lions.dev.entity.BaseEntity;
|
||||||
|
import com.lions.dev.entity.users.Users;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enregistrement d'un partage d'événement par un utilisateur.
|
||||||
|
* Chaque partage est lié à un utilisateur et à un événement.
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "event_shares")
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@ToString
|
||||||
|
public class EventShare extends BaseEntity {
|
||||||
|
|
||||||
|
@Column(name = "shared_at", nullable = false)
|
||||||
|
private LocalDateTime sharedAt;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "user_id", nullable = false)
|
||||||
|
private Users user;
|
||||||
|
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "event_id", nullable = false)
|
||||||
|
private Events event;
|
||||||
|
|
||||||
|
public EventShare(Users user, Events event) {
|
||||||
|
this.user = user;
|
||||||
|
this.event = event;
|
||||||
|
this.sharedAt = LocalDateTime.now();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -179,6 +179,9 @@ public class Events extends BaseEntity {
|
|||||||
@OneToMany(fetch = FetchType.LAZY, mappedBy = "event")
|
@OneToMany(fetch = FetchType.LAZY, mappedBy = "event")
|
||||||
private List<Comment> comments; // Liste des commentaires associés à l'événement
|
private List<Comment> comments; // Liste des commentaires associés à l'événement
|
||||||
|
|
||||||
|
@OneToMany(fetch = FetchType.LAZY, mappedBy = "event", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||||
|
private List<EventShare> shares = new java.util.ArrayList<>(); // Partages de l'événement
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retourne la liste des commentaires associés à cet événement.
|
* Retourne la liste des commentaires associés à cet événement.
|
||||||
*
|
*
|
||||||
@@ -188,4 +191,11 @@ public class Events extends BaseEntity {
|
|||||||
return comments;
|
return comments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne la liste des partages de cet événement.
|
||||||
|
*/
|
||||||
|
public List<EventShare> getShares() {
|
||||||
|
return shares != null ? shares : new java.util.ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.lions.dev.repository;
|
||||||
|
|
||||||
|
import com.lions.dev.entity.events.EventShare;
|
||||||
|
import com.lions.dev.entity.events.Events;
|
||||||
|
import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
|
||||||
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repository pour l'entité EventShare (partages d'événements).
|
||||||
|
*/
|
||||||
|
@ApplicationScoped
|
||||||
|
public class EventShareRepository implements PanacheRepositoryBase<EventShare, UUID> {
|
||||||
|
}
|
||||||
@@ -108,4 +108,16 @@ public class EventsRepository implements PanacheRepositoryBase<Events, UUID> {
|
|||||||
public List<Events> findEventsStartingBetween(LocalDateTime from, LocalDateTime to) {
|
public List<Events> findEventsStartingBetween(LocalDateTime from, LocalDateTime to) {
|
||||||
return list("startDate >= ?1 AND startDate <= ?2", from, to);
|
return list("startDate >= ?1 AND startDate <= ?2", from, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compte le nombre total de participants dans les événements ouverts et à venir
|
||||||
|
* d'un établissement (pour calcul des places restantes).
|
||||||
|
*/
|
||||||
|
public long countParticipantsForEstablishment(UUID establishmentId) {
|
||||||
|
List<Events> events = find("establishment.id = ?1 and status = ?2 and startDate >= ?3",
|
||||||
|
establishmentId, "OPEN", LocalDateTime.now()).list();
|
||||||
|
return events.stream()
|
||||||
|
.mapToLong(e -> e.getParticipants() != null ? e.getParticipants().size() : 0L)
|
||||||
|
.sum();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import com.lions.dev.entity.users.Users;
|
|||||||
import com.lions.dev.repository.BusinessHoursRepository;
|
import com.lions.dev.repository.BusinessHoursRepository;
|
||||||
import com.lions.dev.repository.EstablishmentAmenityRepository;
|
import com.lions.dev.repository.EstablishmentAmenityRepository;
|
||||||
import com.lions.dev.repository.EstablishmentRepository;
|
import com.lions.dev.repository.EstablishmentRepository;
|
||||||
|
import com.lions.dev.repository.EventsRepository;
|
||||||
import com.lions.dev.repository.UsersRepository;
|
import com.lions.dev.repository.UsersRepository;
|
||||||
import com.lions.dev.service.EstablishmentService;
|
import com.lions.dev.service.EstablishmentService;
|
||||||
import com.lions.dev.util.UserRoles;
|
import com.lions.dev.util.UserRoles;
|
||||||
@@ -53,6 +54,9 @@ public class EstablishmentResource {
|
|||||||
@Inject
|
@Inject
|
||||||
EstablishmentAmenityRepository establishmentAmenityRepository;
|
EstablishmentAmenityRepository establishmentAmenityRepository;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
EventsRepository eventsRepository;
|
||||||
|
|
||||||
private static final Logger LOG = Logger.getLogger(EstablishmentResource.class);
|
private static final Logger LOG = Logger.getLogger(EstablishmentResource.class);
|
||||||
|
|
||||||
// *********** Création d'un établissement ***********
|
// *********** Création d'un établissement ***********
|
||||||
@@ -115,6 +119,7 @@ public class EstablishmentResource {
|
|||||||
establishment.setPriceRange(requestDTO.getPriceRange());
|
establishment.setPriceRange(requestDTO.getPriceRange());
|
||||||
establishment.setLatitude(requestDTO.getLatitude());
|
establishment.setLatitude(requestDTO.getLatitude());
|
||||||
establishment.setLongitude(requestDTO.getLongitude());
|
establishment.setLongitude(requestDTO.getLongitude());
|
||||||
|
establishment.setCapacity(requestDTO.getCapacity());
|
||||||
|
|
||||||
Establishment createdEstablishment = establishmentService.createEstablishment(establishment, requestDTO.getManagerId());
|
Establishment createdEstablishment = establishmentService.createEstablishment(establishment, requestDTO.getManagerId());
|
||||||
LOG.info("[LOG] Établissement créé avec succès : " + createdEstablishment.getName());
|
LOG.info("[LOG] Établissement créé avec succès : " + createdEstablishment.getName());
|
||||||
@@ -217,7 +222,8 @@ public class EstablishmentResource {
|
|||||||
LOG.info("[LOG] Récupération de l'établissement avec l'ID : " + id);
|
LOG.info("[LOG] Récupération de l'établissement avec l'ID : " + id);
|
||||||
try {
|
try {
|
||||||
Establishment establishment = establishmentService.getEstablishmentById(id);
|
Establishment establishment = establishmentService.getEstablishmentById(id);
|
||||||
EstablishmentResponseDTO responseDTO = new EstablishmentResponseDTO(establishment);
|
int occupiedPlaces = (int) eventsRepository.countParticipantsForEstablishment(id);
|
||||||
|
EstablishmentResponseDTO responseDTO = new EstablishmentResponseDTO(establishment, occupiedPlaces);
|
||||||
return Response.ok(responseDTO).build();
|
return Response.ok(responseDTO).build();
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
LOG.warn("[WARN] " + e.getMessage());
|
LOG.warn("[WARN] " + e.getMessage());
|
||||||
@@ -330,9 +336,11 @@ public class EstablishmentResource {
|
|||||||
establishment.setPriceRange(requestDTO.getPriceRange());
|
establishment.setPriceRange(requestDTO.getPriceRange());
|
||||||
establishment.setLatitude(requestDTO.getLatitude());
|
establishment.setLatitude(requestDTO.getLatitude());
|
||||||
establishment.setLongitude(requestDTO.getLongitude());
|
establishment.setLongitude(requestDTO.getLongitude());
|
||||||
|
establishment.setCapacity(requestDTO.getCapacity());
|
||||||
|
|
||||||
Establishment updatedEstablishment = establishmentService.updateEstablishment(id, establishment);
|
Establishment updatedEstablishment = establishmentService.updateEstablishment(id, establishment);
|
||||||
EstablishmentResponseDTO responseDTO = new EstablishmentResponseDTO(updatedEstablishment);
|
int occupiedPlaces = (int) eventsRepository.countParticipantsForEstablishment(id);
|
||||||
|
EstablishmentResponseDTO responseDTO = new EstablishmentResponseDTO(updatedEstablishment, occupiedPlaces);
|
||||||
return Response.ok(responseDTO).build();
|
return Response.ok(responseDTO).build();
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
LOG.error("[ERROR] " + e.getMessage());
|
LOG.error("[ERROR] " + e.getMessage());
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ import com.lions.dev.dto.response.events.EventUpdateResponseDTO;
|
|||||||
import com.lions.dev.dto.response.friends.FriendshipReadFriendDetailsResponseDTO;
|
import com.lions.dev.dto.response.friends.FriendshipReadFriendDetailsResponseDTO;
|
||||||
import com.lions.dev.dto.response.users.UserResponseDTO;
|
import com.lions.dev.dto.response.users.UserResponseDTO;
|
||||||
import com.lions.dev.entity.comment.Comment;
|
import com.lions.dev.entity.comment.Comment;
|
||||||
|
import com.lions.dev.entity.events.EventShare;
|
||||||
import com.lions.dev.entity.events.Events;
|
import com.lions.dev.entity.events.Events;
|
||||||
import com.lions.dev.entity.users.Users;
|
import com.lions.dev.entity.users.Users;
|
||||||
|
import com.lions.dev.repository.EventShareRepository;
|
||||||
import com.lions.dev.repository.EventsRepository;
|
import com.lions.dev.repository.EventsRepository;
|
||||||
import com.lions.dev.repository.UsersRepository;
|
import com.lions.dev.repository.UsersRepository;
|
||||||
import com.lions.dev.service.EventService;
|
import com.lions.dev.service.EventService;
|
||||||
@@ -48,6 +50,9 @@ public class EventsResource {
|
|||||||
@Inject
|
@Inject
|
||||||
EventsRepository eventsRepository;
|
EventsRepository eventsRepository;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
EventShareRepository eventShareRepository;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
UsersRepository usersRepository;
|
UsersRepository usersRepository;
|
||||||
|
|
||||||
@@ -914,10 +919,41 @@ public class EventsResource {
|
|||||||
return Response.status(Response.Status.NOT_FOUND).entity("Événement non trouvé.").build();
|
return Response.status(Response.Status.NOT_FOUND).entity("Événement non trouvé.").build();
|
||||||
}
|
}
|
||||||
|
|
||||||
String shareLink = "https://lions.dev /events/" + eventId;
|
String shareLink = "https://lions.dev/events/" + eventId;
|
||||||
return Response.ok(Map.of("shareLink", shareLink)).build();
|
return Response.ok(Map.of("shareLink", shareLink)).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/{id}/share")
|
||||||
|
@Transactional
|
||||||
|
@Operation(summary = "Enregistrer un partage d'événement", description = "Enregistre qu'un utilisateur a partagé l'événement (incrémente le compteur).")
|
||||||
|
public Response shareEvent(@PathParam("id") UUID eventId, @QueryParam("userId") UUID userId) {
|
||||||
|
LOG.info("[LOG] Partage de l'événement ID : " + eventId + " par l'utilisateur ID : " + userId);
|
||||||
|
|
||||||
|
Events event = eventsRepository.findById(eventId);
|
||||||
|
if (event == null) {
|
||||||
|
LOG.warn("[LOG] Événement non trouvé avec l'ID : " + eventId);
|
||||||
|
return Response.status(Response.Status.NOT_FOUND).entity("Événement non trouvé.").build();
|
||||||
|
}
|
||||||
|
|
||||||
|
Users user = usersRepository.findById(userId);
|
||||||
|
if (user == null) {
|
||||||
|
LOG.warn("[LOG] Utilisateur non trouvé avec l'ID : " + userId);
|
||||||
|
return Response.status(Response.Status.NOT_FOUND).entity("Utilisateur non trouvé.").build();
|
||||||
|
}
|
||||||
|
|
||||||
|
EventShare share = new EventShare(user, event);
|
||||||
|
eventShareRepository.persist(share);
|
||||||
|
event.getShares().add(share);
|
||||||
|
|
||||||
|
String shareLink = "https://lions.dev/events/" + eventId;
|
||||||
|
LOG.info("[LOG] Partage enregistré pour l'événement : " + event.getTitle());
|
||||||
|
return Response.status(Response.Status.CREATED).entity(Map.of(
|
||||||
|
"shareLink", shareLink,
|
||||||
|
"sharesCount", event.getShares().size()
|
||||||
|
)).build();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Endpoint pour fermer un événement.
|
* Endpoint pour fermer un événement.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -134,9 +134,7 @@ public class EstablishmentService {
|
|||||||
|
|
||||||
establishment.setLatitude(updatedEstablishment.getLatitude());
|
establishment.setLatitude(updatedEstablishment.getLatitude());
|
||||||
establishment.setLongitude(updatedEstablishment.getLongitude());
|
establishment.setLongitude(updatedEstablishment.getLongitude());
|
||||||
|
establishment.setCapacity(updatedEstablishment.getCapacity());
|
||||||
// v2.0 - Champs supprimés (email, imageUrl, rating, capacity, amenities, openingHours)
|
|
||||||
// Ces champs ne sont plus mis à jour car ils sont dépréciés
|
|
||||||
|
|
||||||
establishmentRepository.persist(establishment);
|
establishmentRepository.persist(establishment);
|
||||||
LOG.info("[LOG] Établissement mis à jour avec succès : " + establishment.getName());
|
LOG.info("[LOG] Établissement mis à jour avec succès : " + establishment.getName());
|
||||||
|
|||||||
@@ -18,7 +18,10 @@ import org.eclipse.microprofile.reactive.messaging.Channel;
|
|||||||
import org.eclipse.microprofile.reactive.messaging.Emitter;
|
import org.eclipse.microprofile.reactive.messaging.Emitter;
|
||||||
import jakarta.enterprise.context.ApplicationScoped;
|
import jakarta.enterprise.context.ApplicationScoped;
|
||||||
import jakarta.inject.Inject;
|
import jakarta.inject.Inject;
|
||||||
|
import jakarta.transaction.Status;
|
||||||
|
import jakarta.transaction.Synchronization;
|
||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
|
import jakarta.transaction.TransactionManager;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@@ -46,8 +49,38 @@ public class FriendshipService {
|
|||||||
@Channel("notifications")
|
@Channel("notifications")
|
||||||
Emitter<NotificationEvent> notificationEmitter; // v2.0 - Publie dans Kafka
|
Emitter<NotificationEvent> notificationEmitter; // v2.0 - Publie dans Kafka
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
TransactionManager transactionManager;
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(FriendshipService.class);
|
private static final Logger logger = Logger.getLogger(FriendshipService.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 sendToKafkaAfterCommit(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.info("[LOG] Événement publié dans Kafka après commit pour : " + event.getUserId());
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("[ERROR] Publication Kafka après commit : " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("[ERROR] Enregistrement synchronisation JTA : " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Envoie une demande d'amitié entre deux utilisateurs.
|
* Envoie une demande d'amitié entre deux utilisateurs.
|
||||||
*
|
*
|
||||||
@@ -105,26 +138,22 @@ public class FriendshipService {
|
|||||||
logger.error("[ERROR] Erreur création notification demande d'amitié : " + e.getMessage());
|
logger.error("[ERROR] Erreur création notification demande d'amitié : " + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
// TEMPS RÉEL: Publier dans Kafka (v2.0)
|
// TEMPS RÉEL: Publier dans Kafka après commit (évite "multiple threads active" JTA)
|
||||||
try {
|
try {
|
||||||
Map<String, Object> notificationData = new HashMap<>();
|
Map<String, Object> notificationData = new HashMap<>();
|
||||||
notificationData.put("requestId", friendship.getId().toString());
|
notificationData.put("requestId", friendship.getId().toString());
|
||||||
notificationData.put("senderId", user.getId().toString());
|
notificationData.put("senderId", user.getId().toString());
|
||||||
// v2.0 - Utiliser les nouveaux noms de champs
|
|
||||||
notificationData.put("senderName", user.getFirstName() + " " + user.getLastName());
|
notificationData.put("senderName", user.getFirstName() + " " + user.getLastName());
|
||||||
notificationData.put("senderProfileImage", user.getProfileImageUrl() != null ? user.getProfileImageUrl() : "");
|
notificationData.put("senderProfileImage", user.getProfileImageUrl() != null ? user.getProfileImageUrl() : "");
|
||||||
|
|
||||||
NotificationEvent event = new NotificationEvent(
|
NotificationEvent event = new NotificationEvent(
|
||||||
friend.getId().toString(), // userId destinataire (clé Kafka)
|
friend.getId().toString(),
|
||||||
"friend_request_received",
|
"friend_request_received",
|
||||||
notificationData
|
notificationData
|
||||||
);
|
);
|
||||||
|
sendToKafkaAfterCommit(event);
|
||||||
notificationEmitter.send(event);
|
|
||||||
logger.info("[LOG] Événement friend_request_received publié dans Kafka pour : " + friend.getId());
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("[ERROR] Erreur lors de la publication dans Kafka : " + e.getMessage(), e);
|
logger.error("[ERROR] Préparation publication Kafka : " + e.getMessage(), e);
|
||||||
// Ne pas bloquer la demande d'amitié si Kafka échoue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("[LOG] Demande d'amitié envoyée avec succès.");
|
logger.info("[LOG] Demande d'amitié envoyée avec succès.");
|
||||||
@@ -168,30 +197,20 @@ public class FriendshipService {
|
|||||||
// Log de succès
|
// Log de succès
|
||||||
logger.info(String.format("[LOG] Demande d'amitié acceptée avec succès pour l'ID: %s", friendshipId)); // Correctement formaté
|
logger.info(String.format("[LOG] Demande d'amitié acceptée avec succès pour l'ID: %s", friendshipId)); // Correctement formaté
|
||||||
|
|
||||||
// TEMPS RÉEL: Publier dans Kafka (v2.0)
|
// TEMPS RÉEL: Publier dans Kafka après commit
|
||||||
try {
|
try {
|
||||||
Users user = friendship.getUser();
|
Users user = friendship.getUser();
|
||||||
Users friend = friendship.getFriend();
|
Users friend = friendship.getFriend();
|
||||||
// v2.0 - Utiliser les nouveaux noms de champs
|
|
||||||
String friendName = friend.getFirstName() + " " + friend.getLastName();
|
String friendName = friend.getFirstName() + " " + friend.getLastName();
|
||||||
|
|
||||||
Map<String, Object> notificationData = new HashMap<>();
|
Map<String, Object> notificationData = new HashMap<>();
|
||||||
notificationData.put("acceptedBy", friendName);
|
notificationData.put("acceptedBy", friendName);
|
||||||
notificationData.put("friendshipId", friendshipId.toString());
|
notificationData.put("friendshipId", friendshipId.toString());
|
||||||
notificationData.put("accepterId", friend.getId().toString());
|
notificationData.put("accepterId", friend.getId().toString());
|
||||||
notificationData.put("accepterProfileImage", friend.getProfileImageUrl() != null ? friend.getProfileImageUrl() : "");
|
notificationData.put("accepterProfileImage", friend.getProfileImageUrl() != null ? friend.getProfileImageUrl() : "");
|
||||||
|
NotificationEvent event = new NotificationEvent(user.getId().toString(), "friend_request_accepted", notificationData);
|
||||||
NotificationEvent event = new NotificationEvent(
|
sendToKafkaAfterCommit(event);
|
||||||
user.getId().toString(), // userId émetteur (destinataire de la notification)
|
|
||||||
"friend_request_accepted",
|
|
||||||
notificationData
|
|
||||||
);
|
|
||||||
|
|
||||||
notificationEmitter.send(event);
|
|
||||||
logger.info("[LOG] Événement friend_request_accepted publié dans Kafka pour : " + user.getId());
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("[ERROR] Erreur lors de la publication dans Kafka : " + e.getMessage(), e);
|
logger.error("[ERROR] Préparation publication Kafka : " + e.getMessage(), e);
|
||||||
// Ne pas bloquer l'acceptation si Kafka échoue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Créer des notifications pour les deux utilisateurs
|
// Créer des notifications pour les deux utilisateurs
|
||||||
@@ -246,25 +265,16 @@ public class FriendshipService {
|
|||||||
friendship.setStatus(FriendshipStatus.REJECTED);
|
friendship.setStatus(FriendshipStatus.REJECTED);
|
||||||
friendshipRepository.persist(friendship);
|
friendshipRepository.persist(friendship);
|
||||||
|
|
||||||
// TEMPS RÉEL: Publier dans Kafka (v2.0)
|
// TEMPS RÉEL: Publier dans Kafka après commit
|
||||||
try {
|
try {
|
||||||
Users user = friendship.getUser();
|
Users user = friendship.getUser();
|
||||||
|
|
||||||
Map<String, Object> notificationData = new HashMap<>();
|
Map<String, Object> notificationData = new HashMap<>();
|
||||||
notificationData.put("friendshipId", friendshipId.toString());
|
notificationData.put("friendshipId", friendshipId.toString());
|
||||||
notificationData.put("rejectedAt", System.currentTimeMillis());
|
notificationData.put("rejectedAt", System.currentTimeMillis());
|
||||||
|
NotificationEvent event = new NotificationEvent(user.getId().toString(), "friend_request_rejected", notificationData);
|
||||||
NotificationEvent event = new NotificationEvent(
|
sendToKafkaAfterCommit(event);
|
||||||
user.getId().toString(), // userId émetteur (destinataire de la notification)
|
|
||||||
"friend_request_rejected",
|
|
||||||
notificationData
|
|
||||||
);
|
|
||||||
|
|
||||||
notificationEmitter.send(event);
|
|
||||||
logger.info("[LOG] Événement friend_request_rejected publié dans Kafka pour : " + user.getId());
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("[ERROR] Erreur lors de la publication dans Kafka : " + e.getMessage(), e);
|
logger.error("[ERROR] Préparation publication Kafka : " + e.getMessage(), e);
|
||||||
// Ne pas bloquer le rejet si Kafka échoue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("[LOG] Demande d'amitié rejetée.");
|
logger.info("[LOG] Demande d'amitié rejetée.");
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ public class ChatWebSocketNext {
|
|||||||
try {
|
try {
|
||||||
UUID userUUID = UUID.fromString(userId);
|
UUID userUUID = UUID.fromString(userId);
|
||||||
sessions.put(userUUID, connection);
|
sessions.put(userUUID, connection);
|
||||||
Log.info("[CHAT-WS-NEXT] WebSocket ouvert pour l'utilisateur ID : " + userId);
|
Log.info("[CHAT-WS-NEXT] WebSocket ouvert pour l'utilisateur ID : " + userId + " (sessions actives: " + sessions.size() + ")");
|
||||||
|
|
||||||
// Envoyer un message de confirmation
|
// Envoyer un message de confirmation
|
||||||
String confirmation = buildJsonMessage("connected",
|
String confirmation = buildJsonMessage("connected",
|
||||||
@@ -220,7 +220,11 @@ public class ChatWebSocketNext {
|
|||||||
WebSocketConnection connection = sessions.get(userId);
|
WebSocketConnection connection = sessions.get(userId);
|
||||||
|
|
||||||
if (connection == null || !connection.isOpen()) {
|
if (connection == null || !connection.isOpen()) {
|
||||||
Log.debug("[CHAT-WS-NEXT] Utilisateur " + userId + " non connecté");
|
if (connection != null) {
|
||||||
|
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() + ")");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,7 +232,8 @@ public class ChatWebSocketNext {
|
|||||||
connection.sendText(message);
|
connection.sendText(message);
|
||||||
Log.debug("[CHAT-WS-NEXT] Message envoyé à l'utilisateur: " + userId);
|
Log.debug("[CHAT-WS-NEXT] Message envoyé à l'utilisateur: " + userId);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.error("[CHAT-WS-NEXT] Erreur lors de l'envoi à " + userId, e);
|
Log.error("[CHAT-WS-NEXT] Erreur lors de l'envoi à " + userId + ", connexion supprimée", e);
|
||||||
|
sessions.remove(userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,6 +244,7 @@ public class ChatWebSocketNext {
|
|||||||
WebSocketConnection connection = sessions.get(senderId);
|
WebSocketConnection connection = sessions.get(senderId);
|
||||||
|
|
||||||
if (connection == null || !connection.isOpen()) {
|
if (connection == null || !connection.isOpen()) {
|
||||||
|
if (connection != null) sessions.remove(senderId);
|
||||||
Log.debug("[CHAT-WS-NEXT] Expéditeur " + senderId + " non connecté pour confirmation");
|
Log.debug("[CHAT-WS-NEXT] Expéditeur " + senderId + " non connecté pour confirmation");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -248,7 +254,8 @@ public class ChatWebSocketNext {
|
|||||||
connection.sendText(response);
|
connection.sendText(response);
|
||||||
Log.debug("[CHAT-WS-NEXT] Confirmation de délivrance envoyée à: " + senderId);
|
Log.debug("[CHAT-WS-NEXT] Confirmation de délivrance envoyée à: " + senderId);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.error("[CHAT-WS-NEXT] Erreur envoi confirmation à " + senderId, e);
|
Log.error("[CHAT-WS-NEXT] Erreur envoi confirmation à " + senderId + ", connexion supprimée", e);
|
||||||
|
sessions.remove(senderId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,6 +266,7 @@ public class ChatWebSocketNext {
|
|||||||
WebSocketConnection connection = sessions.get(senderId);
|
WebSocketConnection connection = sessions.get(senderId);
|
||||||
|
|
||||||
if (connection == null || !connection.isOpen()) {
|
if (connection == null || !connection.isOpen()) {
|
||||||
|
if (connection != null) sessions.remove(senderId);
|
||||||
Log.debug("[CHAT-WS-NEXT] Expéditeur " + senderId + " non connecté pour confirmation de lecture");
|
Log.debug("[CHAT-WS-NEXT] Expéditeur " + senderId + " non connecté pour confirmation de lecture");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -268,7 +276,8 @@ public class ChatWebSocketNext {
|
|||||||
connection.sendText(response);
|
connection.sendText(response);
|
||||||
Log.debug("[CHAT-WS-NEXT] Confirmation de lecture envoyée à: " + senderId);
|
Log.debug("[CHAT-WS-NEXT] Confirmation de lecture envoyée à: " + senderId);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.error("[CHAT-WS-NEXT] Erreur envoi confirmation lecture à " + senderId, e);
|
Log.error("[CHAT-WS-NEXT] Erreur envoi confirmation lecture à " + senderId + ", connexion supprimée", e);
|
||||||
|
sessions.remove(senderId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -106,13 +106,12 @@ public class ChatKafkaBridge {
|
|||||||
data.put("isRead", event.getMetadata() != null &&
|
data.put("isRead", event.getMetadata() != null &&
|
||||||
Boolean.TRUE.equals(event.getMetadata().get("isRead")));
|
Boolean.TRUE.equals(event.getMetadata().get("isRead")));
|
||||||
data.put("isDelivered", true);
|
data.put("isDelivered", true);
|
||||||
if (event.getMetadata() != null) {
|
java.util.Map<String, Object> meta = event.getMetadata();
|
||||||
data.put("senderFirstName", event.getMetadata().getOrDefault("senderFirstName", ""));
|
data.put("senderFirstName", meta != null ? meta.getOrDefault("senderFirstName", "") : "");
|
||||||
data.put("senderLastName", event.getMetadata().getOrDefault("senderLastName", ""));
|
data.put("senderLastName", meta != null ? meta.getOrDefault("senderLastName", "") : "");
|
||||||
data.put("senderProfileImageUrl", event.getMetadata().getOrDefault("senderProfileImageUrl", ""));
|
data.put("senderProfileImageUrl", meta != null ? meta.getOrDefault("senderProfileImageUrl", "") : "");
|
||||||
data.put("attachmentUrl", event.getMetadata().getOrDefault("attachmentUrl", ""));
|
data.put("attachmentUrl", meta != null ? meta.getOrDefault("attachmentUrl", "") : "");
|
||||||
data.put("attachmentType", event.getMetadata().getOrDefault("attachmentType", "text"));
|
data.put("attachmentType", meta != null ? meta.getOrDefault("attachmentType", "text") : "text");
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
-- Migration V18: Création de la table event_shares pour les partages d'événements
|
||||||
|
-- Description: Enregistre chaque partage d'un événement par un utilisateur (compteur de partages)
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS event_shares (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
user_id UUID NOT NULL,
|
||||||
|
event_id UUID NOT NULL,
|
||||||
|
shared_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT fk_event_shares_user
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT fk_event_shares_event
|
||||||
|
FOREIGN KEY (event_id) REFERENCES events(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_event_shares_event_id ON event_shares(event_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_event_shares_user_id ON event_shares(user_id);
|
||||||
|
|
||||||
|
COMMENT ON TABLE event_shares IS 'Partages d''événements par les utilisateurs (compteur)';
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
-- Migration V19: Ajout de la capacité (places) pour les établissements
|
||||||
|
-- Description: Permet au manager de définir le nombre max de places ; places restantes calculées ou gérées manuellement
|
||||||
|
|
||||||
|
ALTER TABLE establishments ADD COLUMN IF NOT EXISTS capacity INTEGER NULL;
|
||||||
|
|
||||||
|
COMMENT ON COLUMN establishments.capacity IS 'Nombre maximum de places dans l''établissement (optionnel). Utilisé pour calculer les places restantes.';
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package com.lions.dev.repository;
|
||||||
|
|
||||||
|
import com.lions.dev.entity.establishment.Establishment;
|
||||||
|
import com.lions.dev.entity.events.Events;
|
||||||
|
import com.lions.dev.entity.users.Users;
|
||||||
|
import io.quarkus.test.junit.QuarkusTest;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
@QuarkusTest
|
||||||
|
class EventsRepositoryTest {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
EventsRepository eventsRepository;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("countParticipantsForEstablishment sans événements retourne 0")
|
||||||
|
void countParticipantsForEstablishment_noEvents_returnsZero() {
|
||||||
|
UUID establishmentId = UUID.randomUUID();
|
||||||
|
|
||||||
|
long count = eventsRepository.countParticipantsForEstablishment(establishmentId);
|
||||||
|
|
||||||
|
assertEquals(0L, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("findEventsAfterDate retourne une liste (éventuellement vide)")
|
||||||
|
void findEventsAfterDate_returnsList() {
|
||||||
|
LocalDateTime future = LocalDateTime.now().plusDays(1);
|
||||||
|
|
||||||
|
var result = eventsRepository.findEventsAfterDate(future);
|
||||||
|
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.isEmpty() || result.stream().allMatch(e -> e.getStartDate().isAfter(future)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("findEventsBetweenDates avec fin avant début retourne liste vide")
|
||||||
|
void findEventsBetweenDates_endBeforeStart_returnsEmpty() {
|
||||||
|
LocalDateTime start = LocalDateTime.now().plusDays(2);
|
||||||
|
LocalDateTime end = LocalDateTime.now().plusDays(1);
|
||||||
|
|
||||||
|
var result = eventsRepository.findEventsBetweenDates(start, end);
|
||||||
|
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.isEmpty());
|
||||||
|
}
|
||||||
|
}
|
||||||
51
src/test/java/com/lions/dev/resource/EventsResourceTest.java
Normal file
51
src/test/java/com/lions/dev/resource/EventsResourceTest.java
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
package com.lions.dev.resource;
|
||||||
|
|
||||||
|
import io.quarkus.test.junit.QuarkusTest;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static io.restassured.RestAssured.given;
|
||||||
|
import static org.hamcrest.CoreMatchers.*;
|
||||||
|
|
||||||
|
@QuarkusTest
|
||||||
|
class EventsResourceTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("POST /events/{id}/share sans événement existant retourne 404")
|
||||||
|
void shareEvent_eventNotFound_returns404() {
|
||||||
|
String eventId = UUID.randomUUID().toString();
|
||||||
|
String userId = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
given()
|
||||||
|
.queryParam("userId", userId)
|
||||||
|
.when()
|
||||||
|
.post("/events/" + eventId + "/share")
|
||||||
|
.then()
|
||||||
|
.statusCode(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("GET /events/share-link avec id invalide retourne 404")
|
||||||
|
void getShareLink_eventNotFound_returns404() {
|
||||||
|
String eventId = UUID.randomUUID().toString();
|
||||||
|
|
||||||
|
given()
|
||||||
|
.when()
|
||||||
|
.get("/events/" + eventId + "/share-link")
|
||||||
|
.then()
|
||||||
|
.statusCode(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("GET /events retourne une liste (200)")
|
||||||
|
void getEvents_returnsOk() {
|
||||||
|
given()
|
||||||
|
.when()
|
||||||
|
.get("/events")
|
||||||
|
.then()
|
||||||
|
.statusCode(200)
|
||||||
|
.body(anyOf(is("[]"), startsWith("[")));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
package com.lions.dev.service;
|
||||||
|
|
||||||
|
import com.lions.dev.dto.request.establishment.EstablishmentRatingRequestDTO;
|
||||||
|
import com.lions.dev.entity.establishment.Establishment;
|
||||||
|
import com.lions.dev.entity.establishment.EstablishmentRating;
|
||||||
|
import com.lions.dev.entity.users.Users;
|
||||||
|
import com.lions.dev.repository.EstablishmentRatingRepository;
|
||||||
|
import com.lions.dev.repository.EstablishmentRepository;
|
||||||
|
import com.lions.dev.repository.UsersRepository;
|
||||||
|
import io.quarkus.test.InjectMock;
|
||||||
|
import io.quarkus.test.junit.QuarkusTest;
|
||||||
|
import jakarta.inject.Inject;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.ArgumentMatchers.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
@QuarkusTest
|
||||||
|
class EstablishmentRatingServiceTest {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
EstablishmentRatingService establishmentRatingService;
|
||||||
|
|
||||||
|
@InjectMock
|
||||||
|
EstablishmentRatingRepository ratingRepository;
|
||||||
|
|
||||||
|
@InjectMock
|
||||||
|
EstablishmentRepository establishmentRepository;
|
||||||
|
|
||||||
|
@InjectMock
|
||||||
|
UsersRepository usersRepository;
|
||||||
|
|
||||||
|
@InjectMock
|
||||||
|
NotificationService notificationService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("submitRating avec données valides crée la note et met à jour les stats")
|
||||||
|
void submitRating_validData_createsRatingAndUpdatesStats() {
|
||||||
|
UUID establishmentId = UUID.randomUUID();
|
||||||
|
UUID userId = UUID.randomUUID();
|
||||||
|
Establishment establishment = new Establishment("Bar", "BAR", "1 rue Test", "Paris", "75001", new Users());
|
||||||
|
establishment.setId(establishmentId);
|
||||||
|
Users user = new Users();
|
||||||
|
user.setId(userId);
|
||||||
|
user.setFirstName("Jean");
|
||||||
|
user.setLastName("Dupont");
|
||||||
|
EstablishmentRatingRequestDTO requestDTO = new EstablishmentRatingRequestDTO();
|
||||||
|
requestDTO.setRating(4);
|
||||||
|
requestDTO.setComment("Très bien");
|
||||||
|
|
||||||
|
when(establishmentRepository.findById(establishmentId)).thenReturn(establishment);
|
||||||
|
when(usersRepository.findById(userId)).thenReturn(user);
|
||||||
|
when(ratingRepository.findByEstablishmentIdAndUserId(establishmentId, userId)).thenReturn(null);
|
||||||
|
when(ratingRepository.calculateAverageRating(establishmentId)).thenReturn(4.0);
|
||||||
|
when(ratingRepository.countByEstablishmentId(establishmentId)).thenReturn(1L);
|
||||||
|
when(ratingRepository.calculateRatingDistribution(establishmentId)).thenReturn(Map.of(4, 1));
|
||||||
|
Notification notif = new Notification("Nouvelle note", "Message", "rating", user);
|
||||||
|
when(notificationService.createNotification(anyString(), anyString(), anyString(), any(UUID.class), any())).thenReturn(notif);
|
||||||
|
|
||||||
|
EstablishmentRating result = establishmentRatingService.submitRating(establishmentId, userId, requestDTO);
|
||||||
|
|
||||||
|
assertNotNull(result);
|
||||||
|
verify(ratingRepository).persist(any(EstablishmentRating.class));
|
||||||
|
verify(establishmentRepository).persist(establishment);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("submitRating avec établissement inexistant lance RuntimeException")
|
||||||
|
void submitRating_establishmentNotFound_throws() {
|
||||||
|
UUID establishmentId = UUID.randomUUID();
|
||||||
|
UUID userId = UUID.randomUUID();
|
||||||
|
EstablishmentRatingRequestDTO requestDTO = new EstablishmentRatingRequestDTO();
|
||||||
|
requestDTO.setRating(5);
|
||||||
|
|
||||||
|
when(establishmentRepository.findById(establishmentId)).thenReturn(null);
|
||||||
|
|
||||||
|
assertThrows(RuntimeException.class, () ->
|
||||||
|
establishmentRatingService.submitRating(establishmentId, userId, requestDTO));
|
||||||
|
verify(ratingRepository, never()).persist(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("submitRating avec utilisateur inexistant lance RuntimeException")
|
||||||
|
void submitRating_userNotFound_throws() {
|
||||||
|
UUID establishmentId = UUID.randomUUID();
|
||||||
|
UUID userId = UUID.randomUUID();
|
||||||
|
Establishment establishment = new Establishment("Bar", "BAR", "1 rue Test", "Paris", "75001", new Users());
|
||||||
|
establishment.setId(establishmentId);
|
||||||
|
EstablishmentRatingRequestDTO requestDTO = new EstablishmentRatingRequestDTO();
|
||||||
|
requestDTO.setRating(5);
|
||||||
|
|
||||||
|
when(establishmentRepository.findById(establishmentId)).thenReturn(establishment);
|
||||||
|
when(usersRepository.findById(userId)).thenReturn(null);
|
||||||
|
|
||||||
|
assertThrows(RuntimeException.class, () ->
|
||||||
|
establishmentRatingService.submitRating(establishmentId, userId, requestDTO));
|
||||||
|
verify(ratingRepository, never()).persist(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("submitRating quand l'utilisateur a déjà noté lance RuntimeException")
|
||||||
|
void submitRating_alreadyRated_throws() {
|
||||||
|
UUID establishmentId = UUID.randomUUID();
|
||||||
|
UUID userId = UUID.randomUUID();
|
||||||
|
Establishment establishment = new Establishment("Bar", "BAR", "1 rue Test", "Paris", "75001", new Users());
|
||||||
|
establishment.setId(establishmentId);
|
||||||
|
Users user = new Users();
|
||||||
|
user.setId(userId);
|
||||||
|
EstablishmentRating existing = new EstablishmentRating(establishment, user, 3);
|
||||||
|
EstablishmentRatingRequestDTO requestDTO = new EstablishmentRatingRequestDTO();
|
||||||
|
requestDTO.setRating(5);
|
||||||
|
|
||||||
|
when(establishmentRepository.findById(establishmentId)).thenReturn(establishment);
|
||||||
|
when(usersRepository.findById(userId)).thenReturn(user);
|
||||||
|
when(ratingRepository.findByEstablishmentIdAndUserId(establishmentId, userId)).thenReturn(existing);
|
||||||
|
|
||||||
|
assertThrows(RuntimeException.class, () ->
|
||||||
|
establishmentRatingService.submitRating(establishmentId, userId, requestDTO));
|
||||||
|
verify(ratingRepository, never()).persist(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("getUserRating retourne la note si elle existe")
|
||||||
|
void getUserRating_exists_returnsRating() {
|
||||||
|
UUID establishmentId = UUID.randomUUID();
|
||||||
|
UUID userId = UUID.randomUUID();
|
||||||
|
Establishment establishment = new Establishment("Bar", "BAR", "1 rue Test", "Paris", "75001", new Users());
|
||||||
|
establishment.setId(establishmentId);
|
||||||
|
Users user = new Users();
|
||||||
|
user.setId(userId);
|
||||||
|
EstablishmentRating rating = new EstablishmentRating(establishment, user, 4);
|
||||||
|
|
||||||
|
when(ratingRepository.findByEstablishmentIdAndUserId(establishmentId, userId)).thenReturn(rating);
|
||||||
|
|
||||||
|
EstablishmentRating result = establishmentRatingService.getUserRating(establishmentId, userId);
|
||||||
|
|
||||||
|
assertNotNull(result);
|
||||||
|
assertEquals(4, result.getRating());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("getUserRating retourne null si pas de note")
|
||||||
|
void getUserRating_notExists_returnsNull() {
|
||||||
|
UUID establishmentId = UUID.randomUUID();
|
||||||
|
UUID userId = UUID.randomUUID();
|
||||||
|
|
||||||
|
when(ratingRepository.findByEstablishmentIdAndUserId(establishmentId, userId)).thenReturn(null);
|
||||||
|
|
||||||
|
EstablishmentRating result = establishmentRatingService.getUserRating(establishmentId, userId);
|
||||||
|
|
||||||
|
assertNull(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("getRatingStats retourne moyenne, total et distribution")
|
||||||
|
void getRatingStats_returnsMap() {
|
||||||
|
UUID establishmentId = UUID.randomUUID();
|
||||||
|
|
||||||
|
when(ratingRepository.calculateAverageRating(establishmentId)).thenReturn(4.2);
|
||||||
|
when(ratingRepository.countByEstablishmentId(establishmentId)).thenReturn(10L);
|
||||||
|
when(ratingRepository.calculateRatingDistribution(establishmentId)).thenReturn(Map.of(4, 6, 5, 4));
|
||||||
|
|
||||||
|
Map<String, Object> result = establishmentRatingService.getRatingStats(establishmentId);
|
||||||
|
|
||||||
|
assertNotNull(result);
|
||||||
|
assertEquals(4.2, result.get("averageRating"));
|
||||||
|
assertEquals(10, result.get("totalRatingsCount"));
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<Integer, Integer> dist = (Map<Integer, Integer>) result.get("ratingDistribution");
|
||||||
|
assertNotNull(dist);
|
||||||
|
assertEquals(6, dist.get(4));
|
||||||
|
assertEquals(4, dist.get(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("updateRating avec note existante met à jour et met à jour les stats")
|
||||||
|
void updateRating_exists_updatesAndRefreshesStats() {
|
||||||
|
UUID establishmentId = UUID.randomUUID();
|
||||||
|
UUID userId = UUID.randomUUID();
|
||||||
|
Establishment establishment = new Establishment("Bar", "BAR", "1 rue Test", "Paris", "75001", new Users());
|
||||||
|
establishment.setId(establishmentId);
|
||||||
|
Users user = new Users();
|
||||||
|
user.setId(userId);
|
||||||
|
EstablishmentRating rating = new EstablishmentRating(establishment, user, 3);
|
||||||
|
EstablishmentRatingRequestDTO requestDTO = new EstablishmentRatingRequestDTO();
|
||||||
|
requestDTO.setRating(5);
|
||||||
|
requestDTO.setComment("Parfait");
|
||||||
|
|
||||||
|
when(ratingRepository.findByEstablishmentIdAndUserId(establishmentId, userId)).thenReturn(rating);
|
||||||
|
when(ratingRepository.calculateAverageRating(establishmentId)).thenReturn(5.0);
|
||||||
|
when(ratingRepository.countByEstablishmentId(establishmentId)).thenReturn(1L);
|
||||||
|
when(ratingRepository.calculateRatingDistribution(establishmentId)).thenReturn(Map.of(5, 1));
|
||||||
|
|
||||||
|
EstablishmentRating result = establishmentRatingService.updateRating(establishmentId, userId, requestDTO);
|
||||||
|
|
||||||
|
assertNotNull(result);
|
||||||
|
verify(ratingRepository).persist(rating);
|
||||||
|
verify(establishmentRepository).persist(establishment);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("updateRating quand la note n'existe pas lance RuntimeException")
|
||||||
|
void updateRating_notExists_throws() {
|
||||||
|
UUID establishmentId = UUID.randomUUID();
|
||||||
|
UUID userId = UUID.randomUUID();
|
||||||
|
EstablishmentRatingRequestDTO requestDTO = new EstablishmentRatingRequestDTO();
|
||||||
|
requestDTO.setRating(5);
|
||||||
|
|
||||||
|
when(ratingRepository.findByEstablishmentIdAndUserId(establishmentId, userId)).thenReturn(null);
|
||||||
|
|
||||||
|
assertThrows(RuntimeException.class, () ->
|
||||||
|
establishmentRatingService.updateRating(establishmentId, userId, requestDTO));
|
||||||
|
verify(ratingRepository, never()).persist(any());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user