794 lines
23 KiB
Rust
794 lines
23 KiB
Rust
//! Gestion unifiée des salons, messages directs et modération
|
|
//!
|
|
//! Ce module fournit une interface unifiée pour:
|
|
//! - Gestion des salons (création, suppression, permissions)
|
|
//! - Messages directs entre utilisateurs
|
|
//! - Système de modération avancé
|
|
//! - Gestion des rôles et permissions
|
|
//! - Intégration avec les métriques et logs
|
|
|
|
use crate::authentication::{Role, UserSession};
|
|
use crate::error::{ChatError, Result};
|
|
use crate::prometheus_metrics::PrometheusMetrics;
|
|
use crate::structured_logging::chat_logs;
|
|
use chrono::{DateTime, Utc};
|
|
use std::time::Duration;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::{HashMap, HashSet};
|
|
use std::sync::Arc;
|
|
use tokio::sync::RwLock;
|
|
use uuid::Uuid;
|
|
|
|
/// Types de salons
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub enum RoomType {
|
|
/// Salon public - visible par tous
|
|
Public,
|
|
/// Salon privé - invitation uniquement
|
|
Private,
|
|
/// Salon direct - conversation entre 2 utilisateurs
|
|
Direct,
|
|
/// Salon système - géré par le système
|
|
System,
|
|
}
|
|
|
|
/// Permissions dans un salon
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
|
pub enum RoomPermission {
|
|
/// Lire les messages
|
|
Read,
|
|
/// Envoyer des messages
|
|
Write,
|
|
/// Modifier les messages
|
|
Edit,
|
|
/// Supprimer les messages
|
|
Delete,
|
|
/// Inviter des utilisateurs
|
|
Invite,
|
|
/// Gérer le salon
|
|
Manage,
|
|
/// Modérer le salon
|
|
Moderate,
|
|
}
|
|
|
|
/// Statut d'un salon
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub enum RoomStatus {
|
|
/// Salon actif
|
|
Active,
|
|
/// Salon archivé
|
|
Archived,
|
|
/// Salon supprimé
|
|
Deleted,
|
|
/// Salon suspendu
|
|
Suspended,
|
|
}
|
|
|
|
/// Configuration d'un salon
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct RoomConfig {
|
|
/// Nom du salon
|
|
pub name: String,
|
|
/// Description du salon
|
|
pub description: Option<String>,
|
|
/// Type du salon
|
|
pub room_type: RoomType,
|
|
/// Permissions par défaut
|
|
pub default_permissions: HashSet<RoomPermission>,
|
|
/// Limite de membres (None = illimitée)
|
|
pub max_members: Option<u32>,
|
|
/// Activer l'historique des messages
|
|
pub enable_history: bool,
|
|
/// Activer les réactions
|
|
pub enable_reactions: bool,
|
|
/// Activer les mentions
|
|
pub enable_mentions: bool,
|
|
/// Activer les fils de discussion
|
|
pub enable_threads: bool,
|
|
/// Mots-clés interdits
|
|
pub forbidden_words: HashSet<String>,
|
|
/// Activer la modération automatique
|
|
pub auto_moderation: bool,
|
|
}
|
|
|
|
impl Default for RoomConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
name: "Nouveau salon".to_string(),
|
|
description: None,
|
|
room_type: RoomType::Public,
|
|
default_permissions: HashSet::from([RoomPermission::Read, RoomPermission::Write]),
|
|
max_members: Some(1000),
|
|
enable_history: true,
|
|
enable_reactions: true,
|
|
enable_mentions: true,
|
|
enable_threads: true,
|
|
forbidden_words: HashSet::new(),
|
|
auto_moderation: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Salon de chat
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Room {
|
|
/// ID unique du salon
|
|
pub id: Uuid,
|
|
/// Configuration du salon
|
|
pub config: RoomConfig,
|
|
/// Créateur du salon
|
|
pub creator_id: i32,
|
|
/// Date de création
|
|
pub created_at: DateTime<Utc>,
|
|
/// Date de dernière activité
|
|
pub last_activity: DateTime<Utc>,
|
|
/// Statut du salon
|
|
pub status: RoomStatus,
|
|
/// Membres du salon (user_id -> permissions)
|
|
pub members: HashMap<i32, HashSet<RoomPermission>>,
|
|
/// Modérateurs du salon
|
|
pub moderators: HashSet<i32>,
|
|
/// Administrateurs du salon
|
|
pub administrators: HashSet<i32>,
|
|
/// Messages épinglés
|
|
pub pinned_messages: Vec<Uuid>,
|
|
/// Tags du salon
|
|
pub tags: HashSet<String>,
|
|
}
|
|
|
|
impl Room {
|
|
/// Crée un nouveau salon
|
|
pub fn new(creator_id: i32, config: RoomConfig) -> Self {
|
|
let now = Utc::now();
|
|
Self {
|
|
id: Uuid::new_v4(),
|
|
config,
|
|
creator_id,
|
|
created_at: now,
|
|
last_activity: now,
|
|
status: RoomStatus::Active,
|
|
members: HashMap::new(),
|
|
moderators: HashSet::new(),
|
|
administrators: HashSet::new(),
|
|
pinned_messages: Vec::new(),
|
|
tags: HashSet::new(),
|
|
}
|
|
}
|
|
|
|
/// Ajoute un membre au salon
|
|
pub fn add_member(&mut self, user_id: i32, permissions: HashSet<RoomPermission>) {
|
|
self.members.insert(user_id, permissions);
|
|
self.last_activity = Utc::now();
|
|
}
|
|
|
|
/// Retire un membre du salon
|
|
pub fn remove_member(&mut self, user_id: i32) {
|
|
self.members.remove(&user_id);
|
|
self.moderators.remove(&user_id);
|
|
self.administrators.remove(&user_id);
|
|
self.last_activity = Utc::now();
|
|
}
|
|
|
|
/// Vérifie si un utilisateur a une permission spécifique
|
|
pub fn has_permission(&self, user_id: i32, permission: &RoomPermission) -> bool {
|
|
// L'administrateur du salon a toutes les permissions
|
|
if self.administrators.contains(&user_id) {
|
|
return true;
|
|
}
|
|
|
|
// Vérifier les permissions du membre
|
|
if let Some(member_permissions) = self.members.get(&user_id) {
|
|
member_permissions.contains(permission)
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
/// Vérifie si un utilisateur peut envoyer des messages
|
|
pub fn can_send_messages(&self, user_id: i32) -> bool {
|
|
self.has_permission(user_id, &RoomPermission::Write)
|
|
}
|
|
|
|
/// Vérifie si un utilisateur peut modérer
|
|
pub fn can_moderate(&self, user_id: i32) -> bool {
|
|
self.administrators.contains(&user_id)
|
|
|| self.moderators.contains(&user_id)
|
|
|| self.has_permission(user_id, &RoomPermission::Moderate)
|
|
}
|
|
|
|
/// Ajoute un modérateur
|
|
pub fn add_moderator(&mut self, user_id: i32) -> Result<()> {
|
|
if !self.members.contains_key(&user_id) {
|
|
return Err(ChatError::validation_error(
|
|
"Utilisateur n'est pas membre du salon",
|
|
));
|
|
}
|
|
|
|
self.moderators.insert(user_id);
|
|
self.last_activity = Utc::now();
|
|
Ok(())
|
|
}
|
|
|
|
/// Retire un modérateur
|
|
pub fn remove_moderator(&mut self, user_id: i32) {
|
|
self.moderators.remove(&user_id);
|
|
self.last_activity = Utc::now();
|
|
}
|
|
|
|
/// Épingle un message
|
|
pub fn pin_message(&mut self, message_id: Uuid) -> Result<()> {
|
|
if self.pinned_messages.len() >= 10 {
|
|
return Err(ChatError::validation_error("Trop de messages épinglés"));
|
|
}
|
|
|
|
if !self.pinned_messages.contains(&message_id) {
|
|
self.pinned_messages.push(message_id);
|
|
self.last_activity = Utc::now();
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Désépingle un message
|
|
pub fn unpin_message(&mut self, message_id: Uuid) {
|
|
self.pinned_messages.retain(|&id| id != message_id);
|
|
self.last_activity = Utc::now();
|
|
}
|
|
|
|
/// Met à jour la dernière activité
|
|
pub fn update_activity(&mut self) {
|
|
self.last_activity = Utc::now();
|
|
}
|
|
|
|
/// Archive le salon
|
|
pub fn archive(&mut self) {
|
|
self.status = RoomStatus::Archived;
|
|
self.last_activity = Utc::now();
|
|
}
|
|
|
|
/// Supprime le salon
|
|
pub fn delete(&mut self) {
|
|
self.status = RoomStatus::Deleted;
|
|
self.last_activity = Utc::now();
|
|
}
|
|
}
|
|
|
|
/// Message de chat
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ChatMessage {
|
|
/// ID unique du message
|
|
pub id: Uuid,
|
|
/// ID du salon ou de la conversation
|
|
pub room_id: Uuid,
|
|
/// ID de l'expéditeur
|
|
pub sender_id: i32,
|
|
/// Nom d'utilisateur de l'expéditeur
|
|
pub sender_username: String,
|
|
/// Contenu du message
|
|
pub content: String,
|
|
/// Type de message
|
|
pub message_type: MessageType,
|
|
/// Message parent (pour les fils de discussion)
|
|
pub parent_message_id: Option<Uuid>,
|
|
/// Date d'envoi
|
|
pub sent_at: DateTime<Utc>,
|
|
/// Date de modification
|
|
pub edited_at: Option<DateTime<Utc>>,
|
|
/// Message supprimé
|
|
pub deleted: bool,
|
|
/// Réactions au message
|
|
pub reactions: HashMap<String, Vec<i32>>,
|
|
/// Mentions dans le message
|
|
pub mentions: Vec<i32>,
|
|
/// Fichiers joints
|
|
pub attachments: Vec<Attachment>,
|
|
/// Métadonnées du message
|
|
pub metadata: HashMap<String, String>,
|
|
}
|
|
|
|
/// Types de messages
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub enum MessageType {
|
|
/// Message texte normal
|
|
Text,
|
|
/// Message système
|
|
System,
|
|
/// Message de bienvenue
|
|
Welcome,
|
|
/// Message de modération
|
|
Moderation,
|
|
/// Message de fichier
|
|
File,
|
|
/// Message d'image
|
|
Image,
|
|
/// Message de code
|
|
Code,
|
|
/// Message de commande
|
|
Command,
|
|
}
|
|
|
|
/// Fichier joint
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Attachment {
|
|
/// ID du fichier
|
|
pub id: Uuid,
|
|
/// Nom du fichier
|
|
pub filename: String,
|
|
/// Type MIME
|
|
pub mime_type: String,
|
|
/// Taille en bytes
|
|
pub size_bytes: u64,
|
|
/// URL de téléchargement
|
|
pub download_url: String,
|
|
/// Date d'upload
|
|
pub uploaded_at: DateTime<Utc>,
|
|
}
|
|
|
|
/// Action de modération
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ModerationAction {
|
|
/// ID de l'action
|
|
pub id: Uuid,
|
|
/// Type d'action
|
|
pub action_type: ModerationActionType,
|
|
/// ID du modérateur
|
|
pub moderator_id: i32,
|
|
/// ID de l'utilisateur ciblé
|
|
pub target_user_id: Option<i32>,
|
|
/// ID du message ciblé
|
|
pub target_message_id: Option<Uuid>,
|
|
/// ID du salon
|
|
pub room_id: Uuid,
|
|
/// Raison de l'action
|
|
pub reason: String,
|
|
/// Durée de la sanction (si applicable)
|
|
pub duration: Option<Duration>,
|
|
/// Date de l'action
|
|
pub created_at: DateTime<Utc>,
|
|
/// Action active
|
|
pub active: bool,
|
|
}
|
|
|
|
/// Types d'actions de modération
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub enum ModerationActionType {
|
|
/// Avertissement
|
|
Warning,
|
|
/// Suppression de message
|
|
DeleteMessage,
|
|
/// Modification de message
|
|
EditMessage,
|
|
/// Bannissement temporaire
|
|
TemporaryBan,
|
|
/// Bannissement permanent
|
|
PermanentBan,
|
|
/// Mute temporaire
|
|
TemporaryMute,
|
|
/// Mute permanent
|
|
PermanentMute,
|
|
/// Kick du salon
|
|
Kick,
|
|
/// Suspension du salon
|
|
SuspendRoom,
|
|
/// Suppression du salon
|
|
DeleteRoom,
|
|
}
|
|
|
|
/// Gestionnaire de chat unifié
|
|
pub struct ChatManager {
|
|
/// Salons actifs
|
|
rooms: Arc<RwLock<HashMap<Uuid, Room>>>,
|
|
/// Messages par salon
|
|
messages: Arc<RwLock<HashMap<Uuid, Vec<ChatMessage>>>>,
|
|
/// Actions de modération
|
|
moderation_actions: Arc<RwLock<Vec<ModerationAction>>>,
|
|
/// Métriques Prometheus
|
|
metrics: Option<Arc<PrometheusMetrics>>,
|
|
}
|
|
|
|
impl ChatManager {
|
|
/// Crée un nouveau gestionnaire de chat
|
|
pub fn new(metrics: Option<Arc<PrometheusMetrics>>) -> Self {
|
|
Self {
|
|
rooms: Arc::new(RwLock::new(HashMap::new())),
|
|
messages: Arc::new(RwLock::new(HashMap::new())),
|
|
moderation_actions: Arc::new(RwLock::new(Vec::new())),
|
|
metrics,
|
|
}
|
|
}
|
|
|
|
/// Crée un nouveau salon
|
|
pub async fn create_room(
|
|
&self,
|
|
creator_id: i32,
|
|
creator_username: &str,
|
|
config: RoomConfig,
|
|
) -> Result<Uuid> {
|
|
let room = Room::new(creator_id, config.clone());
|
|
let room_id = room.id;
|
|
|
|
// Ajouter le créateur comme administrateur
|
|
let mut room = room;
|
|
room.administrators.insert(creator_id);
|
|
room.add_member(creator_id, room.config.default_permissions.clone());
|
|
|
|
// Sauvegarder le salon
|
|
{
|
|
let mut rooms = self.rooms.write().await;
|
|
rooms.insert(room_id, room);
|
|
}
|
|
|
|
// Enregistrer les métriques
|
|
if let Some(metrics) = &self.metrics {
|
|
metrics.record_room_created();
|
|
metrics.update_active_rooms(self.get_active_rooms_count().await);
|
|
}
|
|
|
|
chat_logs::room_created(
|
|
&room_id.to_string(),
|
|
&config.name,
|
|
creator_id,
|
|
creator_username,
|
|
);
|
|
|
|
Ok(room_id)
|
|
}
|
|
|
|
/// Supprime un salon
|
|
pub async fn delete_room(
|
|
&self,
|
|
room_id: Uuid,
|
|
deleter_id: i32,
|
|
deleter_username: &str,
|
|
) -> Result<()> {
|
|
let room_name = {
|
|
let rooms = self.rooms.read().await;
|
|
let room = rooms
|
|
.get(&room_id)
|
|
.ok_or_else(|| ChatError::validation_error("Salon non trouvé"))?;
|
|
|
|
// Vérifier les permissions
|
|
if !room.administrators.contains(&deleter_id) {
|
|
return Err(ChatError::unauthorized("Permissions insuffisantes"));
|
|
}
|
|
|
|
room.config.name.clone()
|
|
};
|
|
|
|
// Supprimer le salon
|
|
{
|
|
let mut rooms = self.rooms.write().await;
|
|
if let Some(room) = rooms.get_mut(&room_id) {
|
|
room.delete();
|
|
}
|
|
}
|
|
|
|
// Supprimer les messages
|
|
{
|
|
let mut messages = self.messages.write().await;
|
|
messages.remove(&room_id);
|
|
}
|
|
|
|
// Enregistrer les métriques
|
|
if let Some(metrics) = &self.metrics {
|
|
metrics.record_room_deleted();
|
|
metrics.update_active_rooms(self.get_active_rooms_count().await);
|
|
}
|
|
|
|
chat_logs::room_deleted(
|
|
&room_id.to_string(),
|
|
&room_name,
|
|
deleter_id,
|
|
deleter_username,
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Rejoint un salon
|
|
pub async fn join_room(&self, room_id: Uuid, user_id: i32, username: &str) -> Result<()> {
|
|
let mut rooms = self.rooms.write().await;
|
|
let room = rooms
|
|
.get_mut(&room_id)
|
|
.ok_or_else(|| ChatError::validation_error("Salon non trouvé"))?;
|
|
|
|
// Vérifier si le salon est actif
|
|
if room.status != RoomStatus::Active {
|
|
return Err(ChatError::validation_error("Salon non actif"));
|
|
}
|
|
|
|
// Vérifier la limite de membres
|
|
if let Some(max_members) = room.config.max_members {
|
|
if room.members.len() >= max_members as usize {
|
|
return Err(ChatError::validation_error("Salon plein"));
|
|
}
|
|
}
|
|
|
|
// Ajouter le membre
|
|
room.add_member(user_id, room.config.default_permissions.clone());
|
|
|
|
chat_logs::room_created(&room_id.to_string(), &room.config.name, user_id, username);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Quitte un salon
|
|
pub async fn leave_room(&self, room_id: Uuid, user_id: i32, username: &str) -> Result<()> {
|
|
let mut rooms = self.rooms.write().await;
|
|
let room = rooms
|
|
.get_mut(&room_id)
|
|
.ok_or_else(|| ChatError::validation_error("Salon non trouvé"))?;
|
|
|
|
room.remove_member(user_id);
|
|
|
|
chat_logs::room_deleted(&room_id.to_string(), &room.config.name, user_id, username);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Envoie un message dans un salon
|
|
pub async fn send_message(
|
|
&self,
|
|
room_id: Uuid,
|
|
sender_id: i32,
|
|
sender_username: &str,
|
|
content: String,
|
|
message_type: MessageType,
|
|
) -> Result<Uuid> {
|
|
// Vérifier les permissions
|
|
{
|
|
let rooms = self.rooms.read().await;
|
|
let room = rooms
|
|
.get(&room_id)
|
|
.ok_or_else(|| ChatError::validation_error("Salon non trouvé"))?;
|
|
|
|
if !room.can_send_messages(sender_id) {
|
|
return Err(ChatError::unauthorized("Permissions insuffisantes"));
|
|
}
|
|
}
|
|
|
|
// Créer le message
|
|
let message = ChatMessage {
|
|
id: Uuid::new_v4(),
|
|
room_id,
|
|
sender_id,
|
|
sender_username: sender_username.to_string(),
|
|
content: content.clone(),
|
|
message_type,
|
|
parent_message_id: None,
|
|
sent_at: Utc::now(),
|
|
edited_at: None,
|
|
deleted: false,
|
|
reactions: HashMap::new(),
|
|
mentions: Vec::new(),
|
|
attachments: Vec::new(),
|
|
metadata: HashMap::new(),
|
|
};
|
|
|
|
let message_id = message.id;
|
|
|
|
// Sauvegarder le message
|
|
{
|
|
let mut messages = self.messages.write().await;
|
|
messages.entry(room_id).or_default().push(message);
|
|
}
|
|
|
|
// Mettre à jour l'activité du salon
|
|
{
|
|
let mut rooms = self.rooms.write().await;
|
|
if let Some(room) = rooms.get_mut(&room_id) {
|
|
room.update_activity();
|
|
}
|
|
}
|
|
|
|
// Enregistrer les métriques
|
|
if let Some(metrics) = &self.metrics {
|
|
metrics.record_message_sent("text", "room");
|
|
metrics.record_message_size(content.len() as u64, "text");
|
|
}
|
|
|
|
chat_logs::message_sent(
|
|
message_id.to_string(),
|
|
sender_id,
|
|
sender_username,
|
|
&room_id.to_string(),
|
|
"text",
|
|
content.len(),
|
|
);
|
|
|
|
Ok(message_id)
|
|
}
|
|
|
|
/// Récupère les messages d'un salon
|
|
pub async fn get_room_messages(
|
|
&self,
|
|
room_id: Uuid,
|
|
limit: Option<usize>,
|
|
offset: Option<usize>,
|
|
) -> Result<Vec<ChatMessage>> {
|
|
let messages = self.messages.read().await;
|
|
let room_messages = messages
|
|
.get(&room_id)
|
|
.ok_or_else(|| ChatError::validation_error("Salon non trouvé"))?;
|
|
|
|
let offset = offset.unwrap_or(0);
|
|
let limit = limit.unwrap_or(50).min(100);
|
|
|
|
let start = room_messages.len().saturating_sub(offset + limit);
|
|
let end = room_messages.len().saturating_sub(offset);
|
|
|
|
Ok(room_messages[start..end].to_vec())
|
|
}
|
|
|
|
/// Crée une conversation directe
|
|
pub async fn create_direct_conversation(&self, user1_id: i32, user2_id: i32) -> Result<Uuid> {
|
|
let config = RoomConfig {
|
|
name: format!("DM_{}_{}", user1_id, user2_id),
|
|
room_type: RoomType::Direct,
|
|
max_members: Some(2),
|
|
..Default::default()
|
|
};
|
|
|
|
let room_id = self.create_room(user1_id, "system", config).await?;
|
|
|
|
// Ajouter le deuxième utilisateur
|
|
self.join_room(room_id, user2_id, "user").await?;
|
|
|
|
Ok(room_id)
|
|
}
|
|
|
|
/// Applique une action de modération
|
|
pub async fn apply_moderation_action(&self, action: ModerationAction) -> Result<()> {
|
|
// Sauvegarder l'action
|
|
{
|
|
let mut actions = self.moderation_actions.write().await;
|
|
actions.push(action.clone());
|
|
}
|
|
|
|
// Appliquer l'action selon le type
|
|
match action.action_type {
|
|
ModerationActionType::DeleteMessage => {
|
|
if let Some(message_id) = action.target_message_id {
|
|
self.delete_message(message_id, action.moderator_id).await?;
|
|
}
|
|
}
|
|
ModerationActionType::Kick => {
|
|
if let Some(user_id) = action.target_user_id {
|
|
self.leave_room(action.room_id, user_id, "moderated")
|
|
.await?;
|
|
}
|
|
}
|
|
ModerationActionType::SuspendRoom => {
|
|
self.suspend_room(action.room_id, action.moderator_id)
|
|
.await?;
|
|
}
|
|
_ => {
|
|
// Autres actions de modération
|
|
}
|
|
}
|
|
|
|
// Enregistrer les métriques
|
|
if let Some(metrics) = &self.metrics {
|
|
metrics.record_moderation_action(&format!("{:?}", action.action_type));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Supprime un message
|
|
async fn delete_message(&self, message_id: Uuid, moderator_id: i32) -> Result<()> {
|
|
let mut messages = self.messages.write().await;
|
|
|
|
for room_messages in messages.values_mut() {
|
|
if let Some(message) = room_messages.iter_mut().find(|m| m.id == message_id) {
|
|
message.deleted = true;
|
|
message
|
|
.metadata
|
|
.insert("deleted_by".to_string(), moderator_id.to_string());
|
|
message
|
|
.metadata
|
|
.insert("deleted_at".to_string(), Utc::now().to_rfc3339());
|
|
return Ok(());
|
|
}
|
|
}
|
|
|
|
Err(ChatError::validation_error("Message non trouvé"))
|
|
}
|
|
|
|
/// Suspend un salon
|
|
async fn suspend_room(&self, room_id: Uuid, _moderator_id: i32) -> Result<()> {
|
|
let mut rooms = self.rooms.write().await;
|
|
if let Some(room) = rooms.get_mut(&room_id) {
|
|
room.status = RoomStatus::Suspended;
|
|
room.last_activity = Utc::now();
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Récupère le nombre de salons actifs
|
|
async fn get_active_rooms_count(&self) -> u64 {
|
|
let rooms = self.rooms.read().await;
|
|
rooms
|
|
.values()
|
|
.filter(|room| room.status == RoomStatus::Active)
|
|
.count() as u64
|
|
}
|
|
|
|
/// Récupère les statistiques du chat
|
|
pub async fn get_chat_stats(&self) -> ChatStats {
|
|
let rooms = self.rooms.read().await;
|
|
let messages = self.messages.read().await;
|
|
|
|
let total_rooms = rooms.len();
|
|
let active_rooms = rooms
|
|
.values()
|
|
.filter(|room| room.status == RoomStatus::Active)
|
|
.count();
|
|
|
|
let total_messages = messages.values().map(|msgs| msgs.len()).sum::<usize>();
|
|
|
|
let total_members = rooms.values().map(|room| room.members.len()).sum::<usize>();
|
|
|
|
ChatStats {
|
|
total_rooms,
|
|
active_rooms,
|
|
total_messages,
|
|
total_members,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Statistiques du chat
|
|
#[derive(Debug, Clone, Serialize)]
|
|
pub struct ChatStats {
|
|
pub total_rooms: usize,
|
|
pub active_rooms: usize,
|
|
pub total_messages: usize,
|
|
pub total_members: usize,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[tokio::test]
|
|
async fn test_room_creation() {
|
|
let manager = ChatManager::new(None);
|
|
let config = RoomConfig::default();
|
|
|
|
let room_id = manager.create_room(1, "testuser", config).await.unwrap();
|
|
assert!(!room_id.is_nil());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_room_permissions() {
|
|
let room = Room::new(1, RoomConfig::default());
|
|
|
|
// Le créateur doit avoir toutes les permissions
|
|
assert!(room.can_send_messages(1));
|
|
assert!(room.can_moderate(1));
|
|
|
|
// Un utilisateur non membre ne doit pas avoir de permissions
|
|
assert!(!room.can_send_messages(2));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_message_sending() {
|
|
let manager = ChatManager::new(None);
|
|
let config = RoomConfig::default();
|
|
|
|
let room_id = manager.create_room(1, "testuser", config).await.unwrap();
|
|
let message_id = manager
|
|
.send_message(
|
|
room_id,
|
|
1,
|
|
"testuser",
|
|
"Hello world".to_string(),
|
|
MessageType::Text,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert!(!message_id.is_nil());
|
|
}
|
|
}
|