/
rorikstr
/
multiberry-backend
Обзор
Документация
Войти
/
rorikstr
/
multiberry-backend
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
develop
src/api/client.rs
200 строк
5 KB
Rorik Star Platinum
finish 30% lol
09 ноя 2025, 21:03
09 ноя 2025, 21:03
ea863ad
Код
Авторство
О чём код?
// src/api/client.rs use reqwest::Client as HttpClient; use std::{env, str::FromStr, sync::Arc}; use tokio::sync::RwLock; use url::Url; use chrono::{DateTime, Duration, Utc}; // --- Error Types --- #[derive(Debug, thiserror::Error)] pub enum BankingError { #[error("Environment variable not set: {0}")] MissingEnvVar(String), #[error("Invalid URL: {0}")] UrlParseError(#[from] url::ParseError), #[error("HTTP request failed: {0}")] RequestFailed(#[from] reqwest::Error), #[error("API returned an error: {status} {body}")] ApiError { status: u16, body: String }, } // --- Bank Enum --- #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Deserialize)] pub enum Bank { VBank, ABank, SBank, } impl FromStr for Bank { type Err = anyhow::Error; fn from_str(s: &str) -> Result<Self, Self::Err> { match s.to_lowercase().as_str() { "vbank" => Ok(Bank::VBank), "abank" => Ok(Bank::ABank), "sbank" => Ok(Bank::SBank), _ => Err(anyhow::anyhow!("Unknown bank: {}", s)), } } } impl Bank { pub fn code(&self) -> &'static str { match self { Bank::VBank => "vbank", Bank::ABank => "abank", Bank::SBank => "sbank", } } } // --- Token Management --- #[derive(Debug, serde::Deserialize)] pub struct BankTokenResponse { pub access_token: String, pub expires_in: i64, } #[derive(Debug, Clone)] struct StoredToken { access_token: String, expires_at: DateTime<Utc>, } impl StoredToken { fn is_valid(&self) -> bool { self.expires_at > Utc::now() + Duration::seconds(60) } } // --- BankClient --- #[derive(Clone)] pub struct BankClient { pub http_client: HttpClient, pub base_url: Url, pub client_id: String, client_secret: String, token: Arc<RwLock<Option<StoredToken>>>, } impl BankClient { pub fn new( http_client: HttpClient, base_url: Url, client_id: String, client_secret: String, ) -> Self { Self { http_client, base_url, client_id, client_secret, token: Arc::new(RwLock::new(None)), } } async fn fetch_new_token(&self) -> Result<StoredToken, BankingError> { println!("🔄 Requesting new token for bank: {}", self.base_url.host_str().unwrap_or("")); let response = self.http_client .post(self.base_url.join("/auth/bank-token")?) .query(&[ ("client_id", self.client_id.as_str()), ("client_secret", self.client_secret.as_str()), ]) .send() .await?; match response.status().is_success() { true => { let token_response = response.json::<BankTokenResponse>().await?; Ok(StoredToken { access_token: token_response.access_token, expires_at: Utc::now() + Duration::seconds(token_response.expires_in), }) }, false => Err(BankingError::ApiError { status: response.status().as_u16(), body: response.text().await.unwrap_or_default(), }), } } pub async fn get_token(&self) -> Result<String, BankingError> { { let read_guard = self.token.read().await; if let Some(token) = read_guard.as_ref().filter(|t| t.is_valid()) { return Ok(token.access_token.clone()); } } let mut write_guard = self.token.write().await; match write_guard.as_ref() { Some(token) if token.is_valid() => Ok(token.access_token.clone()), _ => { let new_token = self.fetch_new_token().await?; let access_token = new_token.access_token.clone(); *write_guard = Some(new_token); Ok(access_token) } } } } // --- BankingClients --- #[derive(Clone)] pub struct BankingClients { pub vbank: Arc<BankClient>, pub abank: Arc<BankClient>, pub sbank: Arc<BankClient>, } impl BankingClients { pub async fn new() -> Result<Self, BankingError> { let http_client = HttpClient::new(); let get_env_var = |key: String| -> Result<String, BankingError> { env::var(&key).map_err(|_| BankingError::MissingEnvVar(key)) }; let create_client = |bank_prefix: &str| -> Result<Arc<BankClient>, BankingError> { let url_key = format!("{}_API_URL", bank_prefix); let id_key = format!("{}_CLIENT_ID", bank_prefix); let secret_key = format!("{}_CLIENT_SECRET", bank_prefix); let base_url = Url::parse(&get_env_var(url_key)?)?; let client_id = get_env_var(id_key)?; let client_secret = get_env_var(secret_key)?; Ok(Arc::new(BankClient::new( http_client.clone(), base_url, client_id, client_secret, ))) }; Ok(Self { vbank: create_client("VBANK")?, abank: create_client("ABANK")?, sbank: create_client("SBANK")?, }) } pub fn get_client(&self, bank: Bank) -> &Arc<BankClient> { match bank { Bank::VBank => &self.vbank, Bank::ABank => &self.abank, Bank::SBank => &self.sbank, } } }