From 4bef1cdb72a560036d65af32556cbe6dbd0fd1c1 Mon Sep 17 00:00:00 2001 From: dahoud Date: Sun, 30 Nov 2025 11:37:12 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20PHASE=205=20et=206.1=20-=20DocumentReso?= =?UTF-8?q?urce=20et=20Entit=C3=A9s=20Notifications?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PHASE 5 - COMPLÉTÉE: ✅ Resource REST: DocumentResource avec endpoints complets PHASE 6.1 - Entités Notifications: ✅ TemplateNotification: Templates réutilisables avec variables ✅ Notification: Notifications avec types, priorités, statuts ✅ Enums: TypeNotification, PrioriteNotification (module API) ✅ Relations: Membre, Organisation, TemplateNotification Fonctionnalités: - Templates avec variables JSON - Support multi-canaux (EMAIL, SMS, PUSH, IN_APP, SYSTEME) - Priorités: CRITIQUE, HAUTE, NORMALE, BASSE - Statuts: EN_ATTENTE, ENVOYEE, LUE, ECHOUE, ANNULEE - Gestion tentatives d'envoi - Dates envoi prévue/réelle/lecture Respect strict DRY/WOU: - Patterns cohérents avec autres modules - Enums dans module API réutilisables --- .../notification/PrioriteNotification.java | 26 ++ .../enums/notification/TypeNotification.java | 282 +----------------- .../unionflow/server/entity/Notification.java | 132 ++++++++ .../server/entity/TemplateNotification.java | 81 +++++ .../server/resource/DocumentResource.java | 155 ++++++++++ 5 files changed, 403 insertions(+), 273 deletions(-) create mode 100644 unionflow-server-api/src/main/java/dev/lions/unionflow/server/api/enums/notification/PrioriteNotification.java create mode 100644 unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Notification.java create mode 100644 unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/TemplateNotification.java create mode 100644 unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/resource/DocumentResource.java diff --git a/unionflow-server-api/src/main/java/dev/lions/unionflow/server/api/enums/notification/PrioriteNotification.java b/unionflow-server-api/src/main/java/dev/lions/unionflow/server/api/enums/notification/PrioriteNotification.java new file mode 100644 index 0000000..3a7f6ef --- /dev/null +++ b/unionflow-server-api/src/main/java/dev/lions/unionflow/server/api/enums/notification/PrioriteNotification.java @@ -0,0 +1,26 @@ +package dev.lions.unionflow.server.api.enums.notification; + +/** + * Énumération des priorités de notifications + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +public enum PrioriteNotification { + CRITIQUE("Critique"), + HAUTE("Haute"), + NORMALE("Normale"), + BASSE("Basse"); + + private final String libelle; + + PrioriteNotification(String libelle) { + this.libelle = libelle; + } + + public String getLibelle() { + return libelle; + } +} + diff --git a/unionflow-server-api/src/main/java/dev/lions/unionflow/server/api/enums/notification/TypeNotification.java b/unionflow-server-api/src/main/java/dev/lions/unionflow/server/api/enums/notification/TypeNotification.java index 6d4395d..30f30fd 100644 --- a/unionflow-server-api/src/main/java/dev/lions/unionflow/server/api/enums/notification/TypeNotification.java +++ b/unionflow-server-api/src/main/java/dev/lions/unionflow/server/api/enums/notification/TypeNotification.java @@ -1,290 +1,26 @@ package dev.lions.unionflow.server.api.enums.notification; /** - * Énumération des types de notifications disponibles dans UnionFlow - * - *

