wakapi/endpoints/
projects.rs

1//! Projects endpoint.
2//!
3//! Ref: <https://wakatime.com/developers#projects>
4//!
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7
8use crate::{WakapiClient, WakapiError};
9
10use super::commit::RepositoryDetails;
11
12/// Request parameters for the Projects endpoint.
13///
14/// Ref: <https://wakatime.com/developers#projects>
15#[derive(Serialize, Default)]
16pub struct ProjectsParams {
17    /// optional - Filter projects by a search term
18    q: Option<String>,
19    /// optional - Page number of projects, not in documentation
20    page: Option<usize>,
21}
22
23impl ProjectsParams {
24    /// Create a new ProjectsParams with default values (no search term).
25    pub fn new() -> ProjectsParams {
26        ProjectsParams {
27            q: None,
28            page: None,
29        }
30    }
31
32    /// Set the search term parameter for the request.
33    pub fn q(mut self, q: &str) -> ProjectsParams {
34        self.q = Some(q.to_string());
35        self
36    }
37
38    /// Set the page parameter for the request.
39    /// This will return the projects for the given page number.
40    pub fn page(mut self, page: usize) -> ProjectsParams {
41        self.page = Some(page);
42        self
43    }
44}
45
46/// Projects endpoint.
47///
48/// Ref: <https://wakatime.com/developers#projects>
49#[derive(Deserialize, Debug)]
50pub struct Projects {
51    /// list of projects
52    pub data: Vec<ProjectsData>,
53    /// total number of projects
54    pub total: usize,
55    /// total number of pages
56    pub total_pages: usize,
57    /// current page number
58    pub page: usize,
59    /// previous page number
60    pub prev_page: Option<usize>,
61    /// next page number
62    pub next_page: Option<usize>,
63}
64
65#[derive(Deserialize, Debug)]
66pub struct ProjectsData {
67    /// unique project id
68    pub id: String,
69    /// project name
70    pub name: String,
71    /// associated repository if connected
72    pub repository: Option<RepositoryDetails>,
73    /// associated project badge if enabled
74    pub badge: Option<ProjectBadge>,
75    /// custom project color as hex string, or null if using default color
76    pub color: Option<String>,
77    /// clients associated with this project
78    pub clients: Vec<ProjectClients>,
79    /// whether this project has a shareable url defined
80    pub has_public_url: bool,
81    /// time when project last received code stats as human readable string
82    pub human_readable_last_heartbeat_at: String,
83    /// time when project last received code stats in ISO 8601 format
84    pub last_heartbeat_at: DateTime<Utc>,
85    /// time when project first received code stats as human readable string; currently only set for users who signed up after 2024-02-05T00:00:00Z UTC
86    pub first_heartbeat_at: Option<String>,
87    /// url of this project relative to wakatime.com
88    pub url: String,
89    /// project name url entity encoded
90    pub urlencoded_name: String,
91    /// time when project was created in ISO 8601 format
92    pub created_at: DateTime<Utc>,
93}
94
95#[derive(Deserialize, Debug)]
96pub struct ProjectBadge {
97    /// badge color
98    pub color: String,
99    /// badge unique id
100    pub id: String,
101    /// badge left text
102    pub left_text: String,
103    /// badge link
104    pub link: String,
105    /// badge project id
106    pub project_id: String,
107    /// badge snippets
108    pub snippets: Vec<BadgeSnippet>,
109    /// badge title
110    pub title: String,
111    /// badge url
112    pub url: String,
113}
114
115#[derive(Deserialize, Debug)]
116pub struct BadgeSnippet {
117    /// snippet content
118    pub content: String,
119    /// snippet name
120    pub name: String,
121}
122
123#[derive(Deserialize, Debug)]
124pub struct ProjectClients {
125    /// unique client id
126    pub id: String,
127    /// client name
128    pub name: String,
129    /// client rate
130    pub rate: f64,
131    /// client timeout
132    pub timeout: usize,
133}
134
135impl Projects {
136    #[cfg(feature = "blocking")]
137    /// Fetch the projects for the current user.
138    pub fn fetch(client: &WakapiClient, params: ProjectsParams) -> Result<Self, WakapiError> {
139        let url = client.build_url(
140            "/api/v1/users/current/projects",
141            Some(serde_url_params::to_string(&params)?),
142        );
143        // Debug, print url and response body
144        println!(
145            "url: {}\nbody: {}",
146            url,
147            reqwest::blocking::Client::new()
148                .get(&url)
149                .header("Authorization", client.get_auth_header())
150                .send()?
151                .text()?
152        );
153
154        let response = reqwest::blocking::Client::new()
155            .get(&url)
156            .header("Authorization", client.get_auth_header())
157            .send()?;
158        if response.status().is_success() {
159            let body = response.json::<Projects>()?;
160            Ok(body)
161        } else {
162            let error = response.json::<crate::error::ErrorMessage>()?;
163            Err(WakapiError::ResponseError(error))
164        }
165    }
166
167    #[cfg(not(feature = "blocking"))]
168    /// Fetch the projects for the current user.
169    pub async fn fetch(client: &WakapiClient, params: ProjectsParams) -> Result<Self, WakapiError> {
170        let url = client.build_url(
171            "/api/v1/users/current/projects",
172            Some(serde_url_params::to_string(&params)?),
173        );
174        // Debug, print url and response body
175        println!(
176            "url: {}\nbody: {}",
177            url,
178            reqwest::Client::new()
179                .get(&url)
180                .header("Authorization", client.get_auth_header())
181                .send()
182                .await?
183                .text()
184                .await?
185        );
186
187        let response = reqwest::Client::new()
188            .get(&url)
189            .header("Authorization", client.get_auth_header())
190            .send()
191            .await?;
192        if response.status().is_success() {
193            let body = response.json::<Projects>().await?;
194            Ok(body)
195        } else {
196            let error = response.json::<crate::error::ErrorMessage>().await?;
197            Err(WakapiError::ResponseError(error))
198        }
199    }
200}