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 list_invitations(&self) -> Result<ListInvitationsResponse> {
316 let token = self.token_or_bail()?;
317 let resp = self
318 .client
319 .get(self.url("/invitations"))
320 .bearer_auth(token)
321 .send()
322 .await?;
323 parse_response(resp).await
324 }
325
326 pub async fn accept_invitation(&self, id: &str) -> Result<AcceptInvitationResponse> {
327 let token = self.token_or_bail()?;
328 let resp = self
329 .client
330 .post(self.url(&format!("/invitations/{id}/accept")))
331 .bearer_auth(token)
332 .send()
333 .await?;
334 parse_response(resp).await
335 }
336
337 pub async fn decline_invitation(&self, id: &str) -> Result<OkResponse> {
338 let token = self.token_or_bail()?;
339 let resp = self
340 .client
341 .post(self.url(&format!("/invitations/{id}/decline")))
342 .bearer_auth(token)
343 .send()
344 .await?;
345 parse_response(resp).await
346 }
347
348 pub async fn invite_member(&self, team_id: &str, req: &InviteRequest) -> Result<OkResponse> {
349 let token = self.token_or_bail()?;
350 let resp = self
351 .client
352 .post(self.url(&format!("/teams/{team_id}/invite")))
353 .bearer_auth(token)
354 .json(req)
355 .send()
356 .await?;
357 parse_response(resp).await
358 }
359
360 pub async fn sync_pull(
363 &self,
364 team_id: &str,
365 since: Option<&str>,
366 limit: Option<u32>,
367 ) -> Result<SyncPullResponse> {
368 let token = self.token_or_bail()?;
369 let mut url = format!("{}?team_id={team_id}", self.url("/sync/pull"));
370 if let Some(since) = since {
371 url.push_str(&format!("&since={since}"));
372 }
373 if let Some(limit) = limit {
374 url.push_str(&format!("&limit={limit}"));
375 }
376 let resp = self.client.get(&url).bearer_auth(token).send().await?;
377 parse_response(resp).await
378 }
379
380 pub async fn stream_events(
383 &self,
384 session_id: &str,
385 req: &serde_json::Value,
386 ) -> Result<reqwest::Response> {
387 let token = self.token_or_bail()?;
388 let resp = self
389 .client
390 .post(self.url(&format!("/sessions/{session_id}/events")))
391 .bearer_auth(token)
392 .json(req)
393 .send()
394 .await?;
395 Ok(resp)
396 }
397
398 pub async fn config_sync(&self, team_id: &str) -> Result<ConfigSyncResponse> {
401 let token = self.token_or_bail()?;
402 let resp = self
403 .client
404 .get(self.url(&format!("/teams/{team_id}/config")))
405 .bearer_auth(token)
406 .send()
407 .await?;
408 parse_response(resp).await
409 }
410
411 pub async fn get_with_auth(&self, path: &str, token: &str) -> Result<reqwest::Response> {
415 Ok(self
416 .client
417 .get(self.url(path))
418 .bearer_auth(token)
419 .send()
420 .await?)
421 }
422
423 pub async fn post_with_auth(&self, path: &str, token: &str) -> Result<reqwest::Response> {
425 Ok(self
426 .client
427 .post(self.url(path))
428 .bearer_auth(token)
429 .send()
430 .await?)
431 }
432
433 pub async fn post_json_with_auth<T: Serialize>(
435 &self,
436 path: &str,
437 token: &str,
438 body: &T,
439 ) -> Result<reqwest::Response> {
440 Ok(self
441 .client
442 .post(self.url(path))
443 .bearer_auth(token)
444 .json(body)
445 .send()
446 .await?)
447 }
448
449 pub async fn put_json_with_auth<T: Serialize>(
451 &self,
452 path: &str,
453 token: &str,
454 body: &T,
455 ) -> Result<reqwest::Response> {
456 Ok(self
457 .client
458 .put(self.url(path))
459 .bearer_auth(token)
460 .json(body)
461 .send()
462 .await?)
463 }
464
465 pub async fn delete_with_auth(&self, path: &str, token: &str) -> Result<reqwest::Response> {
467 Ok(self
468 .client
469 .delete(self.url(path))
470 .bearer_auth(token)
471 .send()
472 .await?)
473 }
474
475 pub async fn post_json_raw<T: Serialize>(
477 &self,
478 path: &str,
479 body: &T,
480 ) -> Result<reqwest::Response> {
481 Ok(self.client.post(self.url(path)).json(body).send().await?)
482 }
483}
484
485async fn parse_response<T: serde::de::DeserializeOwned>(resp: reqwest::Response) -> Result<T> {
488 let status = resp.status();
489 if !status.is_success() {
490 let body = resp.text().await.unwrap_or_default();
491 bail!("{status}: {body}");
492 }
493 Ok(resp.json().await?)
494}