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 delete_session(&self, id: &str) -> Result<OkResponse> {
219 let token = self.token_or_bail()?;
220 let resp = self
221 .client
222 .delete(self.url(&format!("/sessions/{id}")))
223 .bearer_auth(token)
224 .send()
225 .await?;
226 parse_response(resp).await
227 }
228
229 pub async fn get_session_raw(&self, id: &str) -> Result<serde_json::Value> {
230 let token = self.token_or_bail()?;
231 let resp = self
232 .client
233 .get(self.url(&format!("/sessions/{id}/raw")))
234 .bearer_auth(token)
235 .send()
236 .await?;
237 parse_response(resp).await
238 }
239
240 pub async fn list_teams(&self) -> Result<ListTeamsResponse> {
243 let token = self.token_or_bail()?;
244 let resp = self
245 .client
246 .get(self.url("/teams"))
247 .bearer_auth(token)
248 .send()
249 .await?;
250 parse_response(resp).await
251 }
252
253 pub async fn create_team(&self, req: &CreateTeamRequest) -> Result<TeamResponse> {
254 let token = self.token_or_bail()?;
255 let resp = self
256 .client
257 .post(self.url("/teams"))
258 .bearer_auth(token)
259 .json(req)
260 .send()
261 .await?;
262 parse_response(resp).await
263 }
264
265 pub async fn get_team(&self, id: &str) -> Result<TeamDetailResponse> {
266 let token = self.token_or_bail()?;
267 let resp = self
268 .client
269 .get(self.url(&format!("/teams/{id}")))
270 .bearer_auth(token)
271 .send()
272 .await?;
273 parse_response(resp).await
274 }
275
276 pub async fn update_team(&self, id: &str, req: &UpdateTeamRequest) -> Result<TeamResponse> {
277 let token = self.token_or_bail()?;
278 let resp = self
279 .client
280 .put(self.url(&format!("/teams/{id}")))
281 .bearer_auth(token)
282 .json(req)
283 .send()
284 .await?;
285 parse_response(resp).await
286 }
287
288 pub async fn add_member(&self, team_id: &str, req: &AddMemberRequest) -> Result<OkResponse> {
291 let token = self.token_or_bail()?;
292 let resp = self
293 .client
294 .post(self.url(&format!("/teams/{team_id}/members")))
295 .bearer_auth(token)
296 .json(req)
297 .send()
298 .await?;
299 parse_response(resp).await
300 }
301
302 pub async fn list_members(&self, team_id: &str) -> Result<ListMembersResponse> {
303 let token = self.token_or_bail()?;
304 let resp = self
305 .client
306 .get(self.url(&format!("/teams/{team_id}/members")))
307 .bearer_auth(token)
308 .send()
309 .await?;
310 parse_response(resp).await
311 }
312
313 pub async fn remove_member(&self, team_id: &str, user_id: &str) -> Result<OkResponse> {
314 let token = self.token_or_bail()?;
315 let resp = self
316 .client
317 .delete(self.url(&format!("/teams/{team_id}/members/{user_id}")))
318 .bearer_auth(token)
319 .send()
320 .await?;
321 parse_response(resp).await
322 }
323
324 pub async fn list_invitations(&self) -> Result<ListInvitationsResponse> {
327 let token = self.token_or_bail()?;
328 let resp = self
329 .client
330 .get(self.url("/invitations"))
331 .bearer_auth(token)
332 .send()
333 .await?;
334 parse_response(resp).await
335 }
336
337 pub async fn accept_invitation(&self, id: &str) -> Result<AcceptInvitationResponse> {
338 let token = self.token_or_bail()?;
339 let resp = self
340 .client
341 .post(self.url(&format!("/invitations/{id}/accept")))
342 .bearer_auth(token)
343 .send()
344 .await?;
345 parse_response(resp).await
346 }
347
348 pub async fn decline_invitation(&self, id: &str) -> Result<OkResponse> {
349 let token = self.token_or_bail()?;
350 let resp = self
351 .client
352 .post(self.url(&format!("/invitations/{id}/decline")))
353 .bearer_auth(token)
354 .send()
355 .await?;
356 parse_response(resp).await
357 }
358
359 pub async fn invite_member(&self, team_id: &str, req: &InviteRequest) -> Result<OkResponse> {
360 let token = self.token_or_bail()?;
361 let resp = self
362 .client
363 .post(self.url(&format!("/teams/{team_id}/invite")))
364 .bearer_auth(token)
365 .json(req)
366 .send()
367 .await?;
368 parse_response(resp).await
369 }
370
371 pub async fn sync_pull(
374 &self,
375 team_id: &str,
376 since: Option<&str>,
377 limit: Option<u32>,
378 ) -> Result<SyncPullResponse> {
379 let token = self.token_or_bail()?;
380 let mut url = format!("{}?team_id={team_id}", self.url("/sync/pull"));
381 if let Some(since) = since {
382 url.push_str(&format!("&since={since}"));
383 }
384 if let Some(limit) = limit {
385 url.push_str(&format!("&limit={limit}"));
386 }
387 let resp = self.client.get(&url).bearer_auth(token).send().await?;
388 parse_response(resp).await
389 }
390
391 pub async fn config_sync(&self, team_id: &str) -> Result<ConfigSyncResponse> {
394 let token = self.token_or_bail()?;
395 let resp = self
396 .client
397 .get(self.url(&format!("/teams/{team_id}/config")))
398 .bearer_auth(token)
399 .send()
400 .await?;
401 parse_response(resp).await
402 }
403
404 pub async fn get_with_auth(&self, path: &str, token: &str) -> Result<reqwest::Response> {
408 Ok(self
409 .client
410 .get(self.url(path))
411 .bearer_auth(token)
412 .send()
413 .await?)
414 }
415
416 pub async fn post_with_auth(&self, path: &str, token: &str) -> Result<reqwest::Response> {
418 Ok(self
419 .client
420 .post(self.url(path))
421 .bearer_auth(token)
422 .send()
423 .await?)
424 }
425
426 pub async fn post_json_with_auth<T: Serialize>(
428 &self,
429 path: &str,
430 token: &str,
431 body: &T,
432 ) -> Result<reqwest::Response> {
433 Ok(self
434 .client
435 .post(self.url(path))
436 .bearer_auth(token)
437 .json(body)
438 .send()
439 .await?)
440 }
441
442 pub async fn put_json_with_auth<T: Serialize>(
444 &self,
445 path: &str,
446 token: &str,
447 body: &T,
448 ) -> Result<reqwest::Response> {
449 Ok(self
450 .client
451 .put(self.url(path))
452 .bearer_auth(token)
453 .json(body)
454 .send()
455 .await?)
456 }
457
458 pub async fn delete_with_auth(&self, path: &str, token: &str) -> Result<reqwest::Response> {
460 Ok(self
461 .client
462 .delete(self.url(path))
463 .bearer_auth(token)
464 .send()
465 .await?)
466 }
467
468 pub async fn post_json_raw<T: Serialize>(
470 &self,
471 path: &str,
472 body: &T,
473 ) -> Result<reqwest::Response> {
474 Ok(self.client.post(self.url(path)).json(body).send().await?)
475 }
476}
477
478async fn parse_response<T: serde::de::DeserializeOwned>(resp: reqwest::Response) -> Result<T> {
481 let status = resp.status();
482 if !status.is_success() {
483 let body = resp.text().await.unwrap_or_default();
484 bail!("{status}: {body}");
485 }
486 Ok(resp.json().await?)
487}