api.rs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636
  1. use anyhow::{Context, Result};
  2. use reqwest::{blocking::{Client, Response}, header};
  3. use serde_json::json;
  4. use std::sync::{atomic::{AtomicBool, Ordering}, Arc};
  5. use std::time::Duration;
  6. use crate::models::*;
  7. /// API Client for BeepZone backend
  8. #[derive(Clone)]
  9. pub struct ApiClient {
  10. client: Client,
  11. base_url: String,
  12. token: Option<String>,
  13. db_timeout_flag: Arc<AtomicBool>,
  14. }
  15. impl ApiClient {
  16. /// Create a new API client
  17. pub fn new(base_url: String) -> Result<Self> {
  18. let client = Client::builder()
  19. .timeout(Duration::from_secs(30))
  20. .build()
  21. .context("Failed to create HTTP client")?;
  22. Ok(Self {
  23. client,
  24. base_url: base_url.trim_end_matches('/').to_string(),
  25. token: None,
  26. db_timeout_flag: Arc::new(AtomicBool::new(false)),
  27. })
  28. }
  29. fn flag_timeout_signal(&self) {
  30. self.db_timeout_flag.store(true, Ordering::SeqCst);
  31. }
  32. fn observe_response_error(&self, error: &Option<String>) {
  33. if Self::is_database_timeout_error(error) {
  34. self.flag_timeout_signal();
  35. }
  36. }
  37. fn send_request(
  38. &self,
  39. builder: reqwest::blocking::RequestBuilder,
  40. context_msg: &'static str,
  41. ) -> Result<Response> {
  42. builder
  43. .send()
  44. .map_err(|err| {
  45. self.flag_timeout_signal();
  46. err
  47. })
  48. .context(context_msg)
  49. }
  50. /// Returns true if a timeout signal was previously raised (and clears it)
  51. pub fn take_timeout_signal(&self) -> bool {
  52. self.db_timeout_flag.swap(false, Ordering::SeqCst)
  53. }
  54. /// Set the authentication token
  55. pub fn set_token(&mut self, token: String) {
  56. self.token = Some(token);
  57. }
  58. /// Clear the authentication token
  59. #[allow(dead_code)]
  60. pub fn clear_token(&mut self) {
  61. self.token = None;
  62. }
  63. /// Check if server is reachable
  64. pub fn health_check(&self) -> Result<bool> {
  65. let url = format!("{}/health", self.base_url);
  66. let response = self
  67. .send_request(self.client.get(&url), "Failed to perform health check")?;
  68. Ok(response.status().is_success())
  69. }
  70. /// Get health details (tries to parse JSON; returns None if non-JSON)
  71. pub fn health_info(&self) -> Result<Option<serde_json::Value>> {
  72. let url = format!("{}/health", self.base_url);
  73. let response = self
  74. .send_request(self.client.get(&url), "Failed to fetch health info")?;
  75. if !response.status().is_success() {
  76. return Ok(None);
  77. }
  78. // Try to parse as JSON; if it fails, just return None (some servers return plain text)
  79. let text = response.text()?;
  80. match serde_json::from_str::<serde_json::Value>(&text) {
  81. Ok(v) => Ok(Some(v)),
  82. Err(_) => Ok(None),
  83. }
  84. }
  85. /// Check if the error message indicates a database timeout
  86. pub fn is_database_timeout_error(error: &Option<String>) -> bool {
  87. if let Some(err) = error {
  88. err.contains("Database temporarily unavailable")
  89. } else {
  90. false
  91. }
  92. }
  93. // Authentication Methods
  94. /// Login with username and password
  95. pub fn login_password(&self, username: &str, password: &str) -> Result<LoginResponse> {
  96. let url = format!("{}/auth/login", self.base_url);
  97. let body = LoginRequest {
  98. method: "password".to_string(),
  99. username: Some(username.to_string()),
  100. password: Some(password.to_string()),
  101. pin: None,
  102. login_string: None,
  103. };
  104. let response = self.send_request(
  105. self.client.post(&url).json(&body),
  106. "Failed to send login request",
  107. )?;
  108. let result: LoginResponse = response.json().context("Failed to parse login response")?;
  109. Ok(result)
  110. }
  111. /// Login with PIN
  112. #[allow(dead_code)]
  113. pub fn login_pin(&self, username: &str, pin: &str) -> Result<ApiResponse<LoginResponse>> {
  114. let url = format!("{}/auth/login", self.base_url);
  115. let body = LoginRequest {
  116. method: "pin".to_string(),
  117. username: Some(username.to_string()),
  118. password: None,
  119. pin: Some(pin.to_string()),
  120. login_string: None,
  121. };
  122. let response = self.send_request(
  123. self.client.post(&url).json(&body),
  124. "Failed to send PIN login request",
  125. )?;
  126. let result: ApiResponse<LoginResponse> =
  127. response.json().context("Failed to parse login response")?;
  128. self.observe_response_error(&result.error);
  129. Ok(result)
  130. }
  131. /// Login with token/RFID string
  132. #[allow(dead_code)]
  133. pub fn login_token(&self, login_string: &str) -> Result<ApiResponse<LoginResponse>> {
  134. let url = format!("{}/auth/login", self.base_url);
  135. let body = LoginRequest {
  136. method: "token".to_string(),
  137. username: None,
  138. password: None,
  139. pin: None,
  140. login_string: Some(login_string.to_string()),
  141. };
  142. let response = self.send_request(
  143. self.client.post(&url).json(&body),
  144. "Failed to send token login request",
  145. )?;
  146. let result: ApiResponse<LoginResponse> =
  147. response.json().context("Failed to parse login response")?;
  148. self.observe_response_error(&result.error);
  149. Ok(result)
  150. }
  151. /// Logout current session
  152. pub fn logout(&self) -> Result<ApiResponse<()>> {
  153. let url = format!("{}/auth/logout", self.base_url);
  154. let response = self.send_request(
  155. self.make_authorized_request(reqwest::Method::POST, &url)?,
  156. "Failed to send logout request",
  157. )?;
  158. let result: ApiResponse<()> = response.json()?;
  159. self.observe_response_error(&result.error);
  160. Ok(result)
  161. }
  162. /// Check session status
  163. #[allow(dead_code)]
  164. pub fn check_session(&self) -> Result<ApiResponse<SessionStatus>> {
  165. let url = format!("{}/auth/status", self.base_url);
  166. let response = self.send_request(
  167. self.make_authorized_request(reqwest::Method::GET, &url)?,
  168. "Failed to check session status",
  169. )?;
  170. let result: ApiResponse<SessionStatus> = response.json()?;
  171. self.observe_response_error(&result.error);
  172. Ok(result)
  173. }
  174. /// Best-effort session validity check.
  175. /// Returns Ok(true) when the session appears valid, Ok(false) when clearly invalid (401/403 or explicit valid=false).
  176. /// Be tolerant of different backend response shapes and assume valid on ambiguous 2xx responses.
  177. pub fn check_session_valid(&self) -> Result<bool> {
  178. let url = format!("{}/auth/status", self.base_url);
  179. let response = self.send_request(
  180. self.make_authorized_request(reqwest::Method::GET, &url)?,
  181. "Failed to check session status",
  182. )?;
  183. let status = response.status();
  184. let text = response.text()?;
  185. // Explicitly invalid if unauthorized/forbidden
  186. if status.as_u16() == 401 || status.as_u16() == 403 {
  187. return Ok(false);
  188. }
  189. // Parse generic JSON and look for common shapes first
  190. if let Ok(val) = serde_json::from_str::<serde_json::Value>(&text) {
  191. // data.valid
  192. if let Some(valid) = val
  193. .get("data")
  194. .and_then(|d| d.get("valid"))
  195. .and_then(|v| v.as_bool())
  196. {
  197. return Ok(valid);
  198. }
  199. // top-level valid
  200. if let Some(valid) = val.get("valid").and_then(|v| v.as_bool()) {
  201. return Ok(valid);
  202. }
  203. // success=true is generally a good sign
  204. if val.get("success").and_then(|v| v.as_bool()) == Some(true) {
  205. return Ok(true);
  206. }
  207. }
  208. // As a last attempt, try strict ApiResponse<SessionStatus>
  209. if let Ok(parsed) = serde_json::from_str::<ApiResponse<SessionStatus>>(&text) {
  210. if let Some(data) = parsed.data {
  211. return Ok(data.valid);
  212. }
  213. // If no data provided, treat success=true as valid by default
  214. return Ok(parsed.success || status.is_success());
  215. }
  216. // Last resort: if response was 2xx and not explicitly invalid, assume valid
  217. Ok(status.is_success())
  218. }
  219. // Permissions & Preferences
  220. /// Get current user's permissions
  221. #[allow(dead_code)]
  222. pub fn get_permissions(&self) -> Result<ApiResponse<PermissionsResponse>> {
  223. let url = format!("{}/permissions", self.base_url);
  224. let response = self.send_request(
  225. self.make_authorized_request(reqwest::Method::GET, &url)?,
  226. "Failed to get permissions",
  227. )?;
  228. let result: ApiResponse<PermissionsResponse> = response.json()?;
  229. self.observe_response_error(&result.error);
  230. Ok(result)
  231. }
  232. /// Get user preferences
  233. #[allow(dead_code)]
  234. pub fn get_preferences(
  235. &self,
  236. user_id: Option<i32>,
  237. ) -> Result<ApiResponse<PreferencesResponse>> {
  238. let url = format!("{}/preferences", self.base_url);
  239. let body = PreferencesRequest {
  240. action: "get".to_string(),
  241. user_id,
  242. preferences: None,
  243. };
  244. let response = self.send_request(
  245. self.make_authorized_request(reqwest::Method::POST, &url)?
  246. .json(&body),
  247. "Failed to get preferences",
  248. )?;
  249. let result: ApiResponse<PreferencesResponse> = response.json()?;
  250. self.observe_response_error(&result.error);
  251. Ok(result)
  252. }
  253. /// Set user preferences
  254. #[allow(dead_code)]
  255. pub fn set_preferences(
  256. &self,
  257. values: serde_json::Value,
  258. user_id: Option<i32>,
  259. ) -> Result<ApiResponse<PreferencesResponse>> {
  260. let url = format!("{}/preferences", self.base_url);
  261. let body = PreferencesRequest {
  262. action: "set".to_string(),
  263. user_id,
  264. preferences: Some(values),
  265. };
  266. let response = self.send_request(
  267. self.make_authorized_request(reqwest::Method::POST, &url)?
  268. .json(&body),
  269. "Failed to set preferences",
  270. )?;
  271. let result: ApiResponse<PreferencesResponse> = response.json()?;
  272. self.observe_response_error(&result.error);
  273. Ok(result)
  274. }
  275. // Query Methods
  276. /// Execute a generic query
  277. pub fn query(&self, request: &QueryRequest) -> Result<ApiResponse<serde_json::Value>> {
  278. let url = format!("{}/query", self.base_url);
  279. // Log the serialized request for debugging
  280. let body = serde_json::to_value(request)?;
  281. log::debug!("Query request JSON: {}", serde_json::to_string(&body)?);
  282. // Log the request for debugging JOINs
  283. if request.joins.is_some() {
  284. log::debug!(
  285. "Query with JOINs: table={}, columns={:?}, joins={:?}",
  286. request.table,
  287. request.columns.as_ref().map(|c| c.len()),
  288. request.joins.as_ref().map(|j| j.len())
  289. );
  290. }
  291. let response = self.send_request(
  292. self.make_authorized_request(reqwest::Method::POST, &url)?
  293. .json(&body),
  294. "Failed to execute query",
  295. )?;
  296. // Try to get the response text for debugging
  297. let status = response.status();
  298. let response_text = response.text()?;
  299. // Log the raw response for debugging
  300. if !status.is_success() {
  301. log::error!("API error ({}): {}", status, response_text);
  302. } else {
  303. log::debug!(
  304. "API response (first 500 chars): {}",
  305. if response_text.len() > 500 {
  306. &response_text[..500]
  307. } else {
  308. &response_text
  309. }
  310. );
  311. }
  312. // Now try to parse it
  313. let result: ApiResponse<serde_json::Value> = serde_json::from_str(&response_text)
  314. .with_context(|| {
  315. format!(
  316. "Failed to parse API response. Status: {}, Body: {}",
  317. status,
  318. if response_text.len() > 200 {
  319. &response_text[..200]
  320. } else {
  321. &response_text
  322. }
  323. )
  324. })?;
  325. self.observe_response_error(&result.error);
  326. Ok(result)
  327. }
  328. /// Select records from a table
  329. pub fn select(
  330. &self,
  331. table: &str,
  332. columns: Option<Vec<String>>,
  333. where_clause: Option<serde_json::Value>,
  334. order_by: Option<Vec<OrderBy>>,
  335. limit: Option<u32>,
  336. ) -> Result<ApiResponse<Vec<serde_json::Value>>> {
  337. let request = QueryRequest {
  338. action: "select".to_string(),
  339. table: table.to_string(),
  340. columns,
  341. data: None,
  342. r#where: where_clause,
  343. filter: None,
  344. order_by,
  345. limit,
  346. offset: None,
  347. joins: None,
  348. };
  349. let response = self.query(&request)?;
  350. if response.success {
  351. let data = response.data.unwrap_or(json!([]));
  352. let records: Vec<serde_json::Value> = serde_json::from_value(data)?;
  353. Ok(ApiResponse {
  354. success: true,
  355. data: Some(records),
  356. error: None,
  357. message: response.message,
  358. })
  359. } else {
  360. Ok(ApiResponse {
  361. success: false,
  362. data: None,
  363. error: response.error,
  364. message: response.message,
  365. })
  366. }
  367. }
  368. /// Select records from a table with JOINs
  369. pub fn select_with_joins(
  370. &self,
  371. table: &str,
  372. columns: Option<Vec<String>>,
  373. where_clause: Option<serde_json::Value>,
  374. filter: Option<serde_json::Value>,
  375. order_by: Option<Vec<OrderBy>>,
  376. limit: Option<u32>,
  377. joins: Option<Vec<Join>>,
  378. ) -> Result<ApiResponse<Vec<serde_json::Value>>> {
  379. let request = QueryRequest {
  380. action: "select".to_string(),
  381. table: table.to_string(),
  382. columns,
  383. data: None,
  384. r#where: where_clause,
  385. filter,
  386. order_by,
  387. limit,
  388. offset: None,
  389. joins,
  390. };
  391. let response = self.query(&request)?;
  392. if response.success {
  393. let data = response.data.unwrap_or(json!([]));
  394. let records: Vec<serde_json::Value> = serde_json::from_value(data)?;
  395. Ok(ApiResponse {
  396. success: true,
  397. data: Some(records),
  398. error: None,
  399. message: response.message,
  400. })
  401. } else {
  402. Ok(ApiResponse {
  403. success: false,
  404. data: None,
  405. error: response.error,
  406. message: response.message,
  407. })
  408. }
  409. }
  410. /// Insert a record
  411. pub fn insert(&self, table: &str, values: serde_json::Value) -> Result<ApiResponse<i32>> {
  412. let request = QueryRequest {
  413. action: "insert".to_string(),
  414. table: table.to_string(),
  415. columns: None,
  416. data: Some(values),
  417. r#where: None,
  418. filter: None,
  419. order_by: None,
  420. limit: None,
  421. offset: None,
  422. joins: None,
  423. };
  424. let response = self.query(&request)?;
  425. if response.success {
  426. let id: i32 = serde_json::from_value(response.data.unwrap_or(json!(0)))?;
  427. Ok(ApiResponse {
  428. success: true,
  429. data: Some(id),
  430. error: None,
  431. message: response.message,
  432. })
  433. } else {
  434. Ok(ApiResponse {
  435. success: false,
  436. data: None,
  437. error: response.error,
  438. message: response.message,
  439. })
  440. }
  441. }
  442. /// Update records
  443. pub fn update(
  444. &self,
  445. table: &str,
  446. values: serde_json::Value,
  447. where_clause: serde_json::Value,
  448. ) -> Result<ApiResponse<u32>> {
  449. let request = QueryRequest {
  450. action: "update".to_string(),
  451. table: table.to_string(),
  452. columns: None,
  453. data: Some(values),
  454. r#where: Some(where_clause),
  455. filter: None,
  456. order_by: None,
  457. limit: None,
  458. offset: None,
  459. joins: None,
  460. };
  461. let response = self.query(&request)?;
  462. if response.success {
  463. let count: u32 = serde_json::from_value(response.data.unwrap_or(json!(0)))?;
  464. Ok(ApiResponse {
  465. success: true,
  466. data: Some(count),
  467. error: None,
  468. message: response.message,
  469. })
  470. } else {
  471. Ok(ApiResponse {
  472. success: false,
  473. data: None,
  474. error: response.error,
  475. message: response.message,
  476. })
  477. }
  478. }
  479. /// Delete records
  480. pub fn delete(&self, table: &str, where_clause: serde_json::Value) -> Result<ApiResponse<u32>> {
  481. let request = QueryRequest {
  482. action: "delete".to_string(),
  483. table: table.to_string(),
  484. columns: None,
  485. data: None,
  486. r#where: Some(where_clause),
  487. filter: None,
  488. order_by: None,
  489. limit: None,
  490. offset: None,
  491. joins: None,
  492. };
  493. let response = self.query(&request)?;
  494. if response.success {
  495. let count: u32 = serde_json::from_value(response.data.unwrap_or(json!(0)))?;
  496. Ok(ApiResponse {
  497. success: true,
  498. data: Some(count),
  499. error: None,
  500. message: response.message,
  501. })
  502. } else {
  503. Ok(ApiResponse {
  504. success: false,
  505. data: None,
  506. error: response.error,
  507. message: response.message,
  508. })
  509. }
  510. }
  511. /// Cunt records
  512. pub fn count(
  513. &self,
  514. table: &str,
  515. where_clause: Option<serde_json::Value>,
  516. ) -> Result<ApiResponse<i32>> {
  517. let request = QueryRequest {
  518. action: "count".to_string(),
  519. table: table.to_string(),
  520. columns: None,
  521. data: None,
  522. r#where: where_clause,
  523. filter: None,
  524. order_by: None,
  525. limit: None,
  526. offset: None,
  527. joins: None,
  528. };
  529. let response = self.query(&request)?;
  530. if response.success {
  531. let count: i32 = serde_json::from_value(response.data.unwrap_or(json!(0)))?;
  532. Ok(ApiResponse {
  533. success: true,
  534. data: Some(count),
  535. error: None,
  536. message: response.message,
  537. })
  538. } else {
  539. Ok(ApiResponse {
  540. success: false,
  541. data: None,
  542. error: response.error,
  543. message: response.message,
  544. })
  545. }
  546. }
  547. // Helper Methods
  548. /// Create an authorized request with proper headers
  549. fn make_authorized_request(
  550. &self,
  551. method: reqwest::Method,
  552. url: &str,
  553. ) -> Result<reqwest::blocking::RequestBuilder> {
  554. let token = self.token.as_ref().context("No authentication token set")?;
  555. let builder = self
  556. .client
  557. .request(method, url)
  558. .header(header::AUTHORIZATION, format!("Bearer {}", token));
  559. Ok(builder)
  560. }
  561. /// Get the based URL
  562. pub fn base_url(&self) -> &str {
  563. &self.base_url
  564. }
  565. }