use std::collections::HashMap; use std::sync::Arc; use std::time::{Duration, Instant}; use tokio::sync::RwLock; use serde::{Serialize, Deserialize}; use serde_json::json; use crate::error::Result; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum UserStatus { Online, Away, Busy, Invisible, Offline, } #[derive(Debug, Clone, Serialize)] pub struct UserPresence { pub user_id: i32, pub username: String, pub status: UserStatus, #[serde(skip)] // Skip Instant car non sérialisable pub last_seen: Instant, pub status_message: Option, pub current_room: Option, } impl UserPresence { pub fn new(user_id: i32, username: String) -> Self { Self { user_id, username, status: UserStatus::Online, last_seen: Instant::now(), status_message: None, current_room: None, } } pub fn update_activity(&mut self) { self.last_seen = Instant::now(); if self.status == UserStatus::Away { self.status = UserStatus::Online; } } pub fn set_away_if_inactive(&mut self, away_threshold: Duration) { if self.status == UserStatus::Online && self.last_seen.elapsed() > away_threshold { self.status = UserStatus::Away; } } } /// Gestionnaire de présence des utilisateurs pub struct PresenceManager { users: Arc>>, away_threshold: Duration, } impl Default for PresenceManager { fn default() -> Self { Self::new() } } impl PresenceManager { pub fn new() -> Self { Self { users: Arc::new(RwLock::new(HashMap::new())), away_threshold: Duration::from_secs(300), // 5 minutes } } /// Enregistre un utilisateur comme en ligne pub async fn user_online(&self, user_id: i32, username: String) { let mut users = self.users.write().await; let presence = UserPresence::new(user_id, username.clone()); tracing::info!(user_id = %user_id, username = %username, "👋 Utilisateur en ligne"); users.insert(user_id, presence); } /// Marque un utilisateur comme hors ligne pub async fn user_offline(&self, user_id: i32) { let mut users = self.users.write().await; if let Some(mut presence) = users.remove(&user_id) { presence.status = UserStatus::Offline; tracing::info!(user_id = %user_id, username = %presence.username, "👋 Utilisateur hors ligne"); } } /// Met à jour l'activité d'un utilisateur pub async fn update_user_activity(&self, user_id: i32) { let mut users = self.users.write().await; if let Some(presence) = users.get_mut(&user_id) { presence.update_activity(); } } /// Change le statut d'un utilisateur pub async fn set_user_status(&self, user_id: i32, status: UserStatus, message: Option) -> Result<()> { let mut users = self.users.write().await; if let Some(presence) = users.get_mut(&user_id) { presence.status = status.clone(); presence.status_message = message.clone(); tracing::info!( user_id = %user_id, username = %presence.username, status = ?status, message = ?message, "📊 Statut utilisateur mis à jour" ); } Ok(()) } /// Met à jour le salon actuel d'un utilisateur pub async fn set_user_room(&self, user_id: i32, room: Option) { let mut users = self.users.write().await; if let Some(presence) = users.get_mut(&user_id) { presence.current_room = room.clone(); tracing::debug!(user_id = %user_id, room = ?room, "🏠 Salon actuel mis à jour"); } } /// Obtient la présence d'un utilisateur pub async fn get_user_presence(&self, user_id: i32) -> Option { let users = self.users.read().await; users.get(&user_id).cloned() } /// Obtient la liste des utilisateurs en ligne dans un salon pub async fn get_room_users(&self, room: &str) -> Vec { let users = self.users.read().await; users.values() .filter(|presence| { presence.current_room.as_deref() == Some(room) && presence.status != UserStatus::Offline && presence.status != UserStatus::Invisible }) .cloned() .collect() } /// Obtient tous les utilisateurs en ligne pub async fn get_online_users(&self) -> Vec { let users = self.users.read().await; users.values() .filter(|presence| { presence.status != UserStatus::Offline && presence.status != UserStatus::Invisible }) .cloned() .collect() } /// Nettoie les utilisateurs inactifs (les marque comme "away") pub async fn cleanup_inactive_users(&self) { let mut users = self.users.write().await; let mut updated_users = Vec::new(); for presence in users.values_mut() { let old_status = presence.status.clone(); presence.set_away_if_inactive(self.away_threshold); if old_status != presence.status { updated_users.push(presence.clone()); } } if !updated_users.is_empty() { tracing::info!(count = %updated_users.len(), "😴 Utilisateurs marqués comme inactifs"); } } /// Génère un événement de présence pour diffusion pub fn create_presence_event(&self, presence: &UserPresence, event_type: &str) -> serde_json::Value { json!({ "type": "presence_update", "data": { "event": event_type, "user_id": presence.user_id, "username": presence.username, "status": presence.status, "status_message": presence.status_message, "current_room": presence.current_room } }) } } /// Système de notifications push pub struct NotificationManager { // Ici on pourrait intégrer avec des services comme Firebase, Apple Push, etc. } impl Default for NotificationManager { fn default() -> Self { Self::new() } } impl NotificationManager { pub fn new() -> Self { Self {} } /// Envoie une notification push à un utilisateur pub async fn send_push_notification( &self, user_id: i32, title: &str, body: &str, _data: Option ) -> Result<()> { // Implémentation des notifications push tracing::info!( user_id = %user_id, title = %title, body = %body, "📱 Notification push envoyée" ); // TODO: Intégrer avec Firebase Cloud Messaging, Apple Push Notification, etc. Ok(()) } /// Notification pour un nouveau message direct pub async fn notify_new_dm(&self, to_user: i32, from_username: &str, preview: &str) -> Result<()> { let title = format!("Nouveau message de {}", from_username); let body = if preview.len() > 50 { format!("{}...", &preview[..47]) } else { preview.to_string() }; self.send_push_notification( to_user, &title, &body, Some(json!({"type": "dm", "from": from_username})) ).await } /// Notification pour mention dans un salon pub async fn notify_room_mention(&self, user_id: i32, room: &str, from_username: &str, message: &str) -> Result<()> { let title = format!("Mention dans #{}", room); let body = format!("{}: {}", from_username, if message.len() > 50 { format!("{}...", &message[..47]) } else { message.to_string() } ); self.send_push_notification( user_id, &title, &body, Some(json!({"type": "mention", "room": room, "from": from_username})) ).await } }