Skip to main content

oauth_db_cli/
client.rs

1use crate::error::{CliError, Result};
2use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION, CONTENT_TYPE};
3use serde::{Deserialize, Serialize};
4use std::time::Duration;
5
6#[derive(Debug, Clone)]
7pub struct ApiClient {
8    base_url: String,
9    token: Option<String>,
10    client: reqwest::Client,
11}
12
13#[derive(Debug, Serialize, Deserialize)]
14pub struct LoginRequest {
15    pub grant_type: String,
16    pub username: String,
17    pub password: String,
18    pub client_id: String,
19    pub client_secret: String,
20}
21
22#[derive(Debug, Serialize, Deserialize)]
23pub struct LoginResponse {
24    pub access_token: String,
25    pub token_type: String,
26    pub expires_in: u64,
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub refresh_token: Option<String>,
29}
30
31#[derive(Debug, Serialize, Deserialize)]
32pub struct UserInfo {
33    pub id: String,
34    pub username: String,
35    pub email: String,
36    pub role: String,
37    pub status: String,
38    pub tenant_id: String,
39    pub created_at: String,
40    pub updated_at: String,
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub name: Option<String>,
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub image: Option<String>,
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub email_verified: Option<bool>,
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub external_user_id: Option<String>,
49}
50
51impl ApiClient {
52    pub fn new(base_url: String, token: Option<String>) -> Result<Self> {
53        let client = reqwest::Client::builder()
54            .timeout(Duration::from_secs(30))
55            .build()
56            .map_err(|e| CliError::NetworkError(format!("Failed to create HTTP client: {}", e)))?;
57
58        Ok(Self {
59            base_url,
60            token,
61            client,
62        })
63    }
64
65    pub fn set_token(&mut self, token: String) {
66        self.token = Some(token);
67    }
68
69    fn build_headers(&self) -> Result<HeaderMap> {
70        let mut headers = HeaderMap::new();
71        headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
72
73        if let Some(token) = &self.token {
74            let auth_value = format!("Bearer {}", token);
75            headers.insert(
76                AUTHORIZATION,
77                HeaderValue::from_str(&auth_value)
78                    .map_err(|e| CliError::AuthError(format!("Invalid token: {}", e)))?,
79            );
80        }
81
82        Ok(headers)
83    }
84
85    /// Login to the platform
86    pub async fn login(&self, username: &str, password: &str) -> Result<LoginResponse> {
87        let url = format!("{}/api/v1/oauth2/token", self.base_url);
88
89        let body = serde_json::json!({
90            "grant_type": "password",
91            "username": username,
92            "password": password,
93            "client_id": "oauth-db-cli",
94            "client_secret": "cli-secret",
95        });
96
97        let response = self
98            .client
99            .post(&url)
100            .json(&body)
101            .send()
102            .await
103            .map_err(|e| CliError::NetworkError(format!("Login request failed: {}", e)))?;
104
105        if !response.status().is_success() {
106            let status = response.status();
107            let error_text = response
108                .text()
109                .await
110                .unwrap_or_else(|_| "Unknown error".to_string());
111            return Err(CliError::AuthError(format!(
112                "Login failed ({}): {}",
113                status, error_text
114            )));
115        }
116
117        let login_response: LoginResponse = response
118            .json()
119            .await
120            .map_err(|e| CliError::SerializationError(format!("Failed to parse response: {}", e)))?;
121
122        Ok(login_response)
123    }
124
125    /// Get current user information
126    pub async fn get_me(&self) -> Result<UserInfo> {
127        let url = format!("{}/api/v1/users/me", self.base_url);
128        let headers = self.build_headers()?;
129
130        let response = self
131            .client
132            .get(&url)
133            .headers(headers)
134            .send()
135            .await
136            .map_err(|e| CliError::NetworkError(format!("Request failed: {}", e)))?;
137
138        if response.status() == 401 {
139            return Err(CliError::NotLoggedIn);
140        }
141
142        if !response.status().is_success() {
143            let status = response.status();
144            let error_text = response
145                .text()
146                .await
147                .unwrap_or_else(|_| "Unknown error".to_string());
148            return Err(CliError::ApiError(format!(
149                "Request failed ({}): {}",
150                status, error_text
151            )));
152        }
153
154        let user_info: UserInfo = response
155            .json()
156            .await
157            .map_err(|e| CliError::SerializationError(format!("Failed to parse response: {}", e)))?;
158
159        Ok(user_info)
160    }
161
162    /// Verify if the token is still valid
163    pub async fn verify_token(&self) -> Result<bool> {
164        match self.get_me().await {
165            Ok(_) => Ok(true),
166            Err(CliError::NotLoggedIn) => Ok(false),
167            Err(e) => Err(e),
168        }
169    }
170
171    /// Generic GET request
172    pub async fn get<T: serde::de::DeserializeOwned>(
173        &self,
174        path: &str,
175        query: &[(&str, String)],
176    ) -> Result<T> {
177        let url = format!("{}{}", self.base_url, path);
178        let headers = self.build_headers()?;
179
180        let mut request = self.client.get(&url).headers(headers);
181
182        if !query.is_empty() {
183            request = request.query(query);
184        }
185
186        let response = request
187            .send()
188            .await
189            .map_err(|e| CliError::NetworkError(format!("Request failed: {}", e)))?;
190
191        self.handle_response(response).await
192    }
193
194    /// Generic POST request
195    pub async fn post<T: serde::de::DeserializeOwned>(
196        &self,
197        path: &str,
198        body: &serde_json::Value,
199    ) -> Result<T> {
200        let url = format!("{}{}", self.base_url, path);
201        let headers = self.build_headers()?;
202
203        let response = self
204            .client
205            .post(&url)
206            .headers(headers)
207            .json(body)
208            .send()
209            .await
210            .map_err(|e| CliError::NetworkError(format!("Request failed: {}", e)))?;
211
212        self.handle_response(response).await
213    }
214
215    /// Generic PATCH request
216    pub async fn patch<T: serde::de::DeserializeOwned>(
217        &self,
218        path: &str,
219        body: &serde_json::Value,
220    ) -> Result<T> {
221        let url = format!("{}{}", self.base_url, path);
222        let headers = self.build_headers()?;
223
224        let response = self
225            .client
226            .patch(&url)
227            .headers(headers)
228            .json(body)
229            .send()
230            .await
231            .map_err(|e| CliError::NetworkError(format!("Request failed: {}", e)))?;
232
233        self.handle_response(response).await
234    }
235
236    /// Generic DELETE request
237    pub async fn delete<T: serde::de::DeserializeOwned>(&self, path: &str) -> Result<T> {
238        let url = format!("{}{}", self.base_url, path);
239        let headers = self.build_headers()?;
240
241        let response = self
242            .client
243            .delete(&url)
244            .headers(headers)
245            .send()
246            .await
247            .map_err(|e| CliError::NetworkError(format!("Request failed: {}", e)))?;
248
249        self.handle_response(response).await
250    }
251
252    /// Handle HTTP response and parse JSON
253    async fn handle_response<T: serde::de::DeserializeOwned>(
254        &self,
255        response: reqwest::Response,
256    ) -> Result<T> {
257        let status = response.status();
258
259        if status == 401 {
260            return Err(CliError::NotLoggedIn);
261        }
262
263        if !status.is_success() {
264            let error_text = response
265                .text()
266                .await
267                .unwrap_or_else(|_| "Unknown error".to_string());
268
269            return Err(match status.as_u16() {
270                403 => CliError::PermissionDenied(error_text),
271                404 => CliError::NotFound(error_text),
272                409 => CliError::InvalidInput(format!("Conflict: {}", error_text)),
273                422 => CliError::InvalidInput(error_text),
274                429 => CliError::ApiError(format!("Rate limit exceeded: {}", error_text)),
275                _ => CliError::ApiError(format!("Request failed ({}): {}", status, error_text)),
276            });
277        }
278
279        response
280            .json()
281            .await
282            .map_err(|e| CliError::SerializationError(format!("Failed to parse response: {}", e)))
283    }
284}