1use futures::channel::mpsc::Receiver;
2
3use crate::domain_data::{
4 DomainData, DomainDataMetadata, DownloadQuery, delete_by_id, download_by_id,
5 download_metadata_v1, download_v1_stream,
6};
7#[cfg(target_family = "wasm")]
8use crate::domain_data::{UploadDomainData, upload_v1};
9
10mod auth;
11pub mod config;
12pub mod discovery;
13pub mod domain_data;
14#[cfg(target_family = "wasm")]
15pub mod wasm;
16
17use crate::auth::TokenCache;
18use crate::discovery::{DiscoveryService, DomainWithServer};
19#[derive(Debug, Clone)]
20pub struct DomainClient {
21 discovery_client: DiscoveryService,
22 pub client_id: String,
23}
24
25impl DomainClient {
26 pub fn new(api_url: &str, dds_url: &str, client_id: &str) -> Self {
27 if client_id.is_empty() {
28 panic!("client_id is empty");
29 }
30 Self {
31 discovery_client: DiscoveryService::new(api_url, dds_url, client_id),
32 client_id: client_id.to_string(),
33 }
34 }
35
36 pub async fn new_with_app_credential(
37 api_url: &str,
38 dds_url: &str,
39 client_id: &str,
40 app_key: &str,
41 app_secret: &str,
42 ) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
43 let mut dc = DomainClient::new(api_url, dds_url, client_id);
44 let _ = dc
45 .discovery_client
46 .sign_in_as_auki_app(app_key, app_secret)
47 .await?;
48 Ok(dc)
49 }
50
51 pub async fn new_with_user_credential(
52 api_url: &str,
53 dds_url: &str,
54 client_id: &str,
55 email: &str,
56 password: &str,
57 remember_password: bool,
58 ) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
59 let mut dc = DomainClient::new(api_url, dds_url, client_id);
60 let _ = dc
61 .discovery_client
62 .sign_in_with_auki_account(email, password, remember_password)
63 .await?;
64 Ok(dc)
65 }
66
67 pub fn with_oidc_access_token(&self, token: &str) -> Self {
68 Self {
69 discovery_client: self.discovery_client.with_oidc_access_token(token),
70 client_id: self.client_id.clone(),
71 }
72 }
73
74 pub async fn download_domain_data(
75 &self,
76 domain_id: &str,
77 query: &DownloadQuery,
78 ) -> Result<
79 Receiver<Result<DomainData, Box<dyn std::error::Error + Send + Sync>>>,
80 Box<dyn std::error::Error + Send + Sync>,
81 > {
82 let domain = self.discovery_client.auth_domain(domain_id).await?;
83 let rx = download_v1_stream(
84 &domain.domain.domain_server.url,
85 &self.client_id,
86 &domain.get_access_token(),
87 domain_id,
88 query,
89 )
90 .await?;
91 Ok(rx)
92 }
93
94 #[cfg(not(target_family = "wasm"))]
95 pub async fn upload_domain_data(
96 &self,
97 domain_id: &str,
98 data: Receiver<domain_data::UploadDomainData>,
99 ) -> Result<Vec<DomainDataMetadata>, Box<dyn std::error::Error + Send + Sync>> {
100 use crate::{auth::TokenCache, domain_data::upload_v1_stream};
101 let domain = self.discovery_client.auth_domain(domain_id).await?;
102 upload_v1_stream(
103 &domain.domain.domain_server.url,
104 &domain.get_access_token(),
105 domain_id,
106 data,
107 )
108 .await
109 }
110
111 #[cfg(target_family = "wasm")]
112 pub async fn upload_domain_data(
113 &self,
114 domain_id: &str,
115 data: Vec<UploadDomainData>,
116 ) -> Result<Vec<DomainDataMetadata>, Box<dyn std::error::Error + Send + Sync>> {
117 let domain = self.discovery_client.auth_domain(domain_id).await?;
118 upload_v1(
119 &domain.domain.domain_server.url,
120 &domain.get_access_token(),
121 domain_id,
122 data,
123 )
124 .await
125 }
126
127 pub async fn download_metadata(
128 &self,
129 domain_id: &str,
130 query: &DownloadQuery,
131 ) -> Result<Vec<DomainDataMetadata>, Box<dyn std::error::Error + Send + Sync>> {
132 let domain = self.discovery_client.auth_domain(domain_id).await?;
133 download_metadata_v1(
134 &domain.domain.domain_server.url,
135 &self.client_id,
136 &domain.get_access_token(),
137 domain_id,
138 query,
139 )
140 .await
141 }
142
143 pub async fn download_domain_data_by_id(
144 &self,
145 domain_id: &str,
146 id: &str,
147 ) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
148 let domain = self.discovery_client.auth_domain(domain_id).await?;
149 download_by_id(
150 &domain.domain.domain_server.url,
151 &self.client_id,
152 &domain.get_access_token(),
153 domain_id,
154 id,
155 )
156 .await
157 }
158
159 pub async fn delete_domain_data_by_id(
160 &self,
161 domain_id: &str,
162 id: &str,
163 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
164 let domain = self.discovery_client.auth_domain(domain_id).await?;
165 delete_by_id(
166 &domain.domain.domain_server.url,
167 &domain.get_access_token(),
168 domain_id,
169 id,
170 )
171 .await
172 }
173
174 pub async fn list_domains(
175 &self,
176 org: &str,
177 ) -> Result<Vec<DomainWithServer>, Box<dyn std::error::Error + Send + Sync>> {
178 self.discovery_client.list_domains(org).await
179 }
180
181
182}
183
184#[cfg(not(target_family = "wasm"))]
185#[cfg(test)]
186mod tests {
187 use std::time::Duration;
188
189 use crate::domain_data::{CreateDomainData, DomainAction, UpdateDomainData, UploadDomainData};
190
191 use super::*;
192 use futures::{StreamExt, channel::mpsc};
193 use tokio::{spawn, time::sleep};
194
195 fn get_config() -> (config::Config, String) {
196 if std::path::Path::new("../.env.local").exists() {
197 dotenvy::from_filename("../.env.local").ok();
198 dotenvy::dotenv().ok();
199 }
200 let config = config::Config::from_env().unwrap();
201 (config, std::env::var("DOMAIN_ID").unwrap())
202 }
203
204 #[tokio::test]
205 async fn test_download_domain_data_with_app_credential() {
206 let config = get_config();
208 let client = DomainClient::new_with_app_credential(
209 &config.0.api_url,
210 &config.0.dds_url,
211 &config.0.client_id,
212 &config.0.app_key.unwrap(),
213 &config.0.app_secret.unwrap(),
214 )
215 .await
216 .expect("Failed to create client");
217
218 let query = DownloadQuery {
220 ids: vec![],
221 name: None,
222 data_type: Some("dmt_accel_csv".to_string()),
223 };
224
225 let result = client.download_domain_data(&config.1, &query).await;
227
228 assert!(result.is_ok(), "error message : {:?}", result.err());
229
230 let mut rx = result.unwrap();
231 let mut count = 0;
232 while let Some(Ok(data)) = rx.next().await {
233 count += 1;
234 assert_eq!(data.metadata.data_type, "dmt_accel_csv");
235 }
236 assert!(count > 0);
237 }
238
239 #[tokio::test]
240 async fn test_upload_domain_data_with_user_credential() {
241 use futures::SinkExt;
242 let config = get_config();
243 let client = DomainClient::new_with_user_credential(
244 &config.0.api_url,
245 &config.0.dds_url,
246 &config.0.client_id,
247 &config.0.email.unwrap(),
248 &config.0.password.unwrap(),
249 true,
250 )
251 .await
252 .expect("Failed to create client");
253
254 let data = vec![
255 UploadDomainData {
256 action: DomainAction::Create(CreateDomainData {
257 name: "to be deleted".to_string(),
258 data_type: "test".to_string(),
259 }),
260 data: "{\"test\": \"test\"}".as_bytes().to_vec(),
261 },
262 UploadDomainData {
263 action: DomainAction::Update(UpdateDomainData {
264 id: "a84a36e5-312b-4f80-974a-06f5d19c1e16".to_string(),
265 }),
266 data: "{\"test\": \"test updated\"}".as_bytes().to_vec(),
267 },
268 ];
269 let (mut tx, rx) = mpsc::channel(10);
270 spawn(async move {
271 for d in data {
272 tx.send(d).await.unwrap();
273 }
274 tx.close().await.unwrap();
275 });
276 let result = client.upload_domain_data(&config.1, rx).await;
277
278 assert!(result.is_ok(), "error message : {:?}", result.err());
279
280 sleep(Duration::from_secs(5)).await;
281 let result = result.unwrap();
282 assert_eq!(result.len(), 2);
283
284 let ids = result.iter().map(|d| d.id.clone()).collect::<Vec<String>>();
285 assert_eq!(ids.len(), 2);
286 let query = DownloadQuery {
288 ids: ids,
289 name: None,
290 data_type: None,
291 };
292
293 let result = client.download_domain_data(&config.1, &query).await;
295
296 assert!(result.is_ok(), "error message : {:?}", result.err());
297
298 let mut to_delete = None;
299 let mut count = 0;
300 let mut rx = result.unwrap();
301 while let Some(Ok(data)) = rx.next().await {
302 count += 1;
303 if data.metadata.id == "a84a36e5-312b-4f80-974a-06f5d19c1e16" {
304 assert_eq!(data.data, b"{\"test\": \"test updated\"}");
305 continue;
306 } else {
307 assert_eq!(data.data, b"{\"test\": \"test\"}");
308 }
309 to_delete = Some(data.metadata.id.clone());
310 }
311 assert_eq!(count, 2);
312 assert_eq!(to_delete.is_some(), true);
313
314 let delete_result = client
316 .delete_domain_data_by_id(&config.1, &to_delete.unwrap())
317 .await;
318 assert!(
319 delete_result.is_ok(),
320 "Failed to delete data by id: {:?}",
321 delete_result.err()
322 );
323 }
324
325 #[tokio::test]
326 async fn test_download_domain_data_by_id() {
327 let config = get_config();
328 let client = DomainClient::new_with_app_credential(
329 &config.0.api_url,
330 &config.0.dds_url,
331 &config.0.client_id,
332 &config.0.app_key.unwrap(),
333 &config.0.app_secret.unwrap(),
334 )
335 .await
336 .expect("Failed to create client");
337
338 let download_result = client
340 .download_domain_data_by_id(&config.1, "a84a36e5-312b-4f80-974a-06f5d19c1e16")
341 .await;
342
343 assert!(
344 download_result.is_ok(),
345 "download by id failed: {:?}",
346 download_result.err()
347 );
348 let downloaded_bytes = download_result.unwrap();
349 assert_eq!(downloaded_bytes, b"{\"test\": \"test updated\"}".to_vec());
350 }
351
352 #[tokio::test]
353 async fn test_download_domain_metadata() {
354 let config = get_config();
355 let client = DomainClient::new_with_app_credential(
356 &config.0.api_url,
357 &config.0.dds_url,
358 &config.0.client_id,
359 &config.0.app_key.clone().unwrap(),
360 &config.0.app_secret.clone().unwrap(),
361 )
362 .await
363 .expect("Failed to create client");
364
365 let result = client
367 .download_metadata(
368 &config.1,
369 &DownloadQuery {
370 ids: vec![],
371 name: None,
372 data_type: Some("test".to_string()),
373 },
374 )
375 .await;
376 assert!(
377 result.is_ok(),
378 "Failed to download domain metadata: {:?}",
379 result.err()
380 );
381 let result = result.unwrap();
382 assert!(result.len() > 0);
383 for meta in result {
384 assert!(!meta.id.is_empty());
385 assert_eq!(meta.domain_id, config.1);
386 assert!(!meta.name.is_empty());
387 assert_eq!(meta.data_type, "test");
388 }
389 }
390
391 #[tokio::test]
392 async fn test_load_domain_with_oidc_access_token() {
393 let config = get_config();
394 let oidc_access_token =
396 std::env::var("AUTH_TEST_TOKEN").expect("AUTH_TEST_TOKEN env var not set");
397 if oidc_access_token.is_empty() {
398 eprintln!("Missing AUTH_TEST_TOKEN, skipping test");
399 return;
400 }
401
402 let client =
403 DiscoveryService::new(&config.0.api_url, &config.0.dds_url, &config.0.client_id);
404
405 let domain = client
406 .with_oidc_access_token(&oidc_access_token)
407 .auth_domain(&config.1)
408 .await;
409 assert!(domain.is_ok(), "Failed to get domain: {:?}", domain.err());
410 assert_eq!(domain.unwrap().domain.id, config.1);
411 }
412
413 #[tokio::test]
414 async fn test_list_domains() {
415 let config = get_config();
416 let client = DomainClient::new_with_app_credential(
417 &config.0.api_url,
418 &config.0.dds_url,
419 &config.0.client_id,
420 &config.0.app_key.unwrap(),
421 &config.0.app_secret.unwrap(),
422 )
423 .await
424 .expect("Failed to create client");
425
426 let org = std::env::var("TEST_ORGANIZATION").unwrap_or("own".to_string());
427 let result = client.list_domains(&org).await.unwrap();
428 assert!(result.len() > 0, "No domains found");
429 }
430}