1use std::collections::HashMap;
2use std::str::FromStr;
3
4use anyhow::Result;
5use reqwest::Method;
6use serde::de::DeserializeOwned;
7
8use async_trait::async_trait;
9use reqwest::Client as AsyncClient;
10
11use crate::response_types::*;
12use crate::Gotify;
13
14pub struct AsyncGotify<'a> {
16 gotify: Gotify<'a>,
17 client: AsyncClient,
18}
19
20impl<'a> AsyncGotify<'a> {
21 pub fn new(
22 base_url: &'a str,
23 app_token: Option<&'a str>,
24 client_token: Option<&'a str>,
25 ) -> Self {
26 let gotify = Gotify::new(base_url, app_token, client_token);
27 let client = AsyncClient::new();
28 Self { gotify, client }
29 }
30 pub fn from(gotify: Gotify<'a>) -> Self {
31 let client = AsyncClient::new();
32 Self { gotify, client }
33 }
34
35 fn get_token(&self, auth_mode: Option<&str>) -> Option<&'a str> {
36 if let Some(mode) = auth_mode {
37 if mode == "app" {
38 self.gotify.app_token
39 } else {
40 self.gotify.client_token
41 }
42 } else {
43 self.gotify.app_token
44 }
45 }
46}
47
48#[async_trait]
49trait AsyncGotifyImpl {
50 async fn do_request<T: DeserializeOwned>(
51 &self,
52 method: &str,
53 endpoint_url: &str,
54 data: Option<HashMap<String, Option<String>>>,
55 file: Option<tokio::fs::File>,
56 auth_mode: Option<&str>,
57 ) -> Result<T>;
58
59 async fn applications(&self) -> Result<Vec<Application>>;
60
61 async fn create_application(&self, name: String, description: String) -> Result<Application>;
62
63 async fn update_application(
64 &self,
65 id: i32,
66 name: String,
67 description: Option<String>,
68 ) -> Result<Application>;
69
70 async fn delete_application(&self, id: i32) -> Result<()>;
71
72 async fn upload_application_image(
73 &self,
74 id: i32,
75 image: tokio::fs::File,
76 ) -> Result<Application>;
77
78 async fn get_messages(
79 &self,
80 app_id: Option<i32>,
81 limit: Option<i32>,
82 since: Option<i32>,
83 ) -> Result<PagedMessages>;
84
85 async fn create_message(
86 &self,
87 message: String,
88 priority: Option<i32>,
89 title: Option<String>,
90 ) -> Result<Message>;
92
93 async fn delete_messages(&self, app_id: Option<i32>) -> Result<()>;
94
95 async fn delete_message(&self, msg_id: i32) -> Result<()>;
96
97 async fn get_clients(&self) -> Result<Vec<Client>>;
98
99 async fn create_client(&self, name: String) -> Result<Client>;
100
101 async fn update_client(&self, id: i32, name: String) -> Result<Client>;
102
103 async fn delete_client(&self, id: i32) -> Result<()>;
104
105 async fn get_current_user(&self) -> Result<User>;
106
107 async fn set_password(&self, passwd: String) -> Result<()>;
108
109 async fn get_users(&self) -> Result<Vec<User>>;
110
111 async fn create_user(&self, name: String, passwd: String, admin: Option<bool>) -> Result<User>;
112
113 async fn get_user(&self, id: i32) -> Result<User>;
114
115 async fn update_user(
116 &self,
117 id: i32,
118 name: Option<String>,
119 passwd: Option<String>,
120 admin: Option<bool>,
121 ) -> Result<User>;
122
123 async fn delete_user(&self, id: i32) -> Result<()>;
124
125 async fn get_health(&self) -> Result<Health>;
126
127 async fn get_plugins(&self) -> Result<Vec<PluginConf>>;
128
129 async fn get_plugin_config(&self, id: i32) -> Result<PluginConf>;
130
131 async fn disable_plugin(&self, id: i32) -> Result<()>;
135
136 async fn get_plugin_display(&self, id: i32) -> Result<String>;
137
138 async fn enable_plugin(&self, id: i32) -> Result<()>;
139
140 async fn get_version(&self) -> Result<VersionInfo>;
141}
142
143#[async_trait]
144impl<'a> AsyncGotifyImpl for AsyncGotify<'a> {
145 async fn do_request<T: DeserializeOwned>(
146 &self,
147 method: &str,
148 endpoint_url: &str,
149 data: Option<HashMap<String, Option<String>>>,
150 file: Option<tokio::fs::File>,
151 auth_mode: Option<&str>,
152 ) -> Result<T> {
153 let method = Method::from_str(&method)?;
154 let request_url = format!("{}/{}", self.gotify.base_url, endpoint_url);
155 let mut request = self.client.request(method.clone(), request_url);
156 if let Some(f) = file {
157 request = request.body(f);
158 } else {
159 if let Some(data) = data {
160 match method {
161 Method::GET => {
162 request = request.query(&data);
163 }
164 _ => {
165 request = request.json(&data);
166 }
167 }
168 request = request.json(&data);
169 }
170 }
171 let token = self.get_token(auth_mode).expect("missing token");
172 request = request.header("X-Gotify-Key", token);
173 let response = request.send().await?;
174 Ok(response.json::<T>().await?)
175 }
176
177 async fn applications(&self) -> Result<Vec<Application>> {
178 self.do_request("get", "/applications", None, None, None)
179 .await
180 }
181
182 async fn create_application(&self, name: String, description: String) -> Result<Application> {
183 let mut data = HashMap::new();
184 data.insert("name".to_owned(), Some(name));
185 data.insert("description".to_owned(), Some(description));
186 self.do_request("post", "/application", Some(data), None, None)
187 .await
188 }
189
190 async fn update_application(
191 &self,
192 id: i32,
193 name: String,
194 description: Option<String>,
195 ) -> Result<Application> {
196 let mut data = HashMap::new();
197 data.insert("name".to_string(), Some(name));
198 data.insert("description".to_string(), description);
199 self.do_request("put", &format!("/application/{id}"), Some(data), None, None)
200 .await
201 }
202
203 async fn delete_application(&self, id: i32) -> Result<()> {
204 self.do_request("delete", &format!("/application/{id}"), None, None, None)
205 .await
206 }
207
208 async fn upload_application_image(
209 &self,
210 id: i32,
211 image: tokio::fs::File,
212 ) -> Result<Application> {
213 self.do_request(
214 "post",
215 &format!("/application/{id}/image"),
216 None,
217 Some(image),
218 None,
219 )
220 .await
221 }
222
223 async fn get_messages(
224 &self,
225 app_id: Option<i32>,
226 limit: Option<i32>,
227 since: Option<i32>,
228 ) -> Result<PagedMessages> {
229 let mut data = HashMap::new();
230 data.insert("limit".to_string(), limit.map(|i| i.to_string()));
231 data.insert("since".to_string(), since.map(|i| i.to_string()));
232 if let Some(id) = app_id {
233 self.do_request(
234 "get",
235 &format!("/application/{id}/message"),
236 Some(data),
237 None,
238 None,
239 )
240 .await
241 } else {
242 self.do_request("get", "/message", Some(data), None, None)
243 .await
244 }
245 }
246
247 async fn create_message(
248 &self,
249 message: String,
250 priority: Option<i32>,
251 title: Option<String>,
252 ) -> Result<Message> {
254 let mut data = HashMap::new();
255 data.insert("message".to_string(), Some(message));
256 data.insert("priority".to_string(), priority.map(|i| i.to_string()));
257 data.insert("title".to_string(), title);
258 self.do_request("post", "/message", Some(data), None, Some("app"))
259 .await
260 }
261
262 async fn delete_messages(&self, app_id: Option<i32>) -> Result<()> {
263 if let Some(id) = app_id {
264 self.do_request(
265 "delete",
266 &format!("/application/{id}/message"),
267 None,
268 None,
269 None,
270 )
271 .await
272 } else {
273 self.do_request("delete", &format!("/message"), None, None, None)
274 .await
275 }
276 }
277
278 async fn delete_message(&self, msg_id: i32) -> Result<()> {
279 self.do_request("delete", &format!("/message/{msg_id}"), None, None, None)
280 .await
281 }
282
283 async fn get_clients(&self) -> Result<Vec<Client>> {
284 self.do_request("get", "/client", None, None, None).await
285 }
286
287 async fn create_client(&self, name: String) -> Result<Client> {
288 let mut data = HashMap::new();
289 data.insert("name".to_string(), Some(name));
290 self.do_request("post", "/client", Some(data), None, None)
291 .await
292 }
293
294 async fn update_client(&self, id: i32, name: String) -> Result<Client> {
295 let mut data = HashMap::new();
296 data.insert("name".to_string(), Some(name));
297 self.do_request("put", &format!("/client/{id}"), Some(data), None, None)
298 .await
299 }
300
301 async fn delete_client(&self, id: i32) -> Result<()> {
302 self.do_request("delete", &format!("/client/{id}"), None, None, None)
303 .await
304 }
305
306 async fn get_current_user(&self) -> Result<User> {
307 self.do_request("get", "/current/user", None, None, None)
308 .await
309 }
310
311 async fn set_password(&self, passwd: String) -> Result<()> {
312 let mut data = HashMap::new();
313 data.insert("pass".to_string(), Some(passwd));
314 self.do_request("get", "/current/user/password", Some(data), None, None)
315 .await
316 }
317
318 async fn get_users(&self) -> Result<Vec<User>> {
319 self.do_request("get", "/user", None, None, None).await
320 }
321
322 async fn create_user(&self, name: String, passwd: String, admin: Option<bool>) -> Result<User> {
323 let mut data = HashMap::new();
324 data.insert("pass".to_string(), Some(passwd));
325 data.insert("name".to_string(), Some(name));
326 data.insert(
327 "admin".to_string(),
328 Some(if admin.unwrap_or(false) {
329 "true".to_string()
330 } else {
331 "false".to_string()
332 }),
333 );
334 self.do_request("post", "/user", Some(data), None, None)
335 .await
336 }
337
338 async fn get_user(&self, id: i32) -> Result<User> {
339 self.do_request("get", &format!("/user/{id}"), None, None, None)
340 .await
341 }
342
343 async fn update_user(
344 &self,
345 id: i32,
346 name: Option<String>,
347 passwd: Option<String>,
348 admin: Option<bool>,
349 ) -> Result<User> {
350 let mut data = HashMap::new();
351 data.insert("pass".to_string(), passwd);
352 data.insert("name".to_string(), name);
353 data.insert(
354 "admin".to_string(),
355 Some(if admin.unwrap_or(false) {
356 "true".to_string()
357 } else {
358 "false".to_string()
359 }),
360 );
361 self.do_request("put", &format!("/user/{id}"), Some(data), None, None)
362 .await
363 }
364
365 async fn delete_user(&self, id: i32) -> Result<()> {
366 self.do_request("delete", &format!("/user/{id}"), None, None, None)
367 .await
368 }
369
370 async fn get_health(&self) -> Result<Health> {
371 self.do_request("get", "/health", None, None, None).await
372 }
373
374 async fn get_plugins(&self) -> Result<Vec<PluginConf>> {
375 self.do_request("get", "/plugins", None, None, None).await
376 }
377
378 async fn get_plugin_config(&self, id: i32) -> Result<PluginConf> {
379 self.do_request("get", &format!("/plugins/{id}/config"), None, None, None)
380 .await
381 }
382
383 async fn disable_plugin(&self, id: i32) -> Result<()> {
389 self.do_request("post", &format!("/plugins/{id}/disable"), None, None, None)
390 .await
391 }
392
393 async fn get_plugin_display(&self, id: i32) -> Result<String> {
394 self.do_request("get", &format!("/plugins/{id}/display"), None, None, None)
395 .await
396 }
397
398 async fn enable_plugin(&self, id: i32) -> Result<()> {
399 self.do_request("post", &format!("/plugins/{id}/enable"), None, None, None)
400 .await
401 }
402
403 async fn get_version(&self) -> Result<VersionInfo> {
404 self.do_request("get", "/version", None, None, None).await
405 }
406}