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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user