219 lines
No EOL
8.3 KiB
Rust
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(())
|
|
}
|
|
}
|
|
}
|