1use crate::client::ClickUpClient;
2use crate::commands::auth::resolve_token;
3use crate::error::CliError;
4use crate::git;
5use crate::output::OutputConfig;
6use crate::Cli;
7use clap::Subcommand;
8
9#[derive(Subcommand)]
10pub enum CommentCommands {
11 List {
13 #[arg(long, conflicts_with_all = ["list", "view"])]
15 task: Option<String>,
16 #[arg(long, conflicts_with_all = ["task", "view"])]
18 list: Option<String>,
19 #[arg(long, conflicts_with_all = ["task", "list"])]
21 view: Option<String>,
22 },
23 Create {
25 #[arg(long, conflicts_with_all = ["list", "view"])]
27 task: Option<String>,
28 #[arg(long, conflicts_with_all = ["task", "view"])]
30 list: Option<String>,
31 #[arg(long, conflicts_with_all = ["task", "list"])]
33 view: Option<String>,
34 #[arg(long)]
36 text: String,
37 #[arg(long)]
39 assignee: Option<i64>,
40 #[arg(long)]
42 notify_all: bool,
43 },
44 Update {
46 id: String,
48 #[arg(long)]
50 text: String,
51 #[arg(long)]
53 resolved: bool,
54 #[arg(long)]
56 assignee: Option<i64>,
57 },
58 Delete {
60 id: String,
62 },
63 Replies {
65 id: String,
67 },
68 Reply {
70 id: String,
72 #[arg(long)]
74 text: String,
75 #[arg(long)]
77 assignee: Option<i64>,
78 },
79}
80
81const COMMENT_FIELDS: &[&str] = &["id", "user", "date", "comment_text"];
82
83pub async fn execute(command: CommentCommands, cli: &Cli) -> Result<(), CliError> {
84 let token = resolve_token(cli)?;
85 let client = ClickUpClient::new(&token, cli.timeout)?;
86 let output = OutputConfig::from_cli(&cli.output, &cli.fields, cli.no_header, cli.quiet);
87
88 match command {
89 CommentCommands::List { task, list, view } => {
90 let base = if let Some(id) = list {
91 format!("/v2/list/{}/comment", id)
92 } else if let Some(id) = view {
93 format!("/v2/view/{}/comment", id)
94 } else if let Some(resolved) = git::resolve_task(cli, task.as_deref(), true)? {
95 format!("/v2/task/{}/comment", resolved.id)
96 } else {
97 return Err(CliError::ClientError {
98 message: "One of --task, --list, or --view is required".to_string(),
99 status: 0,
100 });
101 };
102 let comments = crate::commands::pagination::walk_start_id(
103 cli,
104 &client,
105 "comments",
106 |start, start_id| match (start, start_id) {
107 (Some(s), Some(sid)) => format!("{}?start={}&start_id={}", base, s, sid),
108 _ => base.clone(),
109 },
110 )
111 .await?;
112 let truncated: Vec<serde_json::Value> = comments
113 .into_iter()
114 .map(|mut c| {
115 if let Some(text) = c.get("comment_text").and_then(|v| v.as_str()) {
116 let truncated = if text.chars().count() > 60 {
119 let head: String = text.chars().take(60).collect();
120 format!("{}…", head)
121 } else {
122 text.to_string()
123 };
124 c["comment_text"] = serde_json::Value::String(truncated);
125 }
126 c
127 })
128 .collect();
129 output.print_items(&truncated, COMMENT_FIELDS, "id");
130 Ok(())
131 }
132 CommentCommands::Create {
133 task,
134 list,
135 view,
136 text,
137 assignee,
138 notify_all,
139 } => {
140 let (url, resp) = if let Some(id) = list {
141 let body = serde_json::json!({ "comment_text": text });
142 let r = client
143 .post(&format!("/v2/list/{}/comment", id), &body)
144 .await?;
145 (format!("/v2/list/{}/comment", id), r)
146 } else if let Some(id) = view {
147 let body = serde_json::json!({ "comment_text": text });
148 let r = client
149 .post(&format!("/v2/view/{}/comment", id), &body)
150 .await?;
151 (format!("/v2/view/{}/comment", id), r)
152 } else if let Some(resolved) = git::resolve_task(cli, task.as_deref(), true)? {
153 let mut body = serde_json::json!({
154 "comment_text": text,
155 "notify_all": notify_all,
156 });
157 if let Some(a) = assignee {
158 body["assignee"] = serde_json::json!(a);
159 }
160 let r = client
161 .post(&format!("/v2/task/{}/comment", resolved.id), &body)
162 .await?;
163 (format!("/v2/task/{}/comment", resolved.id), r)
164 } else {
165 return Err(CliError::ClientError {
166 message: "One of --task, --list, or --view is required".to_string(),
167 status: 0,
168 });
169 };
170 let _ = url;
171 output.print_single(&resp, COMMENT_FIELDS, "id");
172 Ok(())
173 }
174 CommentCommands::Update {
175 id,
176 text,
177 resolved,
178 assignee,
179 } => {
180 let mut body = serde_json::json!({ "comment_text": text });
181 if resolved {
182 body["resolved"] = serde_json::Value::Bool(true);
183 }
184 if let Some(a) = assignee {
185 body["assignee"] = serde_json::json!(a);
186 }
187 let resp = client.put(&format!("/v2/comment/{}", id), &body).await?;
188 output.print_single(&resp, COMMENT_FIELDS, "id");
189 Ok(())
190 }
191 CommentCommands::Delete { id } => {
192 client.delete(&format!("/v2/comment/{}", id)).await?;
193 output.print_message(&format!("Comment {} deleted", id));
194 Ok(())
195 }
196 CommentCommands::Replies { id } => {
197 let comments = crate::commands::pagination::walk_start_id(
198 cli,
199 &client,
200 "comments",
201 |start, start_id| match (start, start_id) {
202 (Some(s), Some(sid)) => {
203 format!("/v2/comment/{}/reply?start={}&start_id={}", id, s, sid)
204 }
205 _ => format!("/v2/comment/{}/reply", id),
206 },
207 )
208 .await?;
209 output.print_items(&comments, COMMENT_FIELDS, "id");
210 Ok(())
211 }
212 CommentCommands::Reply { id, text, assignee } => {
213 let mut body = serde_json::json!({ "comment_text": text });
214 if let Some(a) = assignee {
215 body["assignee"] = serde_json::json!(a);
216 }
217 let resp = client
218 .post(&format!("/v2/comment/{}/reply", id), &body)
219 .await?;
220 output.print_single(&resp, COMMENT_FIELDS, "id");
221 Ok(())
222 }
223 }
224}