veza/veza-chat-server/docs/CHAT_READ_RECEIPTS.md
okinrev b7955a680c P0: stabilisation backend/chat/stream + nouvelle base migrations v1
Backend Go:
- Remplacement complet des anciennes migrations par la base V1 alignée sur ORIGIN.
- Durcissement global du parsing JSON (BindAndValidateJSON + RespondWithAppError).
- Sécurisation de config.go, CORS, statuts de santé et monitoring.
- Implémentation des transactions P0 (RBAC, duplication de playlists, social toggles).
- Ajout d’un job worker structuré (emails, analytics, thumbnails) + tests associés.
- Nouvelle doc backend : AUDIT_CONFIG, BACKEND_CONFIG, AUTH_PASSWORD_RESET, JOB_WORKER_*.

Chat server (Rust):
- Refonte du pipeline JWT + sécurité, audit et rate limiting avancé.
- Implémentation complète du cycle de message (read receipts, delivered, edit/delete, typing).
- Nettoyage des panics, gestion d’erreurs robuste, logs structurés.
- Migrations chat alignées sur le schéma UUID et nouvelles features.

Stream server (Rust):
- Refonte du moteur de streaming (encoding pipeline + HLS) et des modules core.
- Transactions P0 pour les jobs et segments, garanties d’atomicité.
- Documentation détaillée de la pipeline (AUDIT_STREAM_*, DESIGN_STREAM_PIPELINE, TRANSACTIONS_P0_IMPLEMENTATION).

Documentation & audits:
- TRIAGE.md et AUDIT_STABILITY.md à jour avec l’état réel des 3 services.
- Cartographie complète des migrations et des transactions (DB_MIGRATIONS_*, DB_TRANSACTION_PLAN, AUDIT_DB_TRANSACTIONS, TRANSACTION_TESTS_PHASE3).
- Scripts de reset et de cleanup pour la lab DB et la V1.

Ce commit fige l’ensemble du travail de stabilisation P0 (UUID, backend, chat et stream) avant les phases suivantes (Coherence Guardian, WS hardening, etc.).
2025-12-06 11:14:38 +01:00

10 KiB

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 :

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.

{
  "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.

{
  "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<ReadReceipt>

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<ReadReceipt>

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<Uuid>

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 :

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 :

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)

// 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)

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) :
    -- 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