package dev.lions.unionflow.server.entity; import dev.lions.unionflow.server.api.enums.messagerie.TypeContenu; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.FetchType; import jakarta.persistence.Index; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.PrePersist; import jakarta.persistence.Table; import jakarta.validation.constraints.NotNull; import java.time.LocalDateTime; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; /** * Message envoyé dans une conversation. * *
Supporte trois types de contenu : *
La suppression est douce : {@code supprimeLe} est renseigné au lieu de * supprimer la ligne. Le contenu devient {@code "[Message supprimé]"}. * *
Table : {@code messages} * * @author UnionFlow Team * @version 4.0 * @since 2026-04-13 */ @Entity @Table( name = "messages", indexes = { @Index(name = "idx_messages_conversation", columnList = "conversation_id"), @Index(name = "idx_messages_expediteur", columnList = "expediteur_id"), @Index(name = "idx_messages_date_creation", columnList = "date_creation"), @Index(name = "idx_messages_parent", columnList = "message_parent_id") } ) @Data @NoArgsConstructor @AllArgsConstructor @Builder @EqualsAndHashCode(callSuper = true) public class Message extends BaseEntity { @NotNull @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "conversation_id", nullable = false) private Conversation conversation; @NotNull @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "expediteur_id", nullable = false) private Membre expediteur; @Enumerated(EnumType.STRING) @Builder.Default @Column(name = "type_message", nullable = false, length = 20) private TypeContenu typeMessage = TypeContenu.TEXTE; /** Texte du message — null pour les vocaux/images. */ @Column(name = "contenu", columnDefinition = "TEXT") private String contenu; /** * URL du fichier audio (notes vocales) ou image. * Format : https://storage.lions.dev/chat/{conversationId}/{messageId}.opus */ @Column(name = "url_fichier", length = 500) private String urlFichier; /** Durée en secondes pour les notes vocales. */ @Column(name = "duree_audio") private Integer dureeAudio; /** * Transcription automatique du vocal — null en V1. * Sera renseigné par un service Speech-to-Text en V2. */ @Column(name = "transcription", columnDefinition = "TEXT") private String transcription; /** Message auquel celui-ci répond (threading léger). */ @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "message_parent_id") private Message messageParent; /** Date de suppression douce (null = message actif). */ @Column(name = "supprime_le") private LocalDateTime supprimeLe; @PrePersist @Override protected void onCreate() { super.onCreate(); if (typeMessage == null) { typeMessage = TypeContenu.TEXTE; } } // ── Méthodes métier ─────────────────────────────────────────────────────── /** Retourne true si le message a été supprimé par son auteur. */ public boolean estSupprime() { return supprimeLe != null; } /** Retourne true si c'est un message texte. */ public boolean estTextuel() { return TypeContenu.TEXTE.equals(typeMessage); } /** Retourne true si c'est une note vocale. */ public boolean estVocal() { return TypeContenu.VOCAL.equals(typeMessage); } /** * Supprime le message de façon douce. * Le contenu original est remplacé par un marqueur. */ public void supprimer() { this.supprimeLe = LocalDateTime.now(); this.contenu = "[Message supprimé]"; this.urlFichier = null; } }