dapnet_api/
client.rs

1use crate::{
2    Call, Callsign, News, Node, OutgoingCall, OutgoingNews, Rubric, Statistics, Transmitter,
3    TransmitterGroup,
4};
5use reqwest::StatusCode;
6use serde::{Deserialize, Serialize};
7use url::Url;
8
9#[derive(Clone, Debug)]
10pub struct ClientConfig {
11    pub api_url: Url,
12}
13
14impl Default for ClientConfig {
15    fn default() -> Self {
16        Self {
17            api_url: Url::parse("https://hampager.de/api/").unwrap(),
18        }
19    }
20}
21
22#[derive(Clone, Debug)]
23pub struct Client {
24    client: reqwest::Client,
25    username: String,
26    password: String,
27    config: ClientConfig,
28}
29
30impl Client {
31    /// Creates a new instance of the client with the default configuration.
32    ///
33    /// Example:
34    /// ```
35    /// use dapnet_api::Client;
36    /// let client = Client::new("m0nxn", "my_super_secret_password");
37    /// ```
38    pub fn new(username: &str, password: &str) -> Self {
39        Self {
40            client: reqwest::Client::new(),
41            username: username.to_string(),
42            password: password.to_string(),
43            config: ClientConfig::default(),
44        }
45    }
46
47    async fn get<T: for<'de> Deserialize<'de>>(&self, path: &str) -> crate::Result<Option<T>> {
48        let result = self
49            .client
50            .get(self.config.api_url.join(path)?)
51            .basic_auth(&self.username, Some(&self.password))
52            .send()
53            .await?;
54
55        if result.status().is_success() {
56            Ok(Some(result.json().await?))
57        } else if result.status() == StatusCode::NOT_FOUND {
58            Ok(None)
59        } else {
60            Err(crate::Error::ApiError(result.status()))
61        }
62    }
63
64    async fn get_many<T: for<'de> Deserialize<'de>>(
65        &self,
66        path: &str,
67    ) -> crate::Result<Option<Vec<T>>> {
68        let result = self
69            .client
70            .get(self.config.api_url.join(path)?)
71            .basic_auth(&self.username, Some(&self.password))
72            .send()
73            .await?;
74
75        if result.status().is_success() {
76            Ok(Some(result.json().await?))
77        } else if result.status() == StatusCode::NOT_FOUND {
78            Ok(None)
79        } else {
80            Err(crate::Error::ApiError(result.status()))
81        }
82    }
83
84    async fn post<T: Serialize + ?Sized>(&self, path: &str, item: &T) -> crate::Result<()> {
85        let result = self
86            .client
87            .post(self.config.api_url.join(path)?)
88            .basic_auth(&self.username, Some(&self.password))
89            .json(item)
90            .send()
91            .await?;
92
93        if result.status().is_success() {
94            Ok(())
95        } else {
96            Err(crate::Error::ApiError(result.status()))
97        }
98    }
99
100    pub async fn get_statistics(&self) -> crate::Result<Option<Statistics>> {
101        self.get("stats").await
102    }
103
104    pub async fn get_calls_by(&self, owner: &str) -> crate::Result<Option<Vec<Call>>> {
105        self.get_many(&format!("calls?ownerName={}", owner)).await
106    }
107
108    /// Sends a new call/message.
109    ///
110    /// Example:
111    /// ```no_run
112    /// # use dapnet_api::{Client, OutgoingCallBuilder};
113    /// # #[tokio::main]
114    /// # async fn main() {
115    /// # let client = Client::new("m0nxn", "my_super_secret_password");
116    /// client
117    ///     .new_call(&OutgoingCallBuilder::default()
118    ///         .text("M0NXN: this is a test".to_string())
119    ///         .recipients(vec!["m0nxn".to_string()])
120    ///         .transmitter_groups(vec!["uk-all".to_string()])
121    ///         .build()
122    ///         .unwrap()
123    ///     )
124    ///     .await
125    ///     .unwrap();
126    /// # }
127    /// ```
128    pub async fn new_call(&self, call: &OutgoingCall) -> crate::Result<()> {
129        self.post("calls", call).await
130    }
131
132    pub async fn get_all_nodes(&self) -> crate::Result<Option<Vec<Node>>> {
133        self.get_many("nodes").await
134    }
135
136    pub async fn get_node(&self, name: &str) -> crate::Result<Option<Node>> {
137        self.get(&format!("nodes/{}", name)).await
138    }
139
140    pub async fn get_all_callsigns(&self) -> crate::Result<Option<Vec<Callsign>>> {
141        self.get_many("callsigns").await
142    }
143
144    pub async fn get_callsign(&self, name: &str) -> crate::Result<Option<Callsign>> {
145        self.get(&format!("callsigns/{}", name)).await
146    }
147
148    pub async fn get_all_transmitters(&self) -> crate::Result<Option<Vec<Transmitter>>> {
149        self.get_many("transmitters").await
150    }
151
152    pub async fn get_transmitter(&self, name: &str) -> crate::Result<Option<Transmitter>> {
153        self.get(&format!("transmitters/{}", name)).await
154    }
155
156    pub async fn get_all_transmitter_groups(&self) -> crate::Result<Option<Vec<TransmitterGroup>>> {
157        self.get_many("transmitterGroups").await
158    }
159
160    pub async fn get_transmitter_group(
161        &self,
162        name: &str,
163    ) -> crate::Result<Option<TransmitterGroup>> {
164        self.get(&format!("transmitterGroups/{}", name)).await
165    }
166
167    pub async fn get_all_rubrics(&self) -> crate::Result<Option<Vec<Rubric>>> {
168        self.get_many("rubrics").await
169    }
170
171    pub async fn get_rubric(&self, name: &str) -> crate::Result<Option<Rubric>> {
172        self.get(&format!("rubrics/{}", name)).await
173    }
174
175    pub async fn get_news(&self, name: &str) -> crate::Result<Option<Vec<News>>> {
176        match self
177            .get_many::<Option<News>>(&format!("news?rubricName={}", name))
178            .await?
179        {
180            Some(v) => Ok(Some(v.into_iter().flatten().collect())),
181            None => Ok(None),
182        }
183    }
184
185    /// Sends news to a rubric.
186    ///
187    /// Example:
188    /// ```no_run
189    /// # use dapnet_api::{Client, OutgoingNewsBuilder};
190    /// # #[tokio::main]
191    /// # async fn main() {
192    /// # let client = Client::new("m0nxn", "my_super_secret_password");
193    /// client
194    ///     .new_news(&OutgoingNewsBuilder::default()
195    ///         .rubric("some_rubric_name".to_string())
196    ///         .text("M0NXN: this is a test".to_string())
197    ///         .build()
198    ///         .unwrap()
199    ///     )
200    ///     .await
201    ///     .unwrap();
202    /// # }
203    /// ```
204    pub async fn new_news(&self, news: &OutgoingNews) -> crate::Result<()> {
205        self.post("news", news).await
206    }
207}