diff --git a/src/main/java/dev/lions/unionflow/server/entity/FeedbackEvenement.java b/src/main/java/dev/lions/unionflow/server/entity/FeedbackEvenement.java new file mode 100644 index 0000000..b90d5d3 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/entity/FeedbackEvenement.java @@ -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); + } +} diff --git a/src/main/java/dev/lions/unionflow/server/repository/FeedbackEvenementRepository.java b/src/main/java/dev/lions/unionflow/server/repository/FeedbackEvenementRepository.java new file mode 100644 index 0000000..97dd8db --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/repository/FeedbackEvenementRepository.java @@ -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 { + + /** + * 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 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 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 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 findEnAttente() { + return find( + "moderationStatut = 'EN_ATTENTE' and actif = true order by dateFeedback desc") + .list(); + } +} diff --git a/src/main/java/dev/lions/unionflow/server/repository/InscriptionEvenementRepository.java b/src/main/java/dev/lions/unionflow/server/repository/InscriptionEvenementRepository.java new file mode 100644 index 0000000..9cd4733 --- /dev/null +++ b/src/main/java/dev/lions/unionflow/server/repository/InscriptionEvenementRepository.java @@ -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 { + + /** + * 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 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 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 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 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); + } +} diff --git a/src/main/java/dev/lions/unionflow/server/resource/EvenementResource.java b/src/main/java/dev/lions/unionflow/server/resource/EvenementResource.java index 119a4e0..b3824bc 100644 --- a/src/main/java/dev/lions/unionflow/server/resource/EvenementResource.java +++ b/src/main/java/dev/lions/unionflow/server/resource/EvenementResource.java @@ -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 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 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 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 feedbacks = evenementService.getFeedbacks(evenementId); + Map stats = evenementService.getStatistiquesFeedback(evenementId); + + return Response.ok( + Map.of( + "feedbacks", feedbacks, + "noteMoyenne", stats.get("noteMoyenne"), + "nombreFeedbacks", stats.get("nombreFeedbacks"))) + .build(); + } } diff --git a/src/main/java/dev/lions/unionflow/server/service/EvenementService.java b/src/main/java/dev/lions/unionflow/server/service/EvenementService.java index da729a8..382004e 100644 --- a/src/main/java/dev/lions/unionflow/server/service/EvenementService.java +++ b/src/main/java/dev/lions/unionflow/server/service/EvenementService.java @@ -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 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 getParticipants(UUID evenementId) { + return inscriptionRepository.findConfirmeesByEvenement(evenementId); + } + + /** + * Liste les inscriptions de l'utilisateur connecté + * + * @return Liste des inscriptions du membre + */ + public List 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 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 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 getStatistiquesFeedback(UUID evenementId) { + Double noteMoyenne = feedbackRepository.calculateAverageNote(evenementId); + long nombreFeedbacks = feedbackRepository.countPubliesByEvenement(evenementId); + + return Map.of( + "noteMoyenne", noteMoyenne, + "nombreFeedbacks", nombreFeedbacks); + } } diff --git a/src/main/resources/db/migration/V7__Create_Feedbacks_Evenement_Table.sql b/src/main/resources/db/migration/V7__Create_Feedbacks_Evenement_Table.sql new file mode 100644 index 0000000..378f50f --- /dev/null +++ b/src/main/resources/db/migration/V7__Create_Feedbacks_Evenement_Table.sql @@ -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';