Skip to main content

poly_x_tracker/
client.rs

1use chrono::{DateTime, Utc};
2use reqwest::Client;
3use std::sync::Arc;
4use url::Url;
5
6use thiserror::Error;
7
8use crate::{
9    consts::BASE_URL,
10    model::{BaseResponse, Metric, Platform, Post, Tracking, User, UserTrackings},
11};
12
13#[derive(Error, Debug)]
14pub enum XTrackerError {
15    #[error("URL parsing failed: {0}")]
16    Url(#[from] url::ParseError),
17    #[error("HTTP request failed: {0}")]
18    Request(#[from] reqwest::Error),
19    #[error("Server returned an error: {0}")]
20    Server(String),
21}
22
23#[derive(Debug, Clone)]
24pub struct XTrackerClient(Arc<ClientInner>);
25
26#[derive(Debug)]
27struct ClientInner {
28    base_url: Url,
29    client: Client,
30}
31
32impl Default for XTrackerClient {
33    fn default() -> Self {
34        Self::new()
35    }
36}
37
38impl XTrackerClient {
39    pub fn new() -> Self {
40        Self(Arc::new(ClientInner {
41            base_url: Url::parse(BASE_URL).unwrap(),
42            client: Client::new(),
43        }))
44    }
45
46    pub fn new_with_url(base_url: impl Into<Url>) -> Self {
47        Self(Arc::new(ClientInner {
48            base_url: base_url.into(),
49            client: Client::new(),
50        }))
51    }
52}
53
54impl XTrackerClient {
55    pub async fn get_users(
56        &self,
57        platform: impl Into<Option<Platform>>,
58        active_only: bool,
59    ) -> Result<Vec<User>, XTrackerError> {
60        let mut url = self.0.base_url.join("users")?;
61
62        {
63            let mut query = url.query_pairs_mut();
64
65            if let Some(platform) = platform.into() {
66                query.append_pair("platform", &platform.to_string());
67            }
68
69            query.append_pair("includeInactive", &(!active_only).to_string());
70        }
71
72        let request = self.0.client.get(url);
73
74        let response = request
75            .send()
76            .await?
77            .error_for_status()?
78            .json::<BaseResponse<Vec<User>>>()
79            .await?
80            .into_result()?;
81
82        Ok(response)
83    }
84
85    pub async fn get_user_by_handle(
86        &self,
87        handle: &str,
88        platform: impl Into<Option<Platform>>,
89    ) -> Result<User, XTrackerError> {
90        let mut url = self.0.base_url.join(&format!("users/{handle}"))?;
91
92        if let Some(platform) = platform.into() {
93            url.set_query(Some(&format!("platform={}", &platform.to_string())));
94        }
95
96        let request = self.0.client.get(url);
97
98        let response = request
99            .send()
100            .await?
101            .error_for_status()?
102            .json::<BaseResponse<User>>()
103            .await?
104            .into_result()?;
105
106        Ok(response)
107    }
108}
109
110impl XTrackerClient {
111    pub async fn get_posts_by_handle(
112        &self,
113        handle: &str,
114        platform: impl Into<Option<Platform>>,
115        start: impl Into<Option<DateTime<Utc>>>,
116        end: impl Into<Option<DateTime<Utc>>>,
117    ) -> Result<Vec<Post>, XTrackerError> {
118        let mut url = self.0.base_url.join(&format!("users/{handle}/posts"))?;
119
120        {
121            let mut query = url.query_pairs_mut();
122
123            if let Some(platform) = platform.into() {
124                query.append_pair("platform", &platform.to_string());
125            }
126
127            if let Some(start) = start.into() {
128                query.append_pair("startDate", &start.to_rfc3339());
129            }
130
131            if let Some(end) = end.into() {
132                query.append_pair("endDate", &end.to_rfc3339());
133            }
134        }
135        let request = self.0.client.get(url);
136
137        let response = request
138            .send()
139            .await?
140            .error_for_status()?
141            .json::<BaseResponse<Vec<Post>>>()
142            .await?
143            .into_result()?;
144
145        Ok(response)
146    }
147}
148
149impl XTrackerClient {
150    pub async fn get_trackings(&self, active_only: bool) -> Result<Vec<Tracking>, XTrackerError> {
151        let mut url = self.0.base_url.join("trackings")?;
152
153        url.set_query(Some(&format!("activeOnly={active_only}")));
154
155        let request = self.0.client.get(url);
156
157        let response = request
158            .send()
159            .await?
160            .error_for_status()?
161            .json::<BaseResponse<Vec<Tracking>>>()
162            .await?
163            .into_result()?;
164
165        Ok(response)
166    }
167
168    pub async fn get_tracking_by_id(
169        &self,
170        tracking_id: &str,
171        include_stats: bool,
172    ) -> Result<Tracking, XTrackerError> {
173        let mut url = self.0.base_url.join(&format!("trackings/{tracking_id}"))?;
174
175        url.set_query(Some(&format!("includeStats={include_stats}")));
176
177        let request = self.0.client.get(url);
178
179        let response = request
180            .send()
181            .await?
182            .error_for_status()?
183            .json::<BaseResponse<Tracking>>()
184            .await?
185            .into_result()?;
186
187        Ok(response)
188    }
189
190    pub async fn get_trackings_by_handle(
191        &self,
192        handle: &str,
193        platform: impl Into<Option<Platform>>,
194        active_only: bool,
195    ) -> Result<Vec<UserTrackings>, XTrackerError> {
196        let mut url = self.0.base_url.join(&format!("users/{handle}/trackings"))?;
197
198        {
199            let mut query = url.query_pairs_mut();
200
201            if let Some(platform) = platform.into() {
202                query.append_pair("platform", &platform.to_string());
203            }
204
205            query.append_pair("includeInactive", &(!active_only).to_string());
206        }
207
208        let request = self.0.client.get(url);
209
210        let response = request
211            .send()
212            .await?
213            .error_for_status()?
214            .json::<BaseResponse<Vec<UserTrackings>>>()
215            .await?
216            .into_result()?;
217
218        Ok(response)
219    }
220}
221
222impl XTrackerClient {
223    pub async fn get_metrics_by_user(
224        &self,
225        user_id: &str,
226        start: impl Into<Option<DateTime<Utc>>>,
227        end: impl Into<Option<DateTime<Utc>>>,
228    ) -> Result<Vec<Metric>, XTrackerError> {
229        let mut url = self.0.base_url.join(&format!("metrics/{user_id}"))?;
230
231        {
232            let mut query = url.query_pairs_mut();
233
234            if let Some(start) = start.into() {
235                query.append_pair("startDate", &start.to_rfc3339());
236            }
237
238            if let Some(end) = end.into() {
239                query.append_pair("endDate", &end.to_rfc3339());
240            }
241        }
242
243        let request = self.0.client.get(url);
244
245        let response = request
246            .send()
247            .await?
248            .error_for_status()?
249            .json::<BaseResponse<Vec<Metric>>>()
250            .await?
251            .into_result()?;
252
253        Ok(response)
254    }
255}