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 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 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 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 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 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 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 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 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}