posemesh_domain_http/
discovery.rs

1use std::{collections::HashMap, sync::Arc};
2
3use futures::lock::Mutex;
4use reqwest::Client;
5use serde::Deserialize;
6
7use crate::auth::{get_cached_or_fresh_token, AuthClient, TokenCache};
8
9#[derive(Debug, Deserialize, Clone)]
10pub struct Domain {
11    pub id: String,
12    pub name: String,
13}
14
15#[derive(Debug, Deserialize, Clone)]
16pub struct DomainServer {
17    pub id: String,
18    pub organization_id: String,
19    pub name: String,
20    pub url: String,
21}
22
23#[derive(Debug, Deserialize, Clone)]
24pub struct DomainWithToken {
25    #[serde(flatten)]
26    pub domain: DomainWithServer,
27    pub access_token: String,
28    #[serde(skip)]
29    pub expires_at: u64,
30}
31impl TokenCache for DomainWithToken {
32    fn get_access_token(&self) -> Option<String> {
33        if self.access_token.is_empty() {
34            return None;
35        }
36        Some(self.access_token.clone())
37    }
38
39    fn get_expires_at(&self) -> Option<u64> {
40        if self.expires_at == 0 {
41            return None;
42        }
43        Some(self.expires_at)
44    }
45
46    fn set_expires_at(&mut self, expires_at: u64) {
47        self.expires_at = expires_at;
48    }
49}
50
51#[derive(Debug, Deserialize, Clone)]
52pub struct DomainWithServer {
53    #[serde(flatten)]
54    pub domain: Domain,
55    pub domain_server: DomainServer,
56}
57
58#[derive(Debug, Clone)]
59pub struct DiscoveryService {
60    dds_url: String,
61    client: Client,
62    cache: Arc<Mutex<HashMap<String, DomainWithToken>>>,
63    client_id: String,
64    api_client: AuthClient,
65}
66
67#[derive(Debug, Deserialize)]
68pub struct ListDomainsResponse {
69    pub domains: Vec<DomainWithServer>,
70}
71
72impl DiscoveryService {
73    pub fn new(api_url: &str, dds_url: &str, client_id: &str) -> Self {
74        let api_client = AuthClient::new(api_url, client_id);
75        Self {
76            dds_url: dds_url.to_string(),
77            client: Client::new(),
78            cache: Arc::new(Mutex::new(HashMap::new())),
79            client_id: client_id.to_string(),
80            api_client,
81        }
82    }
83
84    pub async fn list_domains(&self, access_token: &str) -> Result<Vec<DomainWithServer>, Box<dyn std::error::Error>> {
85        let response = self.client
86            .get(&format!("{}/api/v1/domains?with=domain_server", self.dds_url))
87            .bearer_auth(access_token)
88            .header("Content-Type", "application/json")
89            .header("posemesh-client-id", self.client_id.clone())
90            .send()
91            .await?;
92
93        if response.status().is_success() {
94            let domain_servers: ListDomainsResponse = response.json().await?;
95            Ok(domain_servers.domains)
96        } else {
97            Err(format!("Failed to list domains. Status: {}", response.status()).into())
98        }
99    }
100
101    pub async fn sign_in_with_auki_account(&mut self, email: &str, password: &str) -> Result<(), Box<dyn std::error::Error>> {
102        let _ = self.api_client.user_login(email, password).await?;
103        self.cache.lock().await.clear();
104        Ok(())
105    }
106
107    pub async fn sign_in_as_auki_app(&mut self, app_key: &str, app_secret: &str) -> Result<(), Box<dyn std::error::Error>> {
108        self.api_client.set_app_credentials(app_key, app_secret).await;
109        self.cache.lock().await.clear();
110        Ok(())
111    }
112
113    pub async fn auth_domain(&self, domain_id: &str) -> Result<DomainWithToken, Box<dyn std::error::Error>> {
114        let access_token = self.api_client.get_dds_access_token().await?;
115        // Check cache first
116        let cache = if let Some(cached_domain) = self.cache.lock().await.get(domain_id) {
117            cached_domain.clone()
118        } else {
119            DomainWithToken {
120                domain: DomainWithServer {
121                    domain: Domain {
122                        id: domain_id.to_string(),
123                        name: "".to_string(),
124                    },
125                    domain_server: DomainServer {
126                        id: "".to_string(),
127                        organization_id: "".to_string(),
128                        name: "".to_string(),
129                        url: "".to_string(),
130                    },
131                },
132                access_token: "".to_string(),
133                expires_at: 0,
134            }
135        };
136        
137        let cached = get_cached_or_fresh_token(&cache, || {
138            let client = self.client.clone();
139            let dds_url = self.dds_url.clone();
140            let client_id = self.client_id.clone();
141            async move {
142                let response = client
143                    .post(&format!("{}/api/v1/domains/{}/auth", dds_url, domain_id))
144                    .bearer_auth(access_token)
145                    .header("Content-Type", "application/json")
146                    .header("posemesh-client-id", client_id)
147                    .send()
148                    .await?;
149
150                if response.status().is_success() {
151                    let domain_with_token: DomainWithToken = response.json().await?;
152                    Ok(domain_with_token)
153                } else {
154                    let status = response.status();
155                    let text = response.text().await.unwrap_or_else(|_| "Unknown error".to_string());
156                    Err(format!("Failed to auth domain. Status: {} - {}", status, text).into())
157                }
158            }
159        }).await?;
160
161        
162        // Cache the result
163        let mut cache = self.cache.lock().await;
164        cache.insert(domain_id.to_string(), cached.clone());
165        Ok(cached)
166    }
167}