posemesh_domain_http/
discovery.rs1use 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
58pub struct DiscoveryService {
59 dds_url: String,
60 client: Client,
61 cache: Arc<Mutex<HashMap<String, DomainWithToken>>>,
62 client_id: String,
63 api_client: AuthClient,
64}
65
66#[derive(Debug, Deserialize)]
67pub struct ListDomainsResponse {
68 pub domains: Vec<DomainWithServer>,
69}
70
71impl DiscoveryService {
72 pub fn new(api_url: &str, dds_url: &str, client_id: &str) -> Self {
73 let api_client = AuthClient::new(api_url, client_id);
74 Self {
75 dds_url: dds_url.to_string(),
76 client: Client::new(),
77 cache: Arc::new(Mutex::new(HashMap::new())),
78 client_id: client_id.to_string(),
79 api_client,
80 }
81 }
82
83 pub async fn list_domains(&self, access_token: &str) -> Result<Vec<DomainWithServer>, Box<dyn std::error::Error>> {
84 let response = self.client
85 .get(&format!("{}/api/v1/domains?with=domain_server", self.dds_url))
86 .bearer_auth(access_token)
87 .header("Content-Type", "application/json")
88 .header("posemesh-client-id", self.client_id.clone())
89 .send()
90 .await?;
91
92 if response.status().is_success() {
93 let domain_servers: ListDomainsResponse = response.json().await?;
94 Ok(domain_servers.domains)
95 } else {
96 Err(format!("Failed to list domains. Status: {}", response.status()).into())
97 }
98 }
99
100 pub async fn sign_in_with_auki_account(&mut self, email: &str, password: &str) -> Result<(), Box<dyn std::error::Error>> {
101 let _ = self.api_client.user_login(email, password).await?;
102 self.cache.lock().await.clear();
103 Ok(())
104 }
105
106 pub async fn sign_in_as_auki_app(&mut self, app_key: &str, app_secret: &str) -> Result<(), Box<dyn std::error::Error>> {
107 self.api_client.set_app_credentials(app_key, app_secret).await;
108 self.cache.lock().await.clear();
109 Ok(())
110 }
111
112 pub async fn auth_domain(&self, domain_id: &str) -> Result<DomainWithToken, Box<dyn std::error::Error>> {
113 let access_token = self.api_client.get_dds_access_token().await?;
114 let cache = if let Some(cached_domain) = self.cache.lock().await.get(domain_id) {
116 cached_domain.clone()
117 } else {
118 DomainWithToken {
119 domain: DomainWithServer {
120 domain: Domain {
121 id: domain_id.to_string(),
122 name: "".to_string(),
123 },
124 domain_server: DomainServer {
125 id: "".to_string(),
126 organization_id: "".to_string(),
127 name: "".to_string(),
128 url: "".to_string(),
129 },
130 },
131 access_token: "".to_string(),
132 expires_at: 0,
133 }
134 };
135
136 let cached = get_cached_or_fresh_token(&cache, || {
137 let client = self.client.clone();
138 let dds_url = self.dds_url.clone();
139 let client_id = self.client_id.clone();
140 async move {
141 let response = client
142 .post(&format!("{}/api/v1/domains/{}/auth", dds_url, domain_id))
143 .bearer_auth(access_token)
144 .header("Content-Type", "application/json")
145 .header("posemesh-client-id", client_id)
146 .send()
147 .await?;
148
149 if response.status().is_success() {
150 let domain_with_token: DomainWithToken = response.json().await?;
151 Ok(domain_with_token)
152 } else {
153 let status = response.status();
154 let text = response.text().await.unwrap_or_else(|_| "Unknown error".to_string());
155 Err(format!("Failed to auth domain. Status: {} - {}", status, text).into())
156 }
157 }
158 }).await?;
159
160
161 let mut cache = self.cache.lock().await;
163 cache.insert(domain_id.to_string(), cached.clone());
164 Ok(cached)
165 }
166}