veza/veza-chat-server/src/grpc_client.rs
2025-12-03 20:33:26 +01:00

219 lines
No EOL
8.3 KiB
Rust

//! Client gRPC pour communiquer avec veza-backend-api
use crate::error::{ChatError, Result};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use tokio::time::timeout;
use tracing::{info, warn, error};
/// Configuration du client gRPC
#[derive(Debug, Clone)]
pub struct GrpcClientConfig {
pub backend_api_url: String,
pub timeout_seconds: u64,
pub max_retries: u32,
}
impl Default for GrpcClientConfig {
fn default() -> Self {
Self {
backend_api_url: std::env::var("BACKEND_API_URL")
.unwrap_or_else(|_| "http://localhost:8080".to_string()),
timeout_seconds: 30,
max_retries: 3,
}
}
}
/// Informations utilisateur depuis le backend
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserInfo {
pub id: i64,
pub username: String,
pub email: String,
pub role: String,
pub is_active: bool,
}
/// Informations de session utilisateur
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionInfo {
pub user_id: i64,
pub session_token: String,
pub expires_at: String,
pub is_active: bool,
}
/// Client gRPC pour communiquer avec le backend API
pub struct BackendApiClient {
config: GrpcClientConfig,
http_client: reqwest::Client,
}
impl BackendApiClient {
/// Crée un nouveau client
pub fn new(config: GrpcClientConfig) -> Self {
let http_client = reqwest::Client::builder()
.timeout(Duration::from_secs(config.timeout_seconds))
.build()
.unwrap_or_default();
Self {
config,
http_client,
}
}
/// Crée un client avec la configuration par défaut
pub fn with_default_config() -> Self {
Self::new(GrpcClientConfig::default())
}
/// Vérifie la validité d'un token JWT
pub async fn verify_jwt_token(&self, token: &str) -> Result<UserInfo> {
info!("🔍 Vérification du token JWT avec le backend API");
let url = format!("{}/api/v1/auth/profile", self.config.backend_api_url);
let response = timeout(
Duration::from_secs(self.config.timeout_seconds),
self.http_client
.get(&url)
.header("Authorization", format!("Bearer {}", token))
.send()
).await
.map_err(|_| ChatError::configuration_error("Timeout lors de la vérification du token"))?
.map_err(|e| ChatError::configuration_error(&format!("Erreur HTTP: {}", e)))?;
if response.status().is_success() {
let response_body: serde_json::Value = response.json().await
.map_err(|e| ChatError::configuration_error(&format!("Erreur parsing JSON: {}", e)))?;
// Parser la réponse du backend
if let Some(data) = response_body.get("data") {
if let Some(user_data) = data.get("user") {
let user_info = UserInfo {
id: user_data.get("id").and_then(|v| v.as_i64()).unwrap_or(0),
username: user_data.get("username").and_then(|v| v.as_str()).unwrap_or("unknown").to_string(),
email: user_data.get("email").and_then(|v| v.as_str()).unwrap_or("").to_string(),
role: user_data.get("role").and_then(|v| v.as_str()).unwrap_or("user").to_string(),
is_active: user_data.get("is_active").and_then(|v| v.as_bool()).unwrap_or(false),
};
info!("✅ Token valide pour l'utilisateur: {}", user_info.username);
return Ok(user_info);
}
}
error!("❌ Réponse invalide du backend API");
Err(ChatError::configuration_error("Réponse invalide du backend API"))
} else if response.status() == 401 {
warn!("⚠️ Token JWT invalide ou expiré");
Err(ChatError::configuration_error("Token JWT invalide ou expiré"))
} else {
error!("❌ Erreur backend API: {}", response.status());
Err(ChatError::configuration_error(&format!("Erreur backend API: {}", response.status())))
}
}
/// Synchronise les informations utilisateur
pub async fn sync_user_info(&self, user_id: i64) -> Result<UserInfo> {
info!("🔄 Synchronisation des informations utilisateur ID: {}", user_id);
let url = format!("{}/api/v1/users/{}", self.config.backend_api_url, user_id);
let response = timeout(
Duration::from_secs(self.config.timeout_seconds),
self.http_client.get(&url).send()
).await
.map_err(|_| ChatError::configuration_error("Timeout lors de la synchronisation utilisateur"))?
.map_err(|e| ChatError::configuration_error(&format!("Erreur HTTP: {}", e)))?;
if response.status().is_success() {
let user_info: UserInfo = response.json().await
.map_err(|e| ChatError::configuration_error(&format!("Erreur parsing user info: {}", e)))?;
info!("✅ Informations utilisateur synchronisées: {}", user_info.username);
Ok(user_info)
} else {
error!("❌ Impossible de récupérer les informations utilisateur: {}", response.status());
Err(ChatError::configuration_error(&format!("Erreur récupération utilisateur: {}", response.status())))
}
}
/// Notifie le backend d'un nouveau message de chat
pub async fn notify_new_message(&self, message_id: i32, room_id: &str, sender_id: i64) -> Result<()> {
info!("📢 Notification nouveau message au backend: message={}, room={}, sender={}",
message_id, room_id, sender_id);
let url = format!("{}/api/v1/chat/message-notifications", self.config.backend_api_url);
let payload = serde_json::json!({
"message_id": message_id,
"room_id": room_id,
"sender_id": sender_id,
"timestamp": chrono::Utc::now()
});
let response = timeout(
Duration::from_secs(self.config.timeout_seconds),
self.http_client
.post(&url)
.json(&payload)
.send()
).await
.map_err(|_| ChatError::configuration_error("Timeout lors de la notification"))?
.map_err(|e| ChatError::configuration_error(&format!("Erreur HTTP notification: {}", e)))?;
if response.status().is_success() {
info!("✅ Notification envoyée avec succès");
Ok(())
} else {
warn!("⚠️ Échec notification (non critique): {}", response.status());
// Les notifications sont non-critiques, ne pas faire échouer l'opération
Ok(())
}
}
/// Vérifie la santé du backend API
pub async fn health_check(&self) -> Result<bool> {
let url = format!("{}/health", self.config.backend_api_url);
match timeout(
Duration::from_secs(5), // Timeout court pour les health checks
self.http_client.get(&url).send()
).await {
Ok(Ok(response)) => Ok(response.status().is_success()),
_ => Ok(false),
}
}
/// Met à jour les statistiques d'activité utilisateur
pub async fn update_user_activity(&self, user_id: i64, activity_type: &str) -> Result<()> {
let url = format!("{}/api/v1/users/{}/activity", self.config.backend_api_url, user_id);
let payload = serde_json::json!({
"activity_type": activity_type,
"timestamp": chrono::Utc::now()
});
let response = timeout(
Duration::from_secs(self.config.timeout_seconds),
self.http_client
.post(&url)
.json(&payload)
.send()
).await
.map_err(|_| ChatError::configuration_error("Timeout lors de la mise à jour d'activité"))?
.map_err(|e| ChatError::configuration_error(&format!("Erreur HTTP activité: {}", e)))?;
if response.status().is_success() {
info!("✅ Activité utilisateur mise à jour: {} - {}", user_id, activity_type);
Ok(())
} else {
warn!("⚠️ Échec mise à jour activité (non critique): {}", response.status());
// Les mises à jour d'activité sont non-critiques
Ok(())
}
}
}