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 (url, key) = if let Some(id) = list {
91 (format!("/v2/list/{}/comment", id), "comments")
92 } else if let Some(id) = view {
93 (format!("/v2/view/{}/comment", id), "comments")
94 } else if let Some(resolved) = git::resolve_task(cli, task.as_deref(), true)? {
95 (format!("/v2/task/{}/comment", resolved.id), "comments")
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 resp = client.get(&url).await?;
103 let comments = resp
104 .get(key)
105 .and_then(|c| c.as_array())
106 .cloned()
107 .unwrap_or_default();
108 let truncated: Vec<serde_json::Value> = comments
109 .into_iter()
110 .map(|mut c| {
111 if let Some(text) = c.get("comment_text").and_then(|v| v.as_str()) {
112 let truncated = if text.len() > 60 {
113 format!("{}…", &text[..60])
114 } else {
115 text.to_string()
116 };
117 c["comment_text"] = serde_json::Value::String(truncated);
118 }
119 c
120 })
121 .collect();
122 output.print_items(&truncated, COMMENT_FIELDS, "id");
123 Ok(())
124 }
125 CommentCommands::Create {
126 task,
127 list,
128 view,
129 text,
130 assignee,
131 notify_all,
132 } => {
133 let (url, resp) = if let Some(id) = list {
134 let body = serde_json::json!({ "comment_text": text });
135 let r = client
136 .post(&format!("/v2/list/{}/comment", id), &body)
137 .await?;
138 (format!("/v2/list/{}/comment", id), r)
139 } else if let Some(id) = view {
140 let body = serde_json::json!({ "comment_text": text });
141 let r = client
142 .post(&format!("/v2/view/{}/comment", id), &body)
143 .await?;
144 (format!("/v2/view/{}/comment", id), r)
145 } else if let Some(resolved) = git::resolve_task(cli, task.as_deref(), true)? {
146 let mut body = serde_json::json!({
147 "comment_text": text,
148 "notify_all": notify_all,
149 });
150 if let Some(a) = assignee {
151 body["assignee"] = serde_json::json!(a);
152 }
153 let r = client
154 .post(&format!("/v2/task/{}/comment", resolved.id), &body)
155 .await?;
156 (format!("/v2/task/{}/comment", resolved.id), r)
157 } else {
158 return Err(CliError::ClientError {
159 message: "One of --task, --list, or --view is required".to_string(),
160 status: 0,
161 });
162 };
163 let _ = url;
164 output.print_single(&resp, COMMENT_FIELDS, "id");
165 Ok(())
166 }
167 CommentCommands::Update {
168 id,
169 text,
170 resolved,
171 assignee,
172 } => {
173 let mut body = serde_json::json!({ "comment_text": text });
174 if resolved {
175 body["resolved"] = serde_json::Value::Bool(true);
176 }
177 if let Some(a) = assignee {
178 body["assignee"] = serde_json::json!(a);
179 }
180 let resp = client.put(&format!("/v2/comment/{}", id), &body).await?;
181 output.print_single(&resp, COMMENT_FIELDS, "id");
182 Ok(())
183 }
184 CommentCommands::Delete { id } => {
185 client.delete(&format!("/v2/comment/{}", id)).await?;
186 output.print_message(&format!("Comment {} deleted", id));
187 Ok(())
188 }
189 CommentCommands::Replies { id } => {
190 let resp = client.get(&format!("/v2/comment/{}/reply", id)).await?;
191 let comments = resp
192 .get("comments")
193 .and_then(|c| c.as_array())
194 .cloned()
195 .unwrap_or_default();
196 output.print_items(&comments, COMMENT_FIELDS, "id");
197 Ok(())
198 }
199 CommentCommands::Reply { id, text, assignee } => {
200 let mut body = serde_json::json!({ "comment_text": text });
201 if let Some(a) = assignee {
202 body["assignee"] = serde_json::json!(a);
203 }
204 let resp = client
205 .post(&format!("/v2/comment/{}/reply", id), &body)
206 .await?;
207 output.print_single(&resp, COMMENT_FIELDS, "id");
208 Ok(())
209 }
210 }
211}