feat(backend): endpoints inscriptions + feedback événements
Ajoute infrastructure complète pour gérer inscriptions et feedbacks événements.
## Entités
- FeedbackEvenement : note 1-5, commentaire, modération (PUBLIE/EN_ATTENTE/REJETE)
- InscriptionEvenement : déjà existait, utilisation ajoutée
## Repositories
- InscriptionEvenementRepository : findByMembreAndEvenement, findByEvenement, countConfirmees, isMembreInscrit
- FeedbackEvenementRepository : findByMembreAndEvenement, findPubliesByEvenement, calculateAverageNote
## Service (EvenementService)
Inscriptions :
- inscrireEvenement() : vérifie capacité, crée inscription CONFIRMEE
- desinscrireEvenement() : soft delete inscription
- getParticipants() : liste inscriptions confirmées
- getMesInscriptions() : inscriptions du membre connecté
Feedbacks :
- soumetteFeedback() : note 1-5 + commentaire, vérifie participation, événement terminé
- getFeedbacks() : liste feedbacks publiés
- getStatistiquesFeedback() : note moyenne + nombre feedbacks
## REST Endpoints (6 total)
Inscriptions :
- POST /api/evenements/{id}/inscriptions - S'inscrire
- DELETE /api/evenements/{id}/inscriptions - Se désinscrire
- GET /api/evenements/{id}/participants - Liste participants
- GET /api/evenements/mes-inscriptions - Mes inscriptions
Feedbacks :
- POST /api/evenements/{id}/feedback - Soumettre feedback (note+commentaire)
- GET /api/evenements/{id}/feedbacks - Liste feedbacks + stats
## Database
- Migration V7 : table feedbacks_evenement
- Contrainte unique: un feedback par membre/événement
- Index: membre_id, evenement_id, date_feedback, moderation_statut
Débloquer fonctionnalités événements mobile.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
package dev.lions.unionflow.server.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* Entité FeedbackEvenement représentant l'évaluation d'un membre sur un événement
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2026-03-16
|
||||
*/
|
||||
@Entity
|
||||
@Table(
|
||||
name = "feedbacks_evenement",
|
||||
indexes = {
|
||||
@Index(name = "idx_feedback_membre", columnList = "membre_id"),
|
||||
@Index(name = "idx_feedback_evenement", columnList = "evenement_id"),
|
||||
@Index(name = "idx_feedback_date", columnList = "date_feedback")
|
||||
},
|
||||
uniqueConstraints = {
|
||||
@UniqueConstraint(
|
||||
name = "uk_feedback_membre_evenement",
|
||||
columnNames = {"membre_id", "evenement_id"})
|
||||
})
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class FeedbackEvenement extends BaseEntity {
|
||||
|
||||
@NotNull
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "membre_id", nullable = false)
|
||||
private Membre membre;
|
||||
|
||||
@NotNull
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "evenement_id", nullable = false)
|
||||
private Evenement evenement;
|
||||
|
||||
@NotNull
|
||||
@Min(1)
|
||||
@Max(5)
|
||||
@Column(name = "note", nullable = false)
|
||||
private Integer note;
|
||||
|
||||
@Column(name = "commentaire", length = 1000)
|
||||
private String commentaire;
|
||||
|
||||
@Builder.Default
|
||||
@Column(name = "date_feedback", nullable = false)
|
||||
private LocalDateTime dateFeedback = LocalDateTime.now();
|
||||
|
||||
@Column(name = "moderation_statut", length = 20)
|
||||
@Builder.Default
|
||||
private String moderationStatut = ModerationStatut.PUBLIE.name();
|
||||
|
||||
@Column(name = "raison_moderation", length = 500)
|
||||
private String raisonModeration;
|
||||
|
||||
/** Énumération des statuts de modération */
|
||||
public enum ModerationStatut {
|
||||
PUBLIE, // Visible publiquement
|
||||
EN_ATTENTE, // En attente de modération
|
||||
REJETE // Rejeté par modération
|
||||
}
|
||||
|
||||
// Méthodes utilitaires
|
||||
|
||||
/** Vérifie si le feedback est publié */
|
||||
public boolean isPublie() {
|
||||
return ModerationStatut.PUBLIE.name().equals(this.moderationStatut);
|
||||
}
|
||||
|
||||
/** Marque le feedback comme en attente de modération */
|
||||
public void mettreEnAttente(String raison) {
|
||||
this.moderationStatut = ModerationStatut.EN_ATTENTE.name();
|
||||
this.raisonModeration = raison;
|
||||
setDateModification(LocalDateTime.now());
|
||||
}
|
||||
|
||||
/** Publie le feedback */
|
||||
public void publier() {
|
||||
this.moderationStatut = ModerationStatut.PUBLIE.name();
|
||||
this.raisonModeration = null;
|
||||
setDateModification(LocalDateTime.now());
|
||||
}
|
||||
|
||||
/** Rejette le feedback */
|
||||
public void rejeter(String raison) {
|
||||
this.moderationStatut = ModerationStatut.REJETE.name();
|
||||
this.raisonModeration = raison;
|
||||
setDateModification(LocalDateTime.now());
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
public void preUpdate() {
|
||||
super.onUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(
|
||||
"FeedbackEvenement{id=%s, membre=%s, evenement=%s, note=%d, dateFeedback=%s}",
|
||||
getId(),
|
||||
membre != null ? membre.getEmail() : "null",
|
||||
evenement != null ? evenement.getTitre() : "null",
|
||||
note,
|
||||
dateFeedback);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.FeedbackEvenement;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Repository pour les feedbacks d'événements
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2026-03-16
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class FeedbackEvenementRepository
|
||||
implements PanacheRepositoryBase<FeedbackEvenement, UUID> {
|
||||
|
||||
/**
|
||||
* Trouve un feedback par membre et événement
|
||||
*
|
||||
* @param membreId UUID du membre
|
||||
* @param evenementId UUID de l'événement
|
||||
* @return Optional de FeedbackEvenement
|
||||
*/
|
||||
public Optional<FeedbackEvenement> findByMembreAndEvenement(
|
||||
UUID membreId, UUID evenementId) {
|
||||
return find("membre.id = ?1 and evenement.id = ?2 and actif = true", membreId, evenementId)
|
||||
.firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste tous les feedbacks d'un événement (publiés uniquement)
|
||||
*
|
||||
* @param evenementId UUID de l'événement
|
||||
* @return Liste des feedbacks publiés
|
||||
*/
|
||||
public List<FeedbackEvenement> findPubliesByEvenement(UUID evenementId) {
|
||||
return find(
|
||||
"evenement.id = ?1 and moderationStatut = 'PUBLIE' and actif = true order by dateFeedback desc",
|
||||
evenementId)
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste tous les feedbacks d'un événement (tous statuts)
|
||||
*
|
||||
* @param evenementId UUID de l'événement
|
||||
* @return Liste de tous les feedbacks
|
||||
*/
|
||||
public List<FeedbackEvenement> findAllByEvenement(UUID evenementId) {
|
||||
return find("evenement.id = ?1 and actif = true order by dateFeedback desc", evenementId)
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcule la note moyenne d'un événement
|
||||
*
|
||||
* @param evenementId UUID de l'événement
|
||||
* @return Note moyenne (ou 0.0 si aucun feedback)
|
||||
*/
|
||||
public Double calculateAverageNote(UUID evenementId) {
|
||||
Double avg =
|
||||
find(
|
||||
"select avg(f.note) from FeedbackEvenement f where f.evenement.id = ?1 and f.moderationStatut = 'PUBLIE' and f.actif = true",
|
||||
evenementId)
|
||||
.project(Double.class)
|
||||
.firstResult();
|
||||
return avg != null ? avg : 0.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compte le nombre de feedbacks publiés pour un événement
|
||||
*
|
||||
* @param evenementId UUID de l'événement
|
||||
* @return Nombre de feedbacks publiés
|
||||
*/
|
||||
public long countPubliesByEvenement(UUID evenementId) {
|
||||
return count(
|
||||
"evenement.id = ?1 and moderationStatut = 'PUBLIE' and actif = true", evenementId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste les feedbacks en attente de modération
|
||||
*
|
||||
* @return Liste des feedbacks en attente
|
||||
*/
|
||||
public List<FeedbackEvenement> findEnAttente() {
|
||||
return find(
|
||||
"moderationStatut = 'EN_ATTENTE' and actif = true order by dateFeedback desc")
|
||||
.list();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package dev.lions.unionflow.server.repository;
|
||||
|
||||
import dev.lions.unionflow.server.entity.Evenement;
|
||||
import dev.lions.unionflow.server.entity.InscriptionEvenement;
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Repository pour les inscriptions aux événements
|
||||
*
|
||||
* @author UnionFlow Team
|
||||
* @version 1.0
|
||||
* @since 2026-03-16
|
||||
*/
|
||||
@ApplicationScoped
|
||||
public class InscriptionEvenementRepository
|
||||
implements PanacheRepositoryBase<InscriptionEvenement, UUID> {
|
||||
|
||||
/**
|
||||
* Trouve une inscription par membre et événement
|
||||
*
|
||||
* @param membreId UUID du membre
|
||||
* @param evenementId UUID de l'événement
|
||||
* @return Optional d'InscriptionEvenement
|
||||
*/
|
||||
public Optional<InscriptionEvenement> findByMembreAndEvenement(
|
||||
UUID membreId, UUID evenementId) {
|
||||
return find("membre.id = ?1 and evenement.id = ?2 and actif = true", membreId, evenementId)
|
||||
.firstResultOptional();
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste toutes les inscriptions d'un membre
|
||||
*
|
||||
* @param membreId UUID du membre
|
||||
* @return Liste des inscriptions
|
||||
*/
|
||||
public List<InscriptionEvenement> findByMembre(UUID membreId) {
|
||||
return find(
|
||||
"membre.id = ?1 and actif = true order by dateInscription desc",
|
||||
membreId)
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste toutes les inscriptions à un événement
|
||||
*
|
||||
* @param evenementId UUID de l'événement
|
||||
* @return Liste des inscriptions
|
||||
*/
|
||||
public List<InscriptionEvenement> findByEvenement(UUID evenementId) {
|
||||
return find(
|
||||
"evenement.id = ?1 and actif = true order by dateInscription asc",
|
||||
evenementId)
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste les inscriptions confirmées pour un événement
|
||||
*
|
||||
* @param evenementId UUID de l'événement
|
||||
* @return Liste des inscriptions confirmées
|
||||
*/
|
||||
public List<InscriptionEvenement> findConfirmeesByEvenement(UUID evenementId) {
|
||||
return find(
|
||||
"evenement.id = ?1 and statut = 'CONFIRMEE' and actif = true order by dateInscription asc",
|
||||
evenementId)
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compte le nombre d'inscriptions confirmées pour un événement
|
||||
*
|
||||
* @param evenementId UUID de l'événement
|
||||
* @return Nombre d'inscriptions confirmées
|
||||
*/
|
||||
public long countConfirmeesByEvenement(UUID evenementId) {
|
||||
return count(
|
||||
"evenement.id = ?1 and statut = 'CONFIRMEE' and actif = true", evenementId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si un membre est inscrit à un événement
|
||||
*
|
||||
* @param membreId UUID du membre
|
||||
* @param evenementId UUID de l'événement
|
||||
* @return true si le membre est inscrit et l'inscription est confirmée
|
||||
*/
|
||||
public boolean isMembreInscrit(UUID membreId, UUID evenementId) {
|
||||
return count(
|
||||
"membre.id = ?1 and evenement.id = ?2 and statut = 'CONFIRMEE' and actif = true",
|
||||
membreId,
|
||||
evenementId)
|
||||
> 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime logiquement une inscription
|
||||
*
|
||||
* @param inscription L'inscription à supprimer
|
||||
*/
|
||||
public void softDelete(InscriptionEvenement inscription) {
|
||||
inscription.setActif(false);
|
||||
persist(inscription);
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,14 @@ package dev.lions.unionflow.server.resource;
|
||||
import dev.lions.unionflow.server.api.dto.common.PagedResponse;
|
||||
import dev.lions.unionflow.server.dto.EvenementMobileDTO;
|
||||
import dev.lions.unionflow.server.entity.Evenement;
|
||||
import dev.lions.unionflow.server.entity.FeedbackEvenement;
|
||||
import dev.lions.unionflow.server.entity.InscriptionEvenement;
|
||||
import dev.lions.unionflow.server.service.EvenementService;
|
||||
import io.quarkus.panache.common.Page;
|
||||
import io.quarkus.panache.common.Sort;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.transaction.Transactional;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.ws.rs.*;
|
||||
@@ -286,4 +289,112 @@ public class EvenementResource {
|
||||
boolean inscrit = evenementService.isUserInscrit(id);
|
||||
return Response.ok(Map.of("inscrit", inscrit)).build();
|
||||
}
|
||||
|
||||
// === GESTION DES INSCRIPTIONS ===
|
||||
|
||||
/** S'inscrire à un événement */
|
||||
@POST
|
||||
@Path("/{id}/inscriptions")
|
||||
@Operation(summary = "S'inscrire à un événement")
|
||||
@APIResponse(responseCode = "201", description = "Inscription créée")
|
||||
@APIResponse(responseCode = "400", description = "Déjà inscrit ou événement complet")
|
||||
@APIResponse(responseCode = "404", description = "Événement non trouvé")
|
||||
@RolesAllowed({ "ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE", "USER" })
|
||||
@Transactional
|
||||
public Response inscrireEvenement(@PathParam("id") UUID evenementId) {
|
||||
try {
|
||||
InscriptionEvenement inscription = evenementService.inscrireEvenement(evenementId);
|
||||
return Response.status(Response.Status.CREATED).entity(inscription).build();
|
||||
} catch (IllegalStateException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Se désinscrire d'un événement */
|
||||
@DELETE
|
||||
@Path("/{id}/inscriptions")
|
||||
@Operation(summary = "Se désinscrire d'un événement")
|
||||
@APIResponse(responseCode = "204", description = "Désinscription effectuée")
|
||||
@APIResponse(responseCode = "404", description = "Inscription non trouvée")
|
||||
@RolesAllowed({ "ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE", "USER" })
|
||||
@Transactional
|
||||
public Response desinscrireEvenement(@PathParam("id") UUID evenementId) {
|
||||
evenementService.desinscrireEvenement(evenementId);
|
||||
return Response.noContent().build();
|
||||
}
|
||||
|
||||
/** Liste des participants d'un événement */
|
||||
@GET
|
||||
@Path("/{id}/participants")
|
||||
@Operation(summary = "Liste des participants")
|
||||
@APIResponse(responseCode = "200", description = "Liste des participants")
|
||||
@RolesAllowed({ "ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE" })
|
||||
public Response getParticipants(@PathParam("id") UUID evenementId) {
|
||||
List<InscriptionEvenement> participants = evenementService.getParticipants(evenementId);
|
||||
return Response.ok(participants).build();
|
||||
}
|
||||
|
||||
/** Mes inscriptions */
|
||||
@GET
|
||||
@Path("/mes-inscriptions")
|
||||
@Operation(summary = "Mes inscriptions aux événements")
|
||||
@APIResponse(responseCode = "200", description = "Liste de mes inscriptions")
|
||||
@RolesAllowed({ "ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE", "USER" })
|
||||
public Response getMesInscriptions() {
|
||||
List<InscriptionEvenement> inscriptions = evenementService.getMesInscriptions();
|
||||
return Response.ok(inscriptions).build();
|
||||
}
|
||||
|
||||
// === GESTION DES FEEDBACKS ===
|
||||
|
||||
/** Soumettre un feedback */
|
||||
@POST
|
||||
@Path("/{id}/feedback")
|
||||
@Operation(summary = "Soumettre un feedback sur l'événement")
|
||||
@APIResponse(responseCode = "201", description = "Feedback créé")
|
||||
@APIResponse(responseCode = "400", description = "Données invalides ou feedback déjà soumis")
|
||||
@APIResponse(responseCode = "404", description = "Événement non trouvé")
|
||||
@RolesAllowed({ "ADMIN", "PRESIDENT", "SECRETAIRE", "ORGANISATEUR_EVENEMENT", "MEMBRE", "USER" })
|
||||
@Transactional
|
||||
public Response soumetteFeedback(
|
||||
@PathParam("id") UUID evenementId, Map<String, Object> requestBody) {
|
||||
|
||||
Integer note = (Integer) requestBody.get("note");
|
||||
String commentaire = (String) requestBody.get("commentaire");
|
||||
|
||||
if (note == null || note < 1 || note > 5) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", "La note doit être entre 1 et 5"))
|
||||
.build();
|
||||
}
|
||||
|
||||
try {
|
||||
FeedbackEvenement feedback =
|
||||
evenementService.soumetteFeedback(evenementId, note, commentaire);
|
||||
return Response.status(Response.Status.CREATED).entity(feedback).build();
|
||||
} catch (IllegalStateException e) {
|
||||
return Response.status(Response.Status.BAD_REQUEST)
|
||||
.entity(Map.of("error", e.getMessage()))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** Liste des feedbacks d'un événement */
|
||||
@GET
|
||||
@Path("/{id}/feedbacks")
|
||||
@Operation(summary = "Liste des feedbacks de l'événement")
|
||||
@APIResponse(responseCode = "200", description = "Liste des feedbacks")
|
||||
public Response getFeedbacks(@PathParam("id") UUID evenementId) {
|
||||
List<FeedbackEvenement> feedbacks = evenementService.getFeedbacks(evenementId);
|
||||
Map<String, Object> stats = evenementService.getStatistiquesFeedback(evenementId);
|
||||
|
||||
return Response.ok(
|
||||
Map.of(
|
||||
"feedbacks", feedbacks,
|
||||
"noteMoyenne", stats.get("noteMoyenne"),
|
||||
"nombreFeedbacks", stats.get("nombreFeedbacks")))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
package dev.lions.unionflow.server.service;
|
||||
|
||||
import dev.lions.unionflow.server.entity.Evenement;
|
||||
import dev.lions.unionflow.server.entity.FeedbackEvenement;
|
||||
import dev.lions.unionflow.server.entity.InscriptionEvenement;
|
||||
import dev.lions.unionflow.server.entity.Membre;
|
||||
import dev.lions.unionflow.server.repository.EvenementRepository;
|
||||
import dev.lions.unionflow.server.repository.FeedbackEvenementRepository;
|
||||
import dev.lions.unionflow.server.repository.InscriptionEvenementRepository;
|
||||
import dev.lions.unionflow.server.repository.MembreRepository;
|
||||
import dev.lions.unionflow.server.repository.OrganisationRepository;
|
||||
import dev.lions.unionflow.server.service.KeycloakService;
|
||||
@@ -45,6 +50,12 @@ public class EvenementService {
|
||||
@Inject
|
||||
KeycloakService keycloakService;
|
||||
|
||||
@Inject
|
||||
InscriptionEvenementRepository inscriptionRepository;
|
||||
|
||||
@Inject
|
||||
FeedbackEvenementRepository feedbackRepository;
|
||||
|
||||
/**
|
||||
* Crée un nouvel événement
|
||||
*
|
||||
@@ -364,4 +375,204 @@ public class EvenementService {
|
||||
.map(m -> evenement.isMemberInscrit(m.getId()))
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
// === GESTION DES INSCRIPTIONS ===
|
||||
|
||||
/**
|
||||
* Inscrit l'utilisateur connecté à un événement
|
||||
*
|
||||
* @param evenementId UUID de l'événement
|
||||
* @return L'inscription créée
|
||||
*/
|
||||
@Transactional
|
||||
public InscriptionEvenement inscrireEvenement(UUID evenementId) {
|
||||
String email = keycloakService.getCurrentUserEmail();
|
||||
if (email == null || email.isBlank()) {
|
||||
throw new IllegalStateException("Utilisateur non authentifié");
|
||||
}
|
||||
|
||||
Membre membre =
|
||||
membreRepository
|
||||
.findByEmail(email)
|
||||
.orElseThrow(() -> new NotFoundException("Membre non trouvé"));
|
||||
|
||||
Evenement evenement =
|
||||
evenementRepository
|
||||
.findByIdOptional(evenementId)
|
||||
.orElseThrow(() -> new NotFoundException("Événement non trouvé"));
|
||||
|
||||
// Vérifier si déjà inscrit
|
||||
Optional<InscriptionEvenement> existante =
|
||||
inscriptionRepository.findByMembreAndEvenement(membre.getId(), evenementId);
|
||||
if (existante.isPresent()) {
|
||||
throw new IllegalStateException("Vous êtes déjà inscrit à cet événement");
|
||||
}
|
||||
|
||||
// Vérifier capacité
|
||||
if (evenement.getCapaciteMax() != null) {
|
||||
long nbInscrits = inscriptionRepository.countConfirmeesByEvenement(evenementId);
|
||||
if (nbInscrits >= evenement.getCapaciteMax()) {
|
||||
throw new IllegalStateException("L'événement est complet");
|
||||
}
|
||||
}
|
||||
|
||||
InscriptionEvenement inscription =
|
||||
InscriptionEvenement.builder()
|
||||
.membre(membre)
|
||||
.evenement(evenement)
|
||||
.statut(InscriptionEvenement.StatutInscription.CONFIRMEE.name())
|
||||
.dateInscription(LocalDateTime.now())
|
||||
.build();
|
||||
|
||||
inscriptionRepository.persist(inscription);
|
||||
LOG.infof(
|
||||
"Inscription créée: membre=%s, événement=%s", membre.getEmail(), evenement.getTitre());
|
||||
return inscription;
|
||||
}
|
||||
|
||||
/**
|
||||
* Désinscrit l'utilisateur connecté d'un événement
|
||||
*
|
||||
* @param evenementId UUID de l'événement
|
||||
*/
|
||||
@Transactional
|
||||
public void desinscrireEvenement(UUID evenementId) {
|
||||
String email = keycloakService.getCurrentUserEmail();
|
||||
if (email == null || email.isBlank()) {
|
||||
throw new IllegalStateException("Utilisateur non authentifié");
|
||||
}
|
||||
|
||||
Membre membre =
|
||||
membreRepository
|
||||
.findByEmail(email)
|
||||
.orElseThrow(() -> new NotFoundException("Membre non trouvé"));
|
||||
|
||||
InscriptionEvenement inscription =
|
||||
inscriptionRepository
|
||||
.findByMembreAndEvenement(membre.getId(), evenementId)
|
||||
.orElseThrow(() -> new NotFoundException("Inscription non trouvée"));
|
||||
|
||||
inscriptionRepository.softDelete(inscription);
|
||||
LOG.infof("Désinscription: membre=%s, événement=%s", membre.getEmail(), evenementId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste les participants d'un événement
|
||||
*
|
||||
* @param evenementId UUID de l'événement
|
||||
* @return Liste des inscriptions confirmées
|
||||
*/
|
||||
public List<InscriptionEvenement> getParticipants(UUID evenementId) {
|
||||
return inscriptionRepository.findConfirmeesByEvenement(evenementId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste les inscriptions de l'utilisateur connecté
|
||||
*
|
||||
* @return Liste des inscriptions du membre
|
||||
*/
|
||||
public List<InscriptionEvenement> getMesInscriptions() {
|
||||
String email = keycloakService.getCurrentUserEmail();
|
||||
if (email == null || email.isBlank()) {
|
||||
throw new IllegalStateException("Utilisateur non authentifié");
|
||||
}
|
||||
|
||||
Membre membre =
|
||||
membreRepository
|
||||
.findByEmail(email)
|
||||
.orElseThrow(() -> new NotFoundException("Membre non trouvé"));
|
||||
|
||||
return inscriptionRepository.findByMembre(membre.getId());
|
||||
}
|
||||
|
||||
// === GESTION DES FEEDBACKS ===
|
||||
|
||||
/**
|
||||
* Soumet un feedback pour un événement
|
||||
*
|
||||
* @param evenementId UUID de l'événement
|
||||
* @param note Note de 1 à 5
|
||||
* @param commentaire Commentaire optionnel
|
||||
* @return Le feedback créé
|
||||
*/
|
||||
@Transactional
|
||||
public FeedbackEvenement soumetteFeedback(
|
||||
UUID evenementId, Integer note, String commentaire) {
|
||||
String email = keycloakService.getCurrentUserEmail();
|
||||
if (email == null || email.isBlank()) {
|
||||
throw new IllegalStateException("Utilisateur non authentifié");
|
||||
}
|
||||
|
||||
Membre membre =
|
||||
membreRepository
|
||||
.findByEmail(email)
|
||||
.orElseThrow(() -> new NotFoundException("Membre non trouvé"));
|
||||
|
||||
Evenement evenement =
|
||||
evenementRepository
|
||||
.findByIdOptional(evenementId)
|
||||
.orElseThrow(() -> new NotFoundException("Événement non trouvé"));
|
||||
|
||||
// Vérifier si déjà soumis
|
||||
Optional<FeedbackEvenement> existant =
|
||||
feedbackRepository.findByMembreAndEvenement(membre.getId(), evenementId);
|
||||
if (existant.isPresent()) {
|
||||
throw new IllegalStateException("Vous avez déjà soumis un feedback pour cet événement");
|
||||
}
|
||||
|
||||
// Vérifier que le membre était inscrit
|
||||
boolean etaitInscrit =
|
||||
inscriptionRepository.isMembreInscrit(membre.getId(), evenementId);
|
||||
if (!etaitInscrit) {
|
||||
throw new IllegalStateException(
|
||||
"Seuls les participants peuvent donner un feedback");
|
||||
}
|
||||
|
||||
// Vérifier que l'événement est terminé
|
||||
if (evenement.getDateFin() == null || evenement.getDateFin().isAfter(LocalDateTime.now())) {
|
||||
throw new IllegalStateException(
|
||||
"Vous ne pouvez donner un feedback qu'après la fin de l'événement");
|
||||
}
|
||||
|
||||
FeedbackEvenement feedback =
|
||||
FeedbackEvenement.builder()
|
||||
.membre(membre)
|
||||
.evenement(evenement)
|
||||
.note(note)
|
||||
.commentaire(commentaire)
|
||||
.dateFeedback(LocalDateTime.now())
|
||||
.moderationStatut(FeedbackEvenement.ModerationStatut.PUBLIE.name())
|
||||
.build();
|
||||
|
||||
feedbackRepository.persist(feedback);
|
||||
LOG.infof(
|
||||
"Feedback créé: membre=%s, événement=%s, note=%d",
|
||||
membre.getEmail(), evenement.getTitre(), note);
|
||||
return feedback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Liste les feedbacks d'un événement
|
||||
*
|
||||
* @param evenementId UUID de l'événement
|
||||
* @return Liste des feedbacks publiés
|
||||
*/
|
||||
public List<FeedbackEvenement> getFeedbacks(UUID evenementId) {
|
||||
return feedbackRepository.findPubliesByEvenement(evenementId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcule les statistiques de feedback pour un événement
|
||||
*
|
||||
* @param evenementId UUID de l'événement
|
||||
* @return Map contenant noteMovenne et nombreFeedbacks
|
||||
*/
|
||||
public Map<String, Object> getStatistiquesFeedback(UUID evenementId) {
|
||||
Double noteMoyenne = feedbackRepository.calculateAverageNote(evenementId);
|
||||
long nombreFeedbacks = feedbackRepository.countPubliesByEvenement(evenementId);
|
||||
|
||||
return Map.of(
|
||||
"noteMoyenne", noteMoyenne,
|
||||
"nombreFeedbacks", nombreFeedbacks);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
-- Migration V7: Création table feedbacks_evenement
|
||||
-- Auteur: UnionFlow Team
|
||||
-- Date: 2026-03-16
|
||||
-- Description: Ajoute la table pour les feedbacks/évaluations des événements
|
||||
|
||||
-- Table feedbacks_evenement (évaluations post-événement)
|
||||
CREATE TABLE IF NOT EXISTS feedbacks_evenement (
|
||||
-- Colonnes BaseEntity
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
date_creation TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
date_modification TIMESTAMP,
|
||||
cree_par VARCHAR(255),
|
||||
modifie_par VARCHAR(255),
|
||||
version INTEGER NOT NULL DEFAULT 0,
|
||||
actif BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
|
||||
-- Relations
|
||||
membre_id UUID NOT NULL,
|
||||
evenement_id UUID NOT NULL,
|
||||
|
||||
-- Données de feedback
|
||||
note INTEGER NOT NULL CHECK (note >= 1 AND note <= 5),
|
||||
commentaire VARCHAR(1000),
|
||||
date_feedback TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
-- Modération
|
||||
moderation_statut VARCHAR(20) NOT NULL DEFAULT 'PUBLIE' CHECK (moderation_statut IN ('PUBLIE', 'EN_ATTENTE', 'REJETE')),
|
||||
raison_moderation VARCHAR(500),
|
||||
|
||||
-- Contraintes
|
||||
CONSTRAINT fk_feedback_membre FOREIGN KEY (membre_id) REFERENCES utilisateurs(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_feedback_evenement FOREIGN KEY (evenement_id) REFERENCES evenements(id) ON DELETE CASCADE,
|
||||
CONSTRAINT uk_feedback_membre_evenement UNIQUE (membre_id, evenement_id)
|
||||
);
|
||||
|
||||
-- Index pour performance
|
||||
CREATE INDEX IF NOT EXISTS idx_feedback_membre ON feedbacks_evenement(membre_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_feedback_evenement ON feedbacks_evenement(evenement_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_feedback_date ON feedbacks_evenement(date_feedback);
|
||||
CREATE INDEX IF NOT EXISTS idx_feedback_moderation ON feedbacks_evenement(moderation_statut);
|
||||
|
||||
-- Commentaires
|
||||
COMMENT ON TABLE feedbacks_evenement IS 'Feedbacks et évaluations des participants après un événement';
|
||||
COMMENT ON COLUMN feedbacks_evenement.note IS 'Note de 1 à 5 étoiles';
|
||||
COMMENT ON COLUMN feedbacks_evenement.moderation_statut IS 'Statut de modération: PUBLIE, EN_ATTENTE, REJETE';
|
||||
COMMENT ON COLUMN feedbacks_evenement.date_feedback IS 'Date de soumission du feedback';
|
||||
Reference in New Issue
Block a user