1use serde::{Deserialize, Serialize};
2
3use crate::{TickTick, TickTickError};
4
5use super::{builders::ProjectBuilder, tasks::Task};
6
7#[derive(Serialize, Deserialize, Default, Debug, Clone)]
9#[serde(transparent)]
10pub struct ProjectID(pub String);
11
12impl ProjectID {
13 pub fn is_empty(&self) -> bool {
14 self.0.is_empty()
15 }
16}
17
18#[derive(Serialize, Deserialize, Default, Debug, Clone)]
20#[serde(transparent)]
21pub struct GroupID(pub String);
22
23impl GroupID {
24 pub fn is_empty(&self) -> bool {
25 self.0.is_empty()
26 }
27}
28
29#[derive(Serialize, Deserialize, Default, Debug)]
32#[serde(rename_all = "camelCase", default)]
33pub struct Project {
34 #[serde(skip)]
35 pub(crate) http_client: reqwest::Client,
36 pub(crate) id: ProjectID,
37 pub name: String,
38 pub color: String,
39 pub sort_order: i64,
40 #[serde(default)]
41 pub closed: bool,
42 pub group_id: GroupID,
43 pub view_mode: ProjectViewMode,
44 pub permission: ProjectUserPermissions,
45 pub kind: ProjectKind,
46}
47
48impl Project {
49 pub fn builder(ticktick: &TickTick, name: String) -> ProjectBuilder {
50 ProjectBuilder::new(ticktick, name)
51 }
52 pub fn get_id(self) -> ProjectID {
53 self.id
54 }
55 pub async fn get_data(&self) -> Result<ProjectData, TickTickError> {
56 let resp = self
57 .http_client
58 .get(format!(
59 "https://ticktick.com/open/v1/project/{}/data",
60 self.id.0
61 ))
62 .send()
63 .await?
64 .error_for_status()?;
65 let mut project_data = resp.json::<ProjectData>().await?;
66 project_data
67 .tasks
68 .iter_mut()
69 .for_each(|task| task.http_client = self.http_client.clone());
70 Ok(project_data)
71 }
72 pub async fn get_all(ticktick: &TickTick) -> Result<Vec<Project>, TickTickError> {
73 ticktick.get_all_projects().await
74 }
75 pub async fn get_tasks(&self) -> Result<Vec<Task>, TickTickError> {
76 Ok(self.get_data().await?.tasks)
77 }
78 pub async fn get_columns(&self) -> Result<Vec<Column>, TickTickError> {
79 Ok(self.get_data().await?.columns)
80 }
81 pub async fn get(ticktick: &TickTick, id: &ProjectID) -> Result<Project, TickTickError> {
82 ticktick.get_project(id).await
83 }
84 pub async fn publish_changes(&self) -> Result<(), reqwest::Error> {
87 self.http_client
88 .post(format!(
89 "https://ticktick.com/open/v1/project/{}",
90 self.id.0
91 ))
92 .json(self)
93 .send()
94 .await?
95 .text()
96 .await?;
97 Ok(())
98 }
99}
100
101#[derive(Serialize, Deserialize, Default, Debug)]
102#[serde(from = "String", rename_all = "lowercase")]
103pub enum ProjectViewMode {
104 #[default]
105 List,
106 Kanban,
107 Timeline,
108}
109
110impl From<String> for ProjectViewMode {
111 fn from(value: String) -> Self {
112 match value.as_str() {
113 "list" => Self::List,
114 "kanban" => Self::Kanban,
115 "timeline" => Self::Timeline,
116 _ => Self::List,
117 }
118 }
119}
120
121#[derive(Serialize, Deserialize, Default, Debug)]
122#[serde(from = "String", rename_all = "lowercase")]
123pub enum ProjectUserPermissions {
124 #[default]
125 Read,
126 Write,
127 Comment,
128}
129
130impl From<String> for ProjectUserPermissions {
131 fn from(value: String) -> Self {
132 match value.as_str() {
133 "read" => Self::Read,
134 "write" => Self::Write,
135 "comment" => Self::Comment,
136 _ => Self::Read,
137 }
138 }
139}
140
141#[derive(Serialize, Deserialize, Default, Debug)]
142#[serde(from = "String", rename_all = "UPPERCASE")]
143pub enum ProjectKind {
144 #[default]
145 Task,
146 Note,
147}
148
149impl From<String> for ProjectKind {
150 fn from(value: String) -> Self {
151 match value.as_str() {
152 "TASK" => Self::Task,
153 "NOTE" => Self::Note,
154 _ => Self::Task,
155 }
156 }
157}
158
159#[derive(Serialize, Deserialize, Default, Debug)]
162#[serde(rename_all = "camelCase")]
163pub struct ProjectData {
164 pub tasks: Vec<Task>,
165 pub columns: Vec<Column>,
166}
167
168#[derive(Serialize, Deserialize, Default, Debug, Clone)]
169#[serde(transparent)]
170pub struct ColumnID(pub String);
171
172impl ColumnID {
173 pub fn is_empty(&self) -> bool {
174 self.0.is_empty()
175 }
176}
177
178#[derive(Serialize, Deserialize, Default, Debug)]
179#[serde(rename_all = "camelCase")]
180pub struct Column {
181 id: ColumnID,
182 project_id: ProjectID,
183 name: String,
184 sort_order: i64,
185}