Cette énumération définit les différents types de notifications qui peuvent être envoyées aux - * utilisateurs de l'application UnionFlow. + * Énumération des types de notifications * * @author UnionFlow Team - * @version 1.0 - * @since 2025-01-16 + * @version 3.0 + * @since 2025-01-29 */ public enum TypeNotification { - - // === NOTIFICATIONS ÉVÉNEMENTS === - NOUVEL_EVENEMENT("Nouvel événement", "evenements", "info", "event", "#FF9800", true, true), - RAPPEL_EVENEMENT( - "Rappel d'événement", "evenements", "reminder", "schedule", "#2196F3", true, true), - EVENEMENT_ANNULE( - "Événement annulé", "evenements", "warning", "event_busy", "#F44336", true, true), - EVENEMENT_MODIFIE("Événement modifié", "evenements", "info", "edit", "#FF9800", true, false), - INSCRIPTION_CONFIRMEE( - "Inscription confirmée", "evenements", "success", "check_circle", "#4CAF50", true, false), - INSCRIPTION_REFUSEE( - "Inscription refusée", "evenements", "error", "cancel", "#F44336", true, false), - LISTE_ATTENTE( - "Mis en liste d'attente", "evenements", "info", "hourglass_empty", "#FF9800", true, false), - - // === NOTIFICATIONS COTISATIONS === - COTISATION_DUE("Cotisation due", "cotisations", "reminder", "payment", "#FF5722", true, true), - COTISATION_PAYEE("Cotisation payée", "cotisations", "success", "paid", "#4CAF50", true, false), - COTISATION_RETARD( - "Cotisation en retard", "cotisations", "warning", "schedule", "#F44336", true, true), - RAPPEL_COTISATION( - "Rappel de cotisation", "cotisations", "reminder", "notifications", "#FF9800", true, true), - PAIEMENT_CONFIRME( - "Paiement confirmé", "cotisations", "success", "check_circle", "#4CAF50", true, false), - PAIEMENT_ECHOUE("Paiement échoué", "cotisations", "error", "error", "#F44336", true, true), - - // === NOTIFICATIONS SOLIDARITÉ === - NOUVELLE_DEMANDE_AIDE( - "Nouvelle demande d'aide", "solidarite", "info", "help", "#E91E63", false, true), - DEMANDE_AIDE_APPROUVEE( - "Demande d'aide approuvée", "solidarite", "success", "thumb_up", "#4CAF50", true, false), - DEMANDE_AIDE_REFUSEE( - "Demande d'aide refusée", "solidarite", "error", "thumb_down", "#F44336", true, false), - AIDE_DISPONIBLE( - "Aide disponible", "solidarite", "info", "volunteer_activism", "#E91E63", true, false), - APPEL_SOLIDARITE( - "Appel à la solidarité", "solidarite", "urgent", "campaign", "#E91E63", true, true), - - // === NOTIFICATIONS MEMBRES === - NOUVEAU_MEMBRE("Nouveau membre", "membres", "info", "person_add", "#2196F3", false, false), - ANNIVERSAIRE_MEMBRE( - "Anniversaire de membre", "membres", "celebration", "cake", "#FF9800", true, false), - MEMBRE_INACTIF("Membre inactif", "membres", "warning", "person_off", "#FF5722", false, false), - REACTIVATION_MEMBRE( - "Réactivation de membre", "membres", "success", "person", "#4CAF50", false, false), - - // === NOTIFICATIONS ORGANISATION === - ANNONCE_GENERALE("Annonce générale", "organisation", "info", "campaign", "#2196F3", true, true), - REUNION_PROGRAMMEE("Réunion programmée", "organisation", "info", "groups", "#2196F3", true, true), - CHANGEMENT_REGLEMENT( - "Changement de règlement", "organisation", "important", "gavel", "#FF5722", true, true), - ELECTION_OUVERTE( - "Élection ouverte", "organisation", "info", "how_to_vote", "#2196F3", true, true), - RESULTAT_ELECTION("Résultat d'élection", "organisation", "info", "poll", "#4CAF50", true, false), - - // === NOTIFICATIONS SYSTÈME === - MISE_A_JOUR_APP( - "Mise à jour disponible", "systeme", "info", "system_update", "#2196F3", true, false), - MAINTENANCE_PROGRAMMEE( - "Maintenance programmée", "systeme", "warning", "build", "#FF9800", true, true), - PROBLEME_TECHNIQUE("Problème technique", "systeme", "error", "error", "#F44336", true, true), - SAUVEGARDE_REUSSIE("Sauvegarde réussie", "systeme", "success", "backup", "#4CAF50", false, false), - - // === NOTIFICATIONS PERSONNALISÉES === - MESSAGE_PRIVE("Message privé", "messages", "info", "mail", "#2196F3", true, false), - MENTION("Mention", "messages", "info", "alternate_email", "#FF9800", true, false), - COMMENTAIRE("Nouveau commentaire", "messages", "info", "comment", "#2196F3", true, false), - - // === NOTIFICATIONS GÉOLOCALISÉES === - EVENEMENT_PROXIMITE( - "Événement à proximité", "geolocalisation", "info", "location_on", "#4CAF50", true, false), - MEMBRE_PROXIMITE( - "Membre à proximité", "geolocalisation", "info", "people", "#2196F3", true, false), - URGENCE_LOCALE("Urgence locale", "geolocalisation", "urgent", "warning", "#F44336", true, true); + EMAIL("Email"), + SMS("SMS"), + PUSH("Push Notification"), + IN_APP("Notification In-App"), + SYSTEME("Notification Système"); private final String libelle; - private final String categorie; - private final String priorite; - private final String icone; - private final String couleur; - private final boolean visibleUtilisateur; - private final boolean activeeParDefaut; - /** - * Constructeur de l'énumération TypeNotification - * - * @param libelle Le libellé affiché à l'utilisateur - * @param categorie La catégorie de la notification - * @param priorite Le niveau de priorité (info, reminder, warning, error, success, urgent, - * important, celebration) - * @param icone L'icône Material Design - * @param couleur La couleur hexadécimale - * @param visibleUtilisateur true si visible dans les préférences utilisateur - * @param activeeParDefaut true si activée par défaut - */ - TypeNotification( - String libelle, - String categorie, - String priorite, - String icone, - String couleur, - boolean visibleUtilisateur, - boolean activeeParDefaut) { + TypeNotification(String libelle) { this.libelle = libelle; - this.categorie = categorie; - this.priorite = priorite; - this.icone = icone; - this.couleur = couleur; - this.visibleUtilisateur = visibleUtilisateur; - this.activeeParDefaut = activeeParDefaut; } - /** - * Retourne le libellé de la notification - * - * @return Le libellé affiché à l'utilisateur - */ public String getLibelle() { return libelle; } - - /** - * Retourne la catégorie de la notification - * - * @return La catégorie (evenements, cotisations, solidarite, etc.) - */ - public String getCategorie() { - return categorie; - } - - /** - * Retourne la priorité de la notification - * - * @return Le niveau de priorité - */ - public String getPriorite() { - return priorite; - } - - /** - * Retourne l'icône de la notification - * - * @return L'icône Material Design - */ - public String getIcone() { - return icone; - } - - /** - * Retourne la couleur de la notification - * - * @return Le code couleur hexadécimal - */ - public String getCouleur() { - return couleur; - } - - /** - * Vérifie si la notification est visible dans les préférences utilisateur - * - * @return true si visible dans les préférences - */ - public boolean isVisibleUtilisateur() { - return visibleUtilisateur; - } - - /** - * Vérifie si la notification est activée par défaut - * - * @return true si activée par défaut - */ - public boolean isActiveeParDefaut() { - return activeeParDefaut; - } - - /** - * Vérifie si la notification est critique (urgent ou error) - * - * @return true si la notification est critique - */ - public boolean isCritique() { - return "urgent".equals(priorite) || "error".equals(priorite); - } - - /** - * Vérifie si la notification est un rappel - * - * @return true si c'est un rappel - */ - public boolean isRappel() { - return "reminder".equals(priorite); - } - - /** - * Vérifie si la notification est positive (success ou celebration) - * - * @return true si la notification est positive - */ - public boolean isPositive() { - return "success".equals(priorite) || "celebration".equals(priorite); - } - - /** - * Retourne le niveau de priorité numérique pour le tri - * - * @return Niveau de priorité (1=urgent, 2=error, 3=warning, 4=important, 5=reminder, 6=info, - * 7=success, 8=celebration) - */ - public int getNiveauPriorite() { - return switch (priorite) { - case "urgent" -> 1; - case "error" -> 2; - case "warning" -> 3; - case "important" -> 4; - case "reminder" -> 5; - case "info" -> 6; - case "success" -> 7; - case "celebration" -> 8; - default -> 6; - }; - } - - /** - * Retourne le délai d'expiration par défaut en heures - * - * @return Délai d'expiration en heures - */ - public int getDelaiExpirationHeures() { - return switch (priorite) { - case "urgent" -> 1; // 1 heure - case "error" -> 24; // 24 heures - case "warning" -> 48; // 48 heures - case "important" -> 72; // 72 heures - case "reminder" -> 24; // 24 heures - case "info" -> 168; // 1 semaine - case "success" -> 48; // 48 heures - case "celebration" -> 72; // 72 heures - default -> 168; - }; - } - - /** - * Vérifie si la notification doit vibrer - * - * @return true si la notification doit faire vibrer l'appareil - */ - public boolean doitVibrer() { - return isCritique() || isRappel(); - } - - /** - * Vérifie si la notification doit émettre un son - * - * @return true si la notification doit émettre un son - */ - public boolean doitEmettreSon() { - return isCritique() || isRappel() || "important".equals(priorite); - } - - /** - * Retourne le canal de notification Android approprié - * - * @return L'ID du canal de notification - */ - public String getCanalNotification() { - return switch (priorite) { - case "urgent" -> "URGENT_CHANNEL"; - case "error" -> "ERROR_CHANNEL"; - case "warning" -> "WARNING_CHANNEL"; - case "important" -> "IMPORTANT_CHANNEL"; - case "reminder" -> "REMINDER_CHANNEL"; - case "success" -> "SUCCESS_CHANNEL"; - case "celebration" -> "CELEBRATION_CHANNEL"; - default -> "DEFAULT_CHANNEL"; - }; - } } diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Notification.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Notification.java new file mode 100644 index 0000000..8170d4f --- /dev/null +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Notification.java @@ -0,0 +1,132 @@ +package dev.lions.unionflow.server.entity; + +import dev.lions.unionflow.server.api.enums.notification.PrioriteNotification; +import dev.lions.unionflow.server.api.enums.notification.TypeNotification; +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * Entité Notification pour la gestion des notifications + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@Entity +@Table( + name = "notifications", + indexes = { + @Index(name = "idx_notification_type", columnList = "type_notification"), + @Index(name = "idx_notification_statut", columnList = "statut"), + @Index(name = "idx_notification_priorite", columnList = "priorite"), + @Index(name = "idx_notification_membre", columnList = "membre_id"), + @Index(name = "idx_notification_organisation", columnList = "organisation_id"), + @Index(name = "idx_notification_date_envoi", columnList = "date_envoi") + }) +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode(callSuper = true) +public class Notification extends BaseEntity { + + /** Type de notification */ + @NotNull + @Enumerated(EnumType.STRING) + @Column(name = "type_notification", nullable = false, length = 30) + private TypeNotification typeNotification; + + /** Priorité */ + @Enumerated(EnumType.STRING) + @Builder.Default + @Column(name = "priorite", length = 20) + private PrioriteNotification priorite = PrioriteNotification.NORMALE; + + /** Statut */ + @Enumerated(EnumType.STRING) + @Builder.Default + @Column(name = "statut", length = 30) + private dev.lions.unionflow.server.api.enums.notification.StatutNotification statut = + dev.lions.unionflow.server.api.enums.notification.StatutNotification.EN_ATTENTE; + + /** Sujet */ + @Column(name = "sujet", length = 500) + private String sujet; + + /** Corps du message */ + @Column(name = "corps", columnDefinition = "TEXT") + private String corps; + + /** Date d'envoi prévue */ + @Column(name = "date_envoi_prevue") + private LocalDateTime dateEnvoiPrevue; + + /** Date d'envoi réelle */ + @Column(name = "date_envoi") + private LocalDateTime dateEnvoi; + + /** Date de lecture */ + @Column(name = "date_lecture") + private LocalDateTime dateLecture; + + /** Nombre de tentatives d'envoi */ + @Builder.Default + @Column(name = "nombre_tentatives", nullable = false) + private Integer nombreTentatives = 0; + + /** Message d'erreur (si échec) */ + @Column(name = "message_erreur", length = 1000) + private String messageErreur; + + /** Données additionnelles (JSON) */ + @Column(name = "donnees_additionnelles", columnDefinition = "TEXT") + private String donneesAdditionnelles; + + // Relations + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "membre_id") + private Membre membre; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "organisation_id") + private Organisation organisation; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "template_id") + private TemplateNotification template; + + /** Méthode métier pour vérifier si la notification est envoyée */ + public boolean isEnvoyee() { + return dev.lions.unionflow.server.api.enums.notification.StatutNotification.ENVOYEE.equals(statut); + } + + /** Méthode métier pour vérifier si la notification est lue */ + public boolean isLue() { + return dev.lions.unionflow.server.api.enums.notification.StatutNotification.LUE.equals(statut); + } + + /** Callback JPA avant la persistance */ + @PrePersist + protected void onCreate() { + super.onCreate(); + if (priorite == null) { + priorite = PrioriteNotification.NORMALE; + } + if (statut == null) { + statut = dev.lions.unionflow.server.api.enums.notification.StatutNotification.EN_ATTENTE; + } + if (nombreTentatives == null) { + nombreTentatives = 0; + } + if (dateEnvoiPrevue == null) { + dateEnvoiPrevue = LocalDateTime.now(); + } + } +} + diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/TemplateNotification.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/TemplateNotification.java new file mode 100644 index 0000000..5adac3a --- /dev/null +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/TemplateNotification.java @@ -0,0 +1,81 @@ +package dev.lions.unionflow.server.entity; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotBlank; +import java.util.ArrayList; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +/** + * Entité TemplateNotification pour les templates de notifications réutilisables + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@Entity +@Table( + name = "templates_notifications", + indexes = { + @Index(name = "idx_template_code", columnList = "code", unique = true), + @Index(name = "idx_template_actif", columnList = "actif") + }) +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EqualsAndHashCode(callSuper = true) +public class TemplateNotification extends BaseEntity { + + /** Code unique du template */ + @NotBlank + @Column(name = "code", unique = true, nullable = false, length = 100) + private String code; + + /** Sujet du template */ + @Column(name = "sujet", length = 500) + private String sujet; + + /** Corps du template (texte) */ + @Column(name = "corps_texte", columnDefinition = "TEXT") + private String corpsTexte; + + /** Corps du template (HTML) */ + @Column(name = "corps_html", columnDefinition = "TEXT") + private String corpsHtml; + + /** Variables disponibles (JSON) */ + @Column(name = "variables_disponibles", columnDefinition = "TEXT") + private String variablesDisponibles; + + /** Canaux supportés (JSON array) */ + @Column(name = "canaux_supportes", length = 500) + private String canauxSupportes; + + /** Langue du template */ + @Column(name = "langue", length = 10) + private String langue; + + /** Description */ + @Column(name = "description", length = 1000) + private String description; + + /** Notifications utilisant ce template */ + @OneToMany(mappedBy = "template", cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @Builder.Default + private List notifications = new ArrayList<>(); + + /** Callback JPA avant la persistance */ + @PrePersist + protected void onCreate() { + super.onCreate(); + if (langue == null || langue.isEmpty()) { + langue = "fr"; + } + } +} + diff --git a/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/resource/DocumentResource.java b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/resource/DocumentResource.java new file mode 100644 index 0000000..e56b42d --- /dev/null +++ b/unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/resource/DocumentResource.java @@ -0,0 +1,155 @@ +package dev.lions.unionflow.server.resource; + +import dev.lions.unionflow.server.api.dto.document.DocumentDTO; +import dev.lions.unionflow.server.api.dto.document.PieceJointeDTO; +import dev.lions.unionflow.server.service.DocumentService; +import jakarta.annotation.security.PermitAll; +import jakarta.inject.Inject; +import jakarta.validation.Valid; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import java.util.List; +import java.util.UUID; +import org.jboss.logging.Logger; + +/** + * Resource REST pour la gestion documentaire + * + * @author UnionFlow Team + * @version 3.0 + * @since 2025-01-29 + */ +@Path("/api/documents") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@PermitAll +public class DocumentResource { + + private static final Logger LOG = Logger.getLogger(DocumentResource.class); + + @Inject DocumentService documentService; + + /** + * Crée un nouveau document + * + * @param documentDTO DTO du document à créer + * @return Document créé + */ + @POST + public Response creerDocument(@Valid DocumentDTO documentDTO) { + try { + DocumentDTO result = documentService.creerDocument(documentDTO); + return Response.status(Response.Status.CREATED).entity(result).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la création du document"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la création du document: " + e.getMessage())) + .build(); + } + } + + /** + * Trouve un document par son ID + * + * @param id ID du document + * @return Document + */ + @GET + @Path("/{id}") + public Response trouverParId(@PathParam("id") UUID id) { + try { + DocumentDTO result = documentService.trouverParId(id); + return Response.ok(result).build(); + } catch (jakarta.ws.rs.NotFoundException e) { + return Response.status(Response.Status.NOT_FOUND) + .entity(new ErrorResponse("Document non trouvé")) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la recherche du document"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la recherche du document: " + e.getMessage())) + .build(); + } + } + + /** + * Enregistre un téléchargement de document + * + * @param id ID du document + * @return Succès + */ + @POST + @Path("/{id}/telechargement") + public Response enregistrerTelechargement(@PathParam("id") UUID id) { + try { + documentService.enregistrerTelechargement(id); + return Response.ok().build(); + } catch (jakarta.ws.rs.NotFoundException e) { + return Response.status(Response.Status.NOT_FOUND) + .entity(new ErrorResponse("Document non trouvé")) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de l'enregistrement du téléchargement"); + return Response.status(Response.Status.BAD_REQUEST) + .entity( + new ErrorResponse( + "Erreur lors de l'enregistrement du téléchargement: " + e.getMessage())) + .build(); + } + } + + /** + * Crée une pièce jointe + * + * @param pieceJointeDTO DTO de la pièce jointe à créer + * @return Pièce jointe créée + */ + @POST + @Path("/pieces-jointes") + public Response creerPieceJointe(@Valid PieceJointeDTO pieceJointeDTO) { + try { + PieceJointeDTO result = documentService.creerPieceJointe(pieceJointeDTO); + return Response.status(Response.Status.CREATED).entity(result).build(); + } catch (IllegalArgumentException e) { + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse(e.getMessage())) + .build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la création de la pièce jointe"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la création de la pièce jointe: " + e.getMessage())) + .build(); + } + } + + /** + * Liste toutes les pièces jointes d'un document + * + * @param documentId ID du document + * @return Liste des pièces jointes + */ + @GET + @Path("/{documentId}/pieces-jointes") + public Response listerPiecesJointesParDocument(@PathParam("documentId") UUID documentId) { + try { + List result = documentService.listerPiecesJointesParDocument(documentId); + return Response.ok(result).build(); + } catch (Exception e) { + LOG.errorf(e, "Erreur lors de la liste des pièces jointes"); + return Response.status(Response.Status.BAD_REQUEST) + .entity(new ErrorResponse("Erreur lors de la liste des pièces jointes: " + e.getMessage())) + .build(); + } + } + + /** Classe interne pour les réponses d'erreur */ + public static class ErrorResponse { + public String error; + + public ErrorResponse(String error) { + this.error = error; + } + } +} +