opensession_api_client/
client.rs1use std::time::Duration;
2
3use anyhow::{bail, Result};
4use serde::Serialize;
5
6use opensession_api_types::*;
7
8pub struct ApiClient {
14 client: reqwest::Client,
15 base_url: String,
16 auth_token: Option<String>,
17}
18
19impl ApiClient {
20 pub fn new(base_url: &str, timeout: Duration) -> Result<Self> {
22 let client = reqwest::Client::builder().timeout(timeout).build()?;
23 Ok(Self {
24 client,
25 base_url: base_url.trim_end_matches('/').to_string(),
26 auth_token: None,
27 })
28 }
29
30 pub fn with_client(client: reqwest::Client, base_url: &str) -> Self {
32 Self {
33 client,
34 base_url: base_url.trim_end_matches('/').to_string(),
35 auth_token: None,
36 }
37 }
38
39 pub fn set_auth(&mut self, token: String) {
40 self.auth_token = Some(token);
41 }
42
43 pub fn auth_token(&self) -> Option<&str> {
44 self.auth_token.as_deref()
45 }
46
47 pub fn base_url(&self) -> &str {
48 &self.base_url
49 }
50
51 pub fn reqwest_client(&self) -> &reqwest::Client {
53 &self.client
54 }
55
56 fn url(&self, path: &str) -> String {
57 format!("{}/api{}", self.base_url, path)
58 }
59
60 fn token_or_bail(&self) -> Result<&str> {
61 self.auth_token
62 .as_deref()
63 .ok_or_else(|| anyhow::anyhow!("auth token not set"))
64 }
65
66 pub async fn health(&self) -> Result<HealthResponse> {
69 let resp = self.client.get(self.url("/health")).send().await?;
70 parse_response(resp).await
71 }
72
73 pub async fn login(&self, req: &LoginRequest) -> Result<AuthTokenResponse> {
76 let resp = self
77 .client
78 .post(self.url("/auth/login"))
79 .json(req)
80 .send()
81 .await?;
82 parse_response(resp).await
83 }
84
85 pub async fn register(&self, req: &AuthRegisterRequest) -> Result<AuthTokenResponse> {
86 let resp = self
87 .client
88 .post(self.url("/auth/register"))
89 .json(req)
90 .send()
91 .await?;
92 parse_response(resp).await
93 }
94
95 pub async fn verify(&self) -> Result<VerifyResponse> {
96 let token = self.token_or_bail()?;
97 let resp = self
98 .client
99 .post(self.url("/auth/verify"))
100 .bearer_auth(token)
101 .send()
102 .await?;
103 parse_response(resp).await
104 }
105
106 pub async fn me(&self) -> Result<UserSettingsResponse> {
107 let token = self.token_or_bail()?;
108 let resp = self
109 .client
110 .get(self.url("/auth/me"))
111 .bearer_auth(token)
112 .send()
113 .await?;
114 parse_response(resp).await
115 }
116
117 pub async fn refresh(&self, req: &RefreshRequest) -> Result<AuthTokenResponse> {
118 let resp = self
119 .client
120 .post(self.url("/auth/refresh"))
121 .json(req)
122 .send()
123 .await?;
124 parse_response(resp).await
125 }
126
127 pub async fn logout(&self, req: &LogoutRequest) -> Result<OkResponse> {
128 let token = self.token_or_bail()?;
129 let resp = self
130 .client
131 .post(self.url("/auth/logout"))
132 .bearer_auth(token)
133 .json(req)
134 .send()
135 .await?;
136 parse_response(resp).await
137 }
138
139 pub async fn change_password(&self, req: &ChangePasswordRequest) -> Result<OkResponse> {
140 let token = self.token_or_bail()?;
141 let resp = self
142 .client
143 .post(self.url("/auth/change-password"))
144 .bearer_auth(token)
145 .json(req)
146 .send()
147 .await?;
148 parse_response(resp).await
149 }
150
151 pub async fn regenerate_key(&self) -> Result<RegenerateKeyResponse> {
152 let token = self.token_or_bail()?;
153 let resp = self
154 .client
155 .post(self.url("/auth/regenerate-key"))
156 .bearer_auth(token)
157 .send()
158 .await?;
159 parse_response(resp).await
160 }
161
162 pub async fn upload_session(&self, req: &UploadRequest) -> Result<UploadResponse> {
165 let token = self.token_or_bail()?;
166 let resp = self
167 .client
168 .post(self.url("/sessions"))
169 .bearer_auth(token)
170 .json(req)
171 .send()
172 .await?;
173 parse_response(resp).await
174 }
175
176 pub async fn list_sessions(&self, query: &SessionListQuery) -> Result<SessionListResponse> {
177 let token = self.token_or_bail()?;
178 let mut url = self.url("/sessions");
179
180 let mut params = Vec::new();
182 params.push(format!("page={}", query.page));
183 params.push(format!("per_page={}", query.per_page));
184 if let Some(ref s) = query.search {
185 params.push(format!("search={s}"));
186 }
187 if let Some(ref t) = query.tool {
188 params.push(format!("tool={t}"));
189 }
190 if let Some(ref t) = query.team_id {
191 params.push(format!("team_id={t}"));
192 }
193 if let Some(ref s) = query.sort {
194 params.push(format!("sort={s}"));
195 }
196 if let Some(ref r) = query.time_range {
197 params.push(format!("time_range={r}"));
198 }
199 if !params.is_empty() {
200 url = format!("{}?{}", url, params.join("&"));
201 }
202
203 let resp = self.client.get(&url).bearer_auth(token).send().await?;
204 parse_response(resp).await
205 }
206
207 pub async fn get_session(&self, id: &str) -> Result<SessionDetail> {
208 let token = self.token_or_bail()?;
209 let resp = self
210 .client
211 .get(self.url(&format!("/sessions/{id}")))
212 .bearer_auth(token)
213 .send()
214 .await?;
215 parse_response(resp).await
216 }
217
218 pub async fn get_session_raw(&self, id: &str) -> Result<serde_json::Value> {
219 let token = self.token_or_bail()?;
220 let resp = self
221 .client
222 .get(self.url(&format!("/sessions/{id}/raw")))
223 .bearer_auth(token)
224 .send()
225 .await?;
226 parse_response(resp).await
227 }
228
229 pub async fn list_teams(&self) -> Result<ListTeamsResponse> {
232 let token = self.token_or_bail()?;
233 let resp = self
234 .client
235 .get(self.url("/teams"))
236 .bearer_auth(token)
237 .send()
238 .await?;
239 parse_response(resp).await
240 }
241
242 pub async fn create_team(&self, req: &CreateTeamRequest) -> Result<TeamResponse> {
243 let token = self.token_or_bail()?;
244 let resp = self
245 .client
246 .post(self.url("/teams"))
247 .bearer_auth(token)
248 .json(req)
249 .send()
250 .await?;
251 parse_response(resp).await
252 }
253
254 pub async fn get_team(&self, id: &str) -> Result<TeamDetailResponse> {
255 let token = self.token_or_bail()?;
256 let resp = self
257 .client
258 .get(self.url(&format!("/teams/{id}")))
259 .bearer_auth(token)
260 .send()
261 .await?;
262 parse_response(resp).await
263 }
264
265 pub async fn update_team(&self, id: &str, req: &UpdateTeamRequest) -> Result<TeamResponse> {
266 let token = self.token_or_bail()?;
267 let resp = self
268 .client
269 .put(self.url(&format!("/teams/{id}")))
270 .bearer_auth(token)
271 .json(req)
272 .send()
273 .await?;
274 parse_response(resp).await
275 }
276
277 pub async fn add_member(&self, team_id: &str, req: &AddMemberRequest) -> Result<OkResponse> {
280 let token = self.token_or_bail()?;
281 let resp = self
282 .client
283 .post(self.url(&format!("/teams/{team_id}/members")))
284 .bearer_auth(token)
285 .json(req)
286 .send()
287 .await?;
288 parse_response(resp).await
289 }
290
291 pub async fn list_members(&self, team_id: &str) -> Result<ListMembersResponse> {
292 let token = self.token_or_bail()?;
293 let resp = self
294 .client
295 .get(self.url(&format!("/teams/{team_id}/members")))
296 .bearer_auth(token)
297 .send()
298 .await?;
299 parse_response(resp).await
300 }
301
302 pub async fn remove_member(&self, team_id: &str, user_id: &str) -> Result<OkResponse> {
303 let token = self.token_or_bail()?;
304 let resp = self
305 .client
306 .delete(self.url(&format!("/teams/{team_id}/members/{user_id}")))
307 .bearer_auth(token)
308 .send()
309 .await?;
310 parse_response(resp).await
311 }
312
313 pub async fn sync_pull(
316 &self,
317 team_id: &str,
318 since: Option<&str>,
319 limit: Option<u32>,
320 ) -> Result<SyncPullResponse> {
321 let token = self.token_or_bail()?;
322 let mut url = format!("{}?team_id={team_id}", self.url("/sync/pull"));
323 if let Some(since) = since {
324 url.push_str(&format!("&since={since}"));
325 }
326 if let Some(limit) = limit {
327 url.push_str(&format!("&limit={limit}"));
328 }
329 let resp = self.client.get(&url).bearer_auth(token).send().await?;
330 parse_response(resp).await
331 }
332
333 pub async fn stream_events(
336 &self,
337 session_id: &str,
338 req: &serde_json::Value,
339 ) -> Result<reqwest::Response> {
340 let token = self.token_or_bail()?;
341 let resp = self
342 .client
343 .post(self.url(&format!("/sessions/{session_id}/events")))
344 .bearer_auth(token)
345 .json(req)
346 .send()
347 .await?;
348 Ok(resp)
349 }
350
351 pub async fn config_sync(&self, team_id: &str) -> Result<ConfigSyncResponse> {
354 let token = self.token_or_bail()?;
355 let resp = self
356 .client
357 .get(self.url(&format!("/teams/{team_id}/config")))
358 .bearer_auth(token)
359 .send()
360 .await?;
361 parse_response(resp).await
362 }
363
364 pub async fn get_with_auth(&self, path: &str, token: &str) -> Result<reqwest::Response> {
368 Ok(self
369 .client
370 .get(self.url(path))
371 .bearer_auth(token)
372 .send()
373 .await?)
374 }
375
376 pub async fn post_with_auth(&self, path: &str, token: &str) -> Result<reqwest::Response> {
378 Ok(self
379 .client
380 .post(self.url(path))
381 .bearer_auth(token)
382 .send()
383 .await?)
384 }
385
386 pub async fn post_json_with_auth<T: Serialize>(
388 &self,
389 path: &str,
390 token: &str,
391 body: &T,
392 ) -> Result<reqwest::Response> {
393 Ok(self
394 .client
395 .post(self.url(path))
396 .bearer_auth(token)
397 .json(body)
398 .send()
399 .await?)
400 }
401
402 pub async fn put_json_with_auth<T: Serialize>(
404 &self,
405 path: &str,
406 token: &str,
407 body: &T,
408 ) -> Result<reqwest::Response> {
409 Ok(self
410 .client
411 .put(self.url(path))
412 .bearer_auth(token)
413 .json(body)
414 .send()
415 .await?)
416 }
417
418 pub async fn delete_with_auth(&self, path: &str, token: &str) -> Result<reqwest::Response> {
420 Ok(self
421 .client
422 .delete(self.url(path))
423 .bearer_auth(token)
424 .send()
425 .await?)
426 }
427
428 pub async fn post_json_raw<T: Serialize>(
430 &self,
431 path: &str,
432 body: &T,
433 ) -> Result<reqwest::Response> {
434 Ok(self.client.post(self.url(path)).json(body).send().await?)
435 }
436}
437
438async fn parse_response<T: serde::de::DeserializeOwned>(resp: reqwest::Response) -> Result<T> {
441 let status = resp.status();
442 if !status.is_success() {
443 let body = resp.text().await.unwrap_or_default();
444 bail!("{status}: {body}");
445 }
446 Ok(resp.json().await?)
447}