From f447cabed18f35395fd1a7a31af97f49899a6426 Mon Sep 17 00:00:00 2001 From: Joshua Coles Date: Mon, 15 Jul 2024 16:38:05 +0100 Subject: [PATCH] Add Projects --- Cargo.lock | 12 +++++++++ Cargo.toml | 1 + src/main.rs | 2 +- src/toggl/mod.rs | 67 +++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 80 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f870fbc..479a35d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1268,6 +1268,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_json_path_to_error" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7f2c2432eb04d880635fbf5d45cd5e619aeca7d4aff62cc1699671512db7d53" +dependencies = [ + "serde", + "serde_json", + "serde_path_to_error", +] + [[package]] name = "serde_path_to_error" version = "0.1.16" @@ -1450,6 +1461,7 @@ dependencies = [ "reqwest-retry", "serde", "serde_json", + "serde_json_path_to_error", "thiserror", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index 3d30155..5e1f31b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,4 @@ tokio = { version = "1.38.0", features = ["full"] } base64 = "0.22.1" serde = { version = "1.0.204", features = ["derive"] } serde_json = "1.0.120" +serde_json_path_to_error = "0.1.4" diff --git a/src/main.rs b/src/main.rs index 95c4931..72bc097 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,5 +10,5 @@ async fn main() { sensitive::WORKSPACE_ID, ); - dbg!(api.get_current_time_entry().await); + dbg!(api.get_projects().await); } diff --git a/src/toggl/mod.rs b/src/toggl/mod.rs index 89a3b9e..9a1ce69 100644 --- a/src/toggl/mod.rs +++ b/src/toggl/mod.rs @@ -9,6 +9,8 @@ use governor::clock::DefaultClock; use reqwest::header::{HeaderMap, HeaderValue}; use base64::engine::general_purpose::STANDARD; use base64::Engine; +use reqwest::Response; +use serde::de::DeserializeOwned; struct ReqwestRateLimiter { rate_limiter: governor::RateLimiter, @@ -66,6 +68,7 @@ impl TogglApi { let mut value = HeaderValue::from_str(&format!("Basic {}", toggl_auth)).unwrap(); value.set_sensitive(true); headers.insert("Authorization", value); + headers.insert("Content-Type", HeaderValue::from_static("application/json")); headers } @@ -81,10 +84,35 @@ impl TogglApi { .await? .json().await?) } + + pub async fn get_projects(&self) -> Result, TogglError> { + let url = format!( + "{base_url}/workspaces/{workspace_id}/projects", + base_url = BASE_URL, + workspace_id = self.workspace_id + ); + + Self::parse(self.client.get(&url) + .headers(self.headers.clone()) + .send().await?).await + } + + async fn parse(response: Response) -> Result { + let data = response.text().await?; + let result = serde_json_path_to_error::from_str(&data); + + let result = result + .map_err(|error| TogglError::JsonWithDataError { + data, + error, + })?; + + Ok(result) + } } mod types { - use chrono::{DateTime, Utc}; + use chrono::{DateTime, NaiveDate, Utc}; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, Clone)] @@ -141,6 +169,37 @@ mod types { } } } + + #[derive(Serialize, Deserialize, Debug, Clone)] + pub struct Project { + id: i64, + workspace_id: i64, + client_id: Option, + + name: String, + color: String, + status: Status, + active: bool, + + at: DateTime, + start_date: NaiveDate, + created_at: DateTime, + server_deleted_at: Option>, + + actual_hours: Option, + actual_seconds: Option, + can_track_time: bool, + } + + #[derive(Serialize, Deserialize, Debug, Clone)] + #[serde(rename_all = "lowercase")] + pub enum Status { + Upcoming, + Active, + Archived, + Ended, + Deleted, + } } #[derive(Debug, thiserror::Error)] @@ -150,6 +209,12 @@ pub enum TogglError { #[error("Json error: {0}")] JsonError(#[from] serde_json::Error), + + #[error("Failed to parse JSON data: {data}, error: {error}")] + JsonWithDataError { + data: String, + error: serde_json_path_to_error::Error, + }, } impl From for TogglError {