trisync/tririga/
api.rs

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		// A hack due to yaml deserialization bug. serde_yaml @ 0.8
136		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		// A hack due to yaml deserialization bug. serde_yaml @ 0.8
166		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}