1use reqwest::{Client, RequestBuilder, StatusCode};
2use std::str::FromStr;
3
4use serde::Deserialize;
5use std::error::Error;
6use std::fs::File;
7use std::io::Write;
8use std::path::Path;
9pub struct Api {
10 client: Client,
11 api_url: String,
12 base_url: String,
13 credentials: Credentials,
14}
15pub struct Credentials {
16 pub user: String,
17 pub password: String,
18}
19
20#[derive(Deserialize, Debug)]
21#[serde(rename_all = "camelCase")]
22struct Response {
23 package_id: u128,
24 package_name: String,
25 status: String,
26 message: String,
27}
28#[derive(Debug, PartialEq, Eq)]
29pub enum PackageStatus {
30 New,
31 ExportPending,
32 Exported,
33 ExportFailed,
34 ImportFailed,
35 Imported,
36 ImportPending,
37}
38
39impl FromStr for PackageStatus {
40 type Err = ();
41 fn from_str(s: &str) -> Result<Self, ()> {
42 match s {
43 "New" => Ok(Self::New),
44 "Export Pending" => Ok(Self::ExportPending),
45 "Exported" => Ok(Self::Exported),
46 "Export Failed" => Ok(Self::ExportFailed),
47 "Import Failed" => Ok(Self::ImportFailed),
48 "Imported" => Ok(Self::Imported),
49 "Import Pending" => Ok(Self::ImportPending),
50 _ => Err(()),
51 }
52 }
53}
54
55impl Api {
56 pub fn new(base_url: &str, credentials: Credentials) -> Api {
57 let api_url = String::from(base_url) + "/api/p/v1/om";
58 let client = Client::builder().cookie_store(true).build().unwrap();
59 Api {
60 client: client,
61 api_url,
62 base_url: base_url.to_owned(),
63 credentials,
64 }
65 }
66
67 pub async fn login(&self) -> Result<(), Box<dyn Error>> {
68 let body = serde_json::json!({
69 "userName": self.credentials.user,
70 "password": self.credentials.password,
71 "normal": false,
72 "api": true,
73 });
74 let url = self.base_url.clone() + "/p/websignon/signon";
75 let response = self.client.post(url).json(&body).send().await?;
76 match response.status() {
77 StatusCode::OK => {
78 println!("🔐 TRIRIGA Authenticated");
79 Ok(())
80 }
81 _ => Err("Unauthenticated")?,
82 }
83 }
84
85 #[allow(dead_code)]
86 fn authenticated_post(&self, url: &str) -> RequestBuilder {
87 self.client.post(url)
88 }
89 fn get(&self, url: &str) -> RequestBuilder {
90 self.client.get(url)
91 }
92
93 #[allow(dead_code)]
94 pub async fn add_all_application_dependant_objects_to_package_by_id(
95 &self,
96 package_id: u128,
97 date_in_millis: &str,
98 ) -> Result<(), Box<dyn std::error::Error>> {
99 let url = self.api_url.clone()
100 + "/addAllApplicationDependentObjectsToPackage"
101 + "/" + &package_id.to_string();
102 self.get(&url)
103 .query(&[("dateInMillis", date_in_millis)])
104 .send()
105 .await?;
106 Ok(())
107 }
108
109 #[allow(dead_code)]
110 pub async fn add_all_application_dependant_objects_to_package_by_name(
111 &self,
112 package_name: &str,
113 date_in_millis: &str,
114 ) -> Result<(), Box<dyn std::error::Error>> {
115 let url = self.api_url.clone() + "/addAllApplicationDependentObjectsToPackage";
116 self.get(&url)
117 .query(&[
118 ("packageName", package_name),
119 ("dateInMillis", date_in_millis),
120 ])
121 .send()
122 .await?;
123 Ok(())
124 }
125 #[allow(dead_code)]
126 pub async fn add_object_to_package_by_name(
127 &self,
128 package_name: &str,
129 object_type: &str,
130 object_name: &str,
131 module_name: &str,
132 bo_name: &str,
133 ) -> Result<(), Box<dyn Error>> {
134 let url = self.api_url.clone() + "/addObjectToPackage";
135 let correct_bo_name = if bo_name == "~" { "" } else { bo_name };
137 let res = self
138 .get(&url)
139 .query(&[
140 ("packageName", package_name),
141 ("objectType", object_type),
142 ("objectName", object_name),
143 ("moduleName", module_name),
144 ("boName", correct_bo_name),
145 ])
146 .send()
147 .await?;
148 match res.status() {
149 StatusCode::OK => Ok(()),
150 _ => panic!(
151 "Couldn't add object {} to package {}",
152 &object_name, &package_name
153 ),
154 }
155 }
156 pub async fn add_object_to_package_by_id(
157 &self,
158 package_id: &str,
159 object_type: &str,
160 object_name: &str,
161 module_name: &str,
162 bo_name: &str,
163 ) -> Result<(), Box<dyn Error>> {
164 let url = self.api_url.clone() + "/addObjectToPackage" + "/" + package_id;
165 let correct_bo_name = if bo_name == "~" { "" } else { bo_name };
167 let res = self
168 .get(&url)
169 .query(&[
170 ("objectType", object_type),
171 ("objectName", object_name),
172 ("moduleName", module_name),
173 ("boName", correct_bo_name),
174 ])
175 .send()
176 .await?;
177 match res.status() {
178 StatusCode::OK => Ok(()),
179 _ => panic!(
180 "Couldn't add object {} to package {}. {:#?}",
181 &object_name, &package_id, &res
182 ),
183 }
184 }
185
186 pub async fn create_new_package(
187 &self,
188 name: &str,
189 description: &str,
190 ) -> Result<u128, Box<dyn Error>> {
191 let url = self.api_url.clone() + "/createEmptyPackage";
192 let res = self
193 .get(&url)
194 .query(&[("packageName", name), ("packageDescription", description)])
195 .send()
196 .await?;
197 let parsed_res = res.json::<Response>().await?;
198 Ok(parsed_res.package_id)
199 }
200
201 pub async fn download_package_as_zip(
202 &self,
203 package_name: &str,
204 path: &Path,
205 ) -> Result<(), Box<dyn Error>> {
206 let url = self.api_url.clone() + "/downloadPackageFromUserFiles";
207 let res = self
208 .get(&url)
209 .query(&[("packageName", package_name)])
210 .send()
211 .await?;
212 let content = res.bytes().await?;
213 self.save_as_file(path, &content)?;
214 Ok(())
215 }
216
217 #[allow(dead_code)]
218 pub async fn export_package_by_name(
219 &self,
220 package_name: &str,
221 ) -> Result<(), Box<dyn Error>> {
222 let url = self.api_url.clone() + "/export";
223 let res = self
224 .get(&url)
225 .query(&[("packageName", package_name)])
226 .send()
227 .await?;
228 match res.status() {
229 StatusCode::OK => Ok(()),
230 e => panic!("Unable to export package. {}", e),
231 }
232 }
233 pub async fn export_package_by_id(&self, package_id: &str) -> Result<(), Box<dyn Error>> {
234 let url = self.api_url.clone() + "/export/" + package_id;
235 let res = self.get(&url).send().await?;
236 match res.status() {
237 StatusCode::OK => Ok(()),
238 e => panic!("Unable to export package. {}", e),
239 }
240 }
241
242 pub async fn get_package_status_by_id(
243 &self,
244 package_id: &str,
245 ) -> Result<PackageStatus, Box<dyn Error>> {
246 let url = self.api_url.clone() + "/packageStatus/" + package_id;
247 let res = self.get(&url).send().await?;
248 let parsed_res = self.parse_response(res).await?;
249
250 match PackageStatus::from_str(&parsed_res.status) {
251 Ok(s) => Ok(s),
252 Err(_) => panic!("Could not parse status"),
253 }
254 }
255
256 pub async fn get_package_status_by_name(
257 &self,
258 package_name: &str,
259 ) -> Result<PackageStatus, Box<dyn Error>> {
260 let url = self.api_url.clone() + "/packageStatus";
261 let res = self
262 .get(&url)
263 .query(&[("packageName", package_name)])
264 .send()
265 .await?;
266 let parsed_res = self.parse_response(res).await?;
267 match PackageStatus::from_str(&parsed_res.status) {
268 Ok(s) => Ok(s),
269 Err(_) => panic!("Could not parse status"),
270 }
271 }
272
273 fn save_as_file(&self, path: &Path, content: &[u8]) -> Result<(), Box<dyn Error>> {
274 if path.exists() {
275 println!("⚠️ [WARN] File {:?} exists. Overwriting...", path)
276 } else {
277 println!("🆕 Creating file {:?}", path)
278 }
279 let mut file = File::create(path)?;
280 file.write_all(content)?;
281 Ok(())
282 }
283
284 async fn parse_response(&self, r: reqwest::Response) -> Result<Response, Box<dyn Error>> {
285 match r.json::<Response>().await {
286 Ok(res) => Ok(res),
287 Err(e) => panic!("Failed to parse response from API: {}", e,),
288 }
289 }
290}