1use anyhow::{Context, Result};
7use serde::Serialize;
8
9use raps_kernel::http;
10
11use crate::DesignAutomationClient;
12use crate::types::*;
13
14impl DesignAutomationClient {
15 pub async fn list_appbundles(&self) -> Result<Vec<String>> {
17 let token = self.auth.get_token().await?;
18 let url = format!("{}/appbundles", self.config.da_url());
19
20 let response = http::send_with_retry(&self.config.http_config, || {
21 self.http_client.get(&url).bearer_auth(&token)
22 })
23 .await?;
24
25 if !response.status().is_success() {
26 let status = response.status();
27 let error_text = response.text().await.unwrap_or_default();
28 anyhow::bail!("Failed to list appbundles ({status}): {error_text}");
29 }
30
31 let paginated: PaginatedResponse<String> = response
32 .json()
33 .await
34 .context("Failed to parse appbundles response")?;
35
36 Ok(paginated.data)
37 }
38
39 pub async fn create_appbundle(
41 &self,
42 id: &str,
43 engine: &str,
44 description: Option<&str>,
45 ) -> Result<AppBundleDetails> {
46 let token = self.auth.get_token().await?;
47 let url = format!("{}/appbundles", self.config.da_url());
48
49 let request = CreateAppBundleRequest {
50 id: id.to_string(),
51 engine: engine.to_string(),
52 description: description.map(|s| s.to_string()),
53 };
54
55 let response = http::send_with_retry(&self.config.http_config, || {
56 self.http_client
57 .post(&url)
58 .bearer_auth(&token)
59 .header("Content-Type", "application/json")
60 .json(&request)
61 })
62 .await?;
63
64 if !response.status().is_success() {
65 let status = response.status();
66 let error_text = response.text().await.unwrap_or_default();
67 anyhow::bail!("Failed to create appbundle ({status}): {error_text}");
68 }
69
70 let appbundle: AppBundleDetails = response
71 .json()
72 .await
73 .context("Failed to parse appbundle response")?;
74
75 Ok(appbundle)
76 }
77
78 pub async fn create_appbundle_alias(
80 &self,
81 bundle_id: &str,
82 alias: &str,
83 version: i32,
84 ) -> Result<()> {
85 let token = self.auth.get_token().await?;
86 let url = format!("{}/appbundles/{}/aliases", self.config.da_url(), bundle_id);
87
88 #[derive(Serialize)]
89 struct AliasRequest {
90 id: String,
91 version: i32,
92 }
93
94 let request = AliasRequest {
95 id: alias.to_string(),
96 version,
97 };
98
99 let response = http::send_with_retry(&self.config.http_config, || {
100 self.http_client
101 .post(&url)
102 .bearer_auth(&token)
103 .header("Content-Type", "application/json")
104 .json(&request)
105 })
106 .await?;
107
108 if !response.status().is_success() {
109 let status = response.status();
110 let error_text = response.text().await.unwrap_or_default();
111 anyhow::bail!("Failed to create appbundle alias ({status}): {error_text}");
112 }
113
114 Ok(())
115 }
116
117 pub async fn delete_appbundle(&self, id: &str) -> Result<()> {
119 let token = self.auth.get_token().await?;
120 let url = format!("{}/appbundles/{}", self.config.da_url(), id);
121
122 let response = http::send_with_retry(&self.config.http_config, || {
123 self.http_client.delete(&url).bearer_auth(&token)
124 })
125 .await?;
126
127 if !response.status().is_success() {
128 let status = response.status();
129 let error_text = response.text().await.unwrap_or_default();
130 anyhow::bail!("Failed to delete appbundle ({status}): {error_text}");
131 }
132
133 Ok(())
134 }
135
136 pub async fn upload_appbundle(
146 &self,
147 upload_params: &UploadParameters,
148 file_path: &std::path::Path,
149 ) -> Result<()> {
150 let endpoint_url = upload_params
151 .endpoint_url
152 .as_deref()
153 .ok_or_else(|| anyhow::anyhow!("Upload parameters missing endpoint URL"))?;
154
155 if !file_path.exists() {
157 anyhow::bail!("File not found: {}", file_path.display());
158 }
159
160 let extension = file_path.extension().and_then(|e| e.to_str()).unwrap_or("");
161 if extension != "zip" {
162 anyhow::bail!(
163 "Expected .zip archive, got .{} ({})",
164 extension,
165 file_path.display()
166 );
167 }
168
169 let file_bytes = tokio::fs::read(file_path)
171 .await
172 .with_context(|| format!("Failed to read file: {}", file_path.display()))?;
173
174 let file_name = file_path
175 .file_name()
176 .and_then(|n| n.to_str())
177 .unwrap_or("bundle.zip")
178 .to_string();
179
180 let mut form = reqwest::multipart::Form::new();
182
183 if let Some(ref form_data) = upload_params.form_data {
185 for (key, value) in form_data {
186 form = form.text(key.clone(), value.clone());
187 }
188 }
189
190 let file_part = reqwest::multipart::Part::bytes(file_bytes)
192 .file_name(file_name)
193 .mime_str("application/octet-stream")?;
194 form = form.part("file", file_part);
195
196 let response = self
198 .http_client
199 .post(endpoint_url)
200 .multipart(form)
201 .send()
202 .await
203 .context("Failed to upload app bundle archive")?;
204
205 if !response.status().is_success() {
206 let status = response.status();
207 let error_text = response.text().await.unwrap_or_default();
208 anyhow::bail!("Failed to upload app bundle ({status}): {error_text}");
209 }
210
211 Ok(())
212 }
213}