# Documentation : Édition et Suppression de Messages **Date de création** : 2025-12-05 **Version** : 1.0.0 **Statut** : ✅ Implémenté ## Vue d'ensemble Ce document décrit l'implémentation complète de l'édition et de la suppression (soft delete) de messages dans le serveur de chat Veza. Ces fonctionnalités sont essentielles pour un système de chat moderne et respectent les meilleures pratiques de sécurité, permissions et cohérence temps réel. ## Table des matières 1. [Architecture](#architecture) 2. [Événements WebSocket](#événements-websocket) 3. [Permissions](#permissions) 4. [Base de données](#base-de-données) 5. [Services](#services) 6. [Exemples d'utilisation](#exemples-dutilisation) 7. [Conséquences UX](#conséquences-ux) 8. [Impact sur la recherche et pagination](#impact-sur-la-recherche-et-pagination) --- ## Architecture ### Composants principaux 1. **Migration SQL** (`migrations/005_message_edit_delete.sql`) - Ajoute `deleted_at` pour la traçabilité - Index pour les requêtes de nettoyage 2. **PermissionService** (`src/security/permission.rs`) - `can_edit_message()` : Vérifie les permissions d'édition - `can_delete_message()` : Vérifie les permissions de suppression 3. **MessageEditService** (`src/services/message_edit_service.rs`) - `edit_message()` : Édite un message avec validation - `delete_message()` : Supprime un message (soft delete) 4. **MessageRepository** (`src/repository/message_repository.rs`) - `update()` : Met à jour le contenu d'un message - `delete()` : Marque un message comme supprimé - `get_by_id_including_deleted()` : Récupère même les messages supprimés 5. **WebSocket Handlers** (`src/websocket/handler.rs`) - Gère les événements `EditMessage` et `DeleteMessage` - Broadcast les événements `MessageEdited` et `MessageDeleted` --- ## Événements WebSocket ### Inbound Events (Client → Serveur) #### EditMessage Édite un message existant. ```json { "type": "EditMessage", "message_id": "550e8400-e29b-41d4-a716-446655440000", "conversation_id": "660e8400-e29b-41d4-a716-446655440000", "new_content": "Nouveau contenu du message" } ``` **Règles de validation** : - `new_content` doit être différent du contenu précédent - `new_content` ne peut pas être vide (après trim) - `new_content` ne peut pas dépasser 4000 caractères - Le message ne doit pas être supprimé - L'utilisateur doit avoir les permissions d'édition #### DeleteMessage Supprime un message (soft delete). ```json { "type": "DeleteMessage", "message_id": "550e8400-e29b-41d4-a716-446655440000", "conversation_id": "660e8400-e29b-41d4-a716-446655440000" } ``` **Règles de validation** : - L'utilisateur doit avoir les permissions de suppression - L'opération est idempotente (supprimer un message déjà supprimé retourne OK) ### Outbound Events (Serveur → Client) #### MessageEdited Notifie tous les clients d'une conversation qu'un message a été édité. ```json { "type": "MessageEdited", "message_id": "550e8400-e29b-41d4-a716-446655440000", "conversation_id": "660e8400-e29b-41d4-a716-446655440000", "editor_id": "770e8400-e29b-41d4-a716-446655440000", "edited_at": "2025-12-05T10:30:00Z", "new_content": "Nouveau contenu du message" } ``` #### MessageDeleted Notifie tous les clients d'une conversation qu'un message a été supprimé. ```json { "type": "MessageDeleted", "message_id": "550e8400-e29b-41d4-a716-446655440000", "conversation_id": "660e8400-e29b-41d4-a716-446655440000", "deleter_id": "770e8400-e29b-41d4-a716-446655440000", "deleted_at": "2025-12-05T10:30:00Z" } ``` --- ## Permissions ### Règles d'édition Un utilisateur peut éditer un message si : 1. **Il est l'auteur du message** : L'auteur peut toujours éditer son propre message 2. **Il est admin/modérateur de la conversation** : Les admins et modérateurs peuvent éditer n'importe quel message dans leur conversation 3. **Le message n'est pas supprimé** : Un message supprimé ne peut jamais être édité ### Règles de suppression Un utilisateur peut supprimer un message si : 1. **Il est l'auteur du message** : L'auteur peut toujours supprimer son propre message 2. **Il est admin/modérateur de la conversation** : Les admins et modérateurs peuvent supprimer n'importe quel message dans leur conversation ### Limitations de temps Actuellement, il n'y a pas de limitation de temps pour l'édition ou la suppression. Un message peut être édité ou supprimé à tout moment tant que les permissions sont respectées. **Note** : Pour une implémentation future, on pourrait ajouter : - Fenêtre d'édition limitée (ex: 15 minutes après l'envoi) - Fenêtre de suppression limitée (ex: 5 minutes après l'envoi) --- ## Base de données ### Schéma La table `messages` contient les colonnes suivantes pour l'édition et la suppression : ```sql CREATE TABLE messages ( id UUID PRIMARY KEY, conversation_id UUID NOT NULL, sender_id UUID NOT NULL, content TEXT NOT NULL, -- ... autres colonnes ... is_edited BOOLEAN NOT NULL DEFAULT FALSE, is_deleted BOOLEAN NOT NULL DEFAULT FALSE, edited_at TIMESTAMPTZ, deleted_at TIMESTAMPTZ, updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); ``` ### Migration La migration `005_message_edit_delete.sql` ajoute : - `deleted_at` : Timestamp de suppression (pour la traçabilité) - Index sur `deleted_at` pour les requêtes de nettoyage - Index sur `edited_at` pour les requêtes de recherche ### Soft Delete Les messages ne sont **jamais supprimés physiquement** de la base de données. Au lieu de cela : - `is_deleted` est mis à `true` - `deleted_at` est mis à `NOW()` - Le contenu reste dans la base de données (pour audit futur) **Note** : Pour une implémentation future, on pourrait : - Créer une table `message_archive` pour stocker les messages supprimés - Vider le contenu du message après suppression (mettre `content` à `NULL` ou `""`) --- ## Services ### MessageEditService Service centralisé pour l'édition et la suppression de messages. #### `edit_message(user_id, message_id, new_content) -> Result` Édite un message avec validation complète. **Validation** : 1. Contenu non vide (après trim) 2. Longueur maximale (4000 caractères) 3. Contenu différent de l'original 4. Message non supprimé 5. Permissions d'édition **Mise à jour DB** : - `content` = nouveau contenu - `is_edited` = `true` - `edited_at` = `NOW()` - `updated_at` = `NOW()` #### `delete_message(user_id, message_id) -> Result` Supprime un message (soft delete). **Validation** : 1. Permissions de suppression **Mise à jour DB** : - `is_deleted` = `true` - `deleted_at` = `NOW()` - `updated_at` = `NOW()` **Idempotence** : Si le message est déjà supprimé, retourne le message tel quel sans erreur. --- ## Exemples d'utilisation ### Édition d'un message **Client** : ```json { "type": "EditMessage", "message_id": "550e8400-e29b-41d4-a716-446655440000", "conversation_id": "660e8400-e29b-41d4-a716-446655440000", "new_content": "Correction : Nouveau contenu" } ``` **Réponse (confirmation)** : ```json { "type": "ActionConfirmed", "action": "message_edited", "success": true } ``` **Broadcast (tous les clients de la conversation)** : ```json { "type": "MessageEdited", "message_id": "550e8400-e29b-41d4-a716-446655440000", "conversation_id": "660e8400-e29b-41d4-a716-446655440000", "editor_id": "770e8400-e29b-41d4-a716-446655440000", "edited_at": "2025-12-05T10:30:00Z", "new_content": "Correction : Nouveau contenu" } ``` ### Suppression d'un message **Client** : ```json { "type": "DeleteMessage", "message_id": "550e8400-e29b-41d4-a716-446655440000", "conversation_id": "660e8400-e29b-41d4-a716-446655440000" } ``` **Réponse (confirmation)** : ```json { "type": "ActionConfirmed", "action": "message_deleted", "success": true } ``` **Broadcast (tous les clients de la conversation)** : ```json { "type": "MessageDeleted", "message_id": "550e8400-e29b-41d4-a716-446655440000", "conversation_id": "660e8400-e29b-41d4-a716-446655440000", "deleter_id": "770e8400-e29b-41d4-a716-446655440000", "deleted_at": "2025-12-05T10:30:00Z" } ``` ### Gestion des erreurs **Permission refusée** : ```json { "type": "Error", "message": "Permissions insuffisantes pour edit_message dans la conversation 660e8400-e29b-41d4-a716-446655440000" } ``` **Message introuvable** : ```json { "type": "Error", "message": "Message 550e8400-e29b-41d4-a716-446655440000 introuvable" } ``` **Message supprimé (tentative d'édition)** : ```json { "type": "Error", "message": "Un message supprimé ne peut pas être édité" } ``` --- ## Conséquences UX ### Affichage des messages édités Lorsqu'un message est édité, l'interface utilisateur doit : 1. **Afficher le nouveau contenu** : Remplacer l'ancien contenu par le nouveau 2. **Indicateur visuel** : Afficher un indicateur "Édité" (ex: "✏️ Édité") 3. **Timestamp d'édition** : Optionnellement afficher `edited_at` au survol 4. **Historique** : Pour une implémentation future, on pourrait afficher l'historique des éditions **Exemple d'affichage** : ``` [Utilisateur] Message original ✏️ Édité ``` ### Affichage des messages supprimés Lorsqu'un message est supprimé, l'interface utilisateur doit : 1. **Placeholder** : Afficher un placeholder comme "Message supprimé" ou "Ce message a été supprimé" 2. **Style visuel** : Utiliser un style atténué (gris, italique) 3. **Informations limitées** : Ne pas afficher le contenu original 4. **Timestamp** : Optionnellement afficher `deleted_at` **Exemple d'affichage** : ``` [Utilisateur] Ce message a été supprimé ``` ### Cohérence multi-device Les événements WebSocket garantissent que : - Tous les clients connectés à la conversation reçoivent les mises à jour en temps réel - Les modifications sont synchronisées instantanément - Pas besoin de rafraîchir la page --- ## Impact sur la recherche et pagination ### Recherche Les messages supprimés sont **exclus** des résultats de recherche par défaut. **Requête SQL** : ```sql SELECT * FROM messages WHERE conversation_id = $1 AND is_deleted = false AND content ILIKE $2 ORDER BY created_at DESC; ``` **Note** : Pour une implémentation future, on pourrait : - Permettre aux admins de rechercher dans les messages supprimés - Créer une vue `messages_active` qui exclut automatiquement les messages supprimés ### Pagination Les messages supprimés sont **exclus** de la pagination par défaut. **Requête SQL** : ```sql SELECT * FROM messages WHERE conversation_id = $1 AND is_deleted = false ORDER BY created_at DESC LIMIT $2 OFFSET $3; ``` **Placeholder dans la liste** : Si un message est supprimé pendant qu'un utilisateur consulte l'historique, il peut être remplacé par un placeholder dans la liste. ### Impact sur les métriques - Les messages supprimés ne sont pas comptés dans les statistiques de messages - Les messages édités sont comptés comme des messages normaux (pas de double comptage) --- ## Tests Les tests sont disponibles dans `tests/chat_edit_delete.rs` : - ✅ Édition par l'auteur - ✅ Édition interdite pour un non-auteur - ✅ Édition interdite pour un message supprimé - ✅ Édition avec contenu identique interdite - ✅ Édition avec contenu vide interdite - ✅ Suppression par l'auteur - ✅ Suppression par un admin - ✅ Suppression interdite pour un non-auteur - ✅ Suppression idempotente - ✅ Validation de la longueur maximale **Note** : Les tests nécessitent une base de données de test et sont marqués avec `#[ignore]`. --- ## Améliorations futures 1. **Limitation de temps** : Fenêtre d'édition/suppression limitée 2. **Historique d'édition** : Stocker l'historique des modifications 3. **Archive de messages** : Table séparée pour les messages supprimés 4. **Raison de suppression** : Champ optionnel pour la raison de suppression (modération) 5. **Recherche dans les supprimés** : Permettre aux admins de rechercher dans les messages supprimés 6. **Notifications** : Notifier l'auteur lorsqu'un admin supprime son message --- ## Références - Migration : `migrations/005_message_edit_delete.sql` - Service : `src/services/message_edit_service.rs` - Permissions : `src/security/permission.rs` - Repository : `src/repository/message_repository.rs` - WebSocket : `src/websocket/handler.rs` - Tests : `tests/chat_edit_delete.rs`