# Système de Read Receipts - Veza Chat Server ## Vue d'ensemble Le système de read receipts permet de tracker quels messages ont été lus par quels utilisateurs dans une conversation. Cette fonctionnalité est essentielle pour fournir un feedback visuel aux utilisateurs (indicateurs "lu" / "non lu") et améliorer l'expérience utilisateur. **Statut** : ✅ **Opérationnel** (implémenté et testé) **Date d'implémentation** : 2025-12-05 --- ## Architecture ### Composants principaux 1. **Table de base de données** : `read_receipts` 2. **Manager** : `ReadReceiptManager` (`src/read_receipts.rs`) 3. **Handler WebSocket** : Intégration dans `src/websocket/handler.rs` 4. **Messages WebSocket** : `MarkAsRead` (inbound) et `MessageRead` (outbound) ### Schéma de base de données La table `read_receipts` est créée par la migration `003_read_receipts.sql` : ```sql CREATE TABLE read_receipts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), message_id UUID NOT NULL REFERENCES messages(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, conversation_id UUID NOT NULL REFERENCES conversations(id) ON DELETE CASCADE, read_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), UNIQUE(message_id, user_id) ); ``` **Index** : - `idx_read_receipts_message_id` : Recherche par message - `idx_read_receipts_user_id` : Recherche par utilisateur - `idx_read_receipts_conversation_id` : Recherche par conversation - `idx_read_receipts_conversation_user` : Requêtes fréquentes (dernière lecture) --- ## Contrat WebSocket ### Message Inbound : `MarkAsRead` Envoyé par le client pour marquer un message comme lu. ```json { "type": "MarkAsRead", "conversation_id": "uuid-de-la-conversation", "message_id": "uuid-du-message" } ``` **Validation côté serveur** : 1. Le message existe et appartient à la conversation indiquée 2. L'utilisateur est membre de la conversation 3. Le JWT est valide (vérifié automatiquement par le handler) **Réponses possibles** : - ✅ `ActionConfirmed` : Le message a été marqué comme lu - ❌ `Error` : Erreur de validation ou de permission ### Message Outbound : `MessageRead` Envoyé à tous les participants de la conversation lorsqu'un message est marqué comme lu. ```json { "type": "MessageRead", "message_id": "uuid-du-message", "user_id": "uuid-de-l-utilisateur-qui-a-lu", "conversation_id": "uuid-de-la-conversation", "read_at": "2025-12-05T10:30:00Z" } ``` **Broadcast** : Ce message est automatiquement diffusé à tous les clients connectés à la conversation (sauf l'utilisateur qui a initié l'action, qui reçoit `ActionConfirmed`). --- ## Comportement serveur ### Flux de traitement 1. **Réception** : Le client envoie `MarkAsRead` via WebSocket 2. **Validation** : - Vérification de l'existence du message - Vérification de l'appartenance du message à la conversation - Vérification de l'appartenance de l'utilisateur à la conversation 3. **Persistance** : - Création d'un nouveau read receipt si inexistant - Mise à jour du timestamp `read_at` si le read receipt existe déjà 4. **Notification** : - Broadcast de `MessageRead` à tous les participants - Envoi de `ActionConfirmed` au client initiateur ### Gestion des erreurs | Erreur | Code | Comportement | |--------|------|--------------| | Message inexistant | `not_found` | Retourne une erreur au client | | Message n'appartient pas à la conversation | `validation_error` | Retourne une erreur au client | | Utilisateur non membre | `unauthorized` | Retourne une erreur au client | | Erreur DB | `internal_error` | Log l'erreur, retourne une erreur générique au client | ### Logs structurés Les événements suivants sont loggés avec `tracing` : - ✅ Message marqué comme lu (info) - ✅ Read receipt créé (info) - ✅ Read receipt mis à jour (debug) - ❌ Erreurs de validation/permission (error) --- ## API du ReadReceiptManager ### Méthodes principales #### `mark_as_read(user_id, message_id, conversation_id)` Marque un message comme lu par un utilisateur. **Retourne** : `ReadReceipt` (créé ou mis à jour) #### `get_receipt(message_id, user_id)` Récupère le read receipt pour un message et un utilisateur spécifiques. **Retourne** : `Option` #### `get_receipts_for_message(message_id)` Récupère tous les read receipts pour un message (tous les utilisateurs qui l'ont lu). **Retourne** : `Vec` #### `get_message_status(message_id, user_id)` Récupère le statut de lecture d'un message pour un utilisateur. **Retourne** : `MessageReadStatus` (`Sent`, `Delivered`, ou `Read`) #### `is_user_in_conversation(user_id, conversation_id)` Vérifie si un utilisateur est membre d'une conversation. **Retourne** : `bool` #### `get_last_read_message(conversation_id, user_id)` Récupère l'ID du dernier message lu par un utilisateur dans une conversation. **Retourne** : `Option` #### `get_unread_count(conversation_id, user_id, last_read_message_id)` Calcule le nombre de messages non lus pour un utilisateur dans une conversation. **Retourne** : `i64` --- ## Prérequis ### Base de données 1. **Migration** : Exécuter `migrations/003_read_receipts.sql` 2. **Extensions PostgreSQL** : `uuid-ossp` (déjà requis par les migrations précédentes) ### Configuration Aucune configuration spécifique requise. Le système utilise le pool de connexions PostgreSQL déjà configuré. --- ## Tests ### Tests unitaires Les tests unitaires sont dans `src/read_receipts.rs` (module `tests`). **Exécution** : ```bash cd veza-chat-server cargo test --lib read_receipts -- --ignored ``` **Tests disponibles** : - `test_mark_as_read_creates_receipt` : Vérifie la création d'un read receipt - `test_mark_as_read_updates_existing` : Vérifie la mise à jour d'un read receipt existant - `test_get_receipt` : Vérifie la récupération d'un read receipt - `test_get_message_status` : Vérifie le statut de lecture - `test_get_receipts_for_message` : Vérifie la récupération de tous les read receipts d'un message ### Tests d'intégration Le test d'intégration est dans `tests/integration_test.rs` : `test_read_receipts_websocket`. **Exécution** : ```bash cd veza-chat-server # 1. Démarrer le serveur : cargo run # 2. Dans un autre terminal : cargo test --test integration_test test_read_receipts_websocket -- --ignored ``` **Prérequis** : - Serveur chat-server en cours d'exécution - Base de données avec migrations appliquées - Variable d'environnement `DATABASE_URL` configurée --- ## Exemples d'utilisation ### Côté client (WebSocket) ```javascript // Marquer un message comme lu const markAsRead = { type: "MarkAsRead", conversation_id: "conversation-uuid", message_id: "message-uuid" }; websocket.send(JSON.stringify(markAsRead)); // Écouter les notifications de lecture websocket.onmessage = (event) => { const message = JSON.parse(event.data); if (message.type === "MessageRead") { console.log(`Message ${message.message_id} lu par ${message.user_id}`); // Mettre à jour l'UI pour afficher l'indicateur "lu" } if (message.type === "ActionConfirmed" && message.action === "marked_as_read") { console.log("Message marqué comme lu avec succès"); } }; ``` ### Côté serveur (Rust) ```rust use chat_server::read_receipts::ReadReceiptManager; // Dans votre handler let manager = ReadReceiptManager::new(pool); // Marquer un message comme lu let receipt = manager .mark_as_read(user_id, message_id, conversation_id) .await?; // Vérifier le statut let status = manager .get_message_status(message_id, user_id) .await?; match status { MessageReadStatus::Read => println!("Message lu"), MessageReadStatus::Sent => println!("Message envoyé"), MessageReadStatus::Delivered => println!("Message livré"), } ``` --- ## Limitations et améliorations futures ### Limitations actuelles 1. **Statut "Delivered"** : Le système ne track pas encore le statut "livré" (message reçu mais pas encore lu). Actuellement, un message est soit `Sent` soit `Read`. 2. **Batch operations** : La méthode `mark_multiple_as_read` existe mais n'est pas encore exposée via WebSocket. ### Améliorations possibles 1. **Support "Delivered"** : Implémenter un système de tracking "delivered" (message reçu par le client mais pas encore ouvert). 2. **API REST** : Exposer une API REST pour : - Récupérer les read receipts d'un message - Récupérer le nombre de messages non lus - Marquer plusieurs messages comme lus en une requête 3. **Optimisations** : - Cache des read receipts fréquemment consultés - Batch processing pour les marquages multiples 4. **Métriques** : Ajouter des métriques Prometheus pour : - Nombre de read receipts créés par seconde - Temps moyen entre l'envoi et la lecture d'un message - Taux de lecture par conversation --- ## Migration depuis l'ancien système Si vous migrez depuis un système utilisant `i64` pour les IDs : 1. **Exécuter la migration** : `migrations/003_read_receipts.sql` 2. **Migrer les données existantes** (si applicable) : ```sql -- Exemple de migration de données (à adapter selon votre schéma) INSERT INTO read_receipts (message_id, user_id, conversation_id, read_at) SELECT message_id::uuid, user_id::uuid, conversation_id::uuid, read_at FROM old_read_receipts; ``` 3. **Mettre à jour le code client** : S'assurer que les clients utilisent des UUID au lieu d'entiers --- ## Support et maintenance ### Logs à surveiller - Erreurs de validation/permission lors du marquage comme lu - Erreurs de base de données lors de la création/mise à jour de read receipts - Temps de réponse élevés pour les requêtes de read receipts ### Monitoring recommandé - Nombre de read receipts créés par minute - Taux d'erreur lors du marquage comme lu - Temps de réponse des requêtes `get_receipts_for_message` --- ## Références - **Migration** : `migrations/003_read_receipts.sql` - **Code source** : `src/read_receipts.rs` - **Handler WebSocket** : `src/websocket/handler.rs` - **Types WebSocket** : `src/websocket/mod.rs` --- **Dernière mise à jour** : 2025-12-05