1use chrono::NaiveDate;
4use reqwest::header;
5use serde::{Deserialize, Serialize};
6use serde_json::Number;
7
8static BASE_API_URL: &str = "https://api.track.toggl.com/api/v9";
9
10pub struct Client {
12 c: reqwest::blocking::Client,
13 token: String,
14}
15
16impl Client {
17 pub fn new(token: String) -> Result<Self, reqwest::Error> {
19 let mut headers = header::HeaderMap::new();
20
21 headers.insert(
24 header::CONTENT_TYPE,
25 header::HeaderValue::from_static("application/json"),
26 );
27
28 Ok(Client {
29 c: reqwest::blocking::Client::builder()
30 .default_headers(headers)
31 .build()?,
32 token,
33 })
34 }
35
36 pub fn get_time_entries(
37 &self,
38 start_end_dates: Option<(NaiveDate, NaiveDate)>,
39 ) -> Result<Vec<TimeEntry>, reqwest::Error> {
40 let url = match start_end_dates {
41 Some((start_date, end_date)) => {
42 format!(
43 "{BASE_API_URL}/me/time_entries?start_date={start_date}&end_date={end_date}"
44 )
45 }
46 None => format!("{BASE_API_URL}/me/time_entries"),
47 };
48
49 self.c
50 .get(url)
51 .basic_auth(&self.token, Some("api_token"))
52 .send()?
53 .error_for_status()?
54 .json::<Vec<TimeEntry>>()
55 }
56
57 pub fn get_current_entry(&self) -> Result<Option<TimeEntry>, reqwest::Error> {
58 self.c
59 .get(format!("{BASE_API_URL}/me/time_entries/current"))
60 .basic_auth(&self.token, Some("api_token"))
61 .send()?
62 .error_for_status()?
63 .json()
64 }
65
66 pub fn create_time_entry(&self, entry: NewTimeEntry) -> Result<TimeEntry, reqwest::Error> {
67 let url = format!(
68 "{BASE_API_URL}/workspaces/{}/time_entries",
69 entry.workspace_id
70 );
71
72 self.c
73 .post(url)
74 .json(&entry)
75 .basic_auth(&self.token, Some("api_token"))
76 .send()?
77 .error_for_status()?
78 .json()
79 }
80
81 pub fn stop_time_entry(
82 &self,
83 workspace_id: &Number,
84 time_entry_id: &Number,
85 ) -> Result<TimeEntry, reqwest::Error> {
86 let url =
87 format!("{BASE_API_URL}/workspaces/{workspace_id}/time_entries/{time_entry_id}/stop");
88
89 self.c
90 .patch(url)
91 .basic_auth(&self.token, Some("api_token"))
92 .send()?
93 .error_for_status()?
94 .json()
95 }
96
97 pub fn get_projects(&self, workspace_id: &Number) -> Result<Vec<Project>, reqwest::Error> {
98 self.c
99 .get(format!("{BASE_API_URL}/workspaces/{workspace_id}/projects"))
100 .basic_auth(&self.token, Some("api_token"))
101 .send()?
102 .error_for_status()?
103 .json()
104 }
105
106 pub fn get_workspaces(&self) -> Result<Vec<Workspace>, reqwest::Error> {
107 self.c
108 .get(format!("{BASE_API_URL}/workspaces"))
109 .basic_auth(&self.token, Some("api_token"))
110 .send()?
111 .error_for_status()?
112 .json()
113 }
114}
115
116#[derive(Deserialize, Debug)]
117pub struct TimeEntry {
118 pub description: Option<String>,
119 pub duration: Number,
120 pub id: Number,
121 pub project_id: Option<Number>,
122 pub start: Option<String>,
123 pub stop: Option<String>,
124 pub task_id: Option<Number>,
125 pub workspace_id: Number,
126}
127
128#[derive(Serialize, Debug)]
129pub struct NewTimeEntry {
130 pub created_with: String,
131 pub description: Option<String>,
132 pub duration: Number,
133 pub project_id: Option<Number>,
134 pub start: String,
135 pub stop: Option<String>,
136 pub task_id: Option<Number>,
137 pub workspace_id: Number,
138}
139
140#[derive(Deserialize, Debug)]
141pub struct Project {
142 pub active: bool,
143 pub client_id: Option<Number>,
144 pub id: Number,
145 pub name: String,
146 pub workspace_id: Number,
147}
148
149#[derive(Deserialize, Debug)]
150pub struct Workspace {
151 pub id: Number,
152 pub name: String,
153}