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
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 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 let mut cache = self.cache.lock().await;
164 cache.insert(domain_id.to_string(), cached.clone());
165 Ok(cached)
166 }
167}