Files
unionflow-server-impl-quarkus/src/main/java/dev/lions/unionflow/server/entity/Message.java
dahoud 719d45e1fe feat(messaging): module messagerie unifié avec contact policies + member blocks
Refactor complet : fusion de Conversation + Message en un module Messaging unique
avec ContactPolicy (règles qui-peut-parler-à-qui) et MemberBlock (blocages utilisateur).

- Migration V28 : tables conversations/conversation_participants/messages/
  contact_policies/member_blocks
- Nouvelles entités : ContactPolicy, ConversationParticipant, MemberBlock
  (Conversation/Message mises à jour avec relations)
- Nouvelles repositories : ContactPolicyRepository, ConversationParticipantRepository,
  MemberBlockRepository
- MessagingResource (nouveau) remplace ConversationResource + MessageResource
- MessagingService (nouveau) remplace ConversationService + MessageService
  avec vérifications appartenance org + policies + blocages avant envoi
- Anciens fichiers Conversation/Message Resource/Service/Tests supprimés
2026-04-15 20:23:04 +00:00

141 lines
4.5 KiB
Java

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.
*
* <p>Supporte trois types de contenu :
* <ul>
* <li>{@link TypeContenu#TEXTE} — message texte classique</li>
* <li>{@link TypeContenu#VOCAL} — note vocale (Opus/AAC), stockée sur object storage.
* Champs {@code urlFichier} + {@code dureeAudio} obligatoires.</li>
* <li>{@link TypeContenu#IMAGE} — image JPEG/PNG. Champ {@code urlFichier} obligatoire.</li>
* </ul>
*
* <p>La suppression est douce : {@code supprimeLe} est renseigné au lieu de
* supprimer la ligne. Le contenu devient {@code "[Message supprimé]"}.
*
* <p>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;
}
}