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:
dahoud
2026-03-16 20:11:03 +00:00
parent 3be01e28a7
commit a7bcaf9277
6 changed files with 690 additions and 0 deletions

View File

@@ -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);
}
}