1use clap::Subcommand;
2use crate::client::ClickUpClient;
3use crate::commands::auth::resolve_token;
4use crate::error::CliError;
5use crate::output::OutputConfig;
6use crate::Cli;
7
8#[derive(Subcommand)]
9pub enum CommentCommands {
10 List {
12 #[arg(long, conflicts_with_all = ["list", "view"])]
14 task: Option<String>,
15 #[arg(long, conflicts_with_all = ["task", "view"])]
17 list: Option<String>,
18 #[arg(long, conflicts_with_all = ["task", "list"])]
20 view: Option<String>,
21 },
22 Create {
24 #[arg(long, conflicts_with_all = ["list", "view"])]
26 task: Option<String>,
27 #[arg(long, conflicts_with_all = ["task", "view"])]
29 list: Option<String>,
30 #[arg(long, conflicts_with_all = ["task", "list"])]
32 view: Option<String>,
33 #[arg(long)]
35 text: String,
36 #[arg(long)]
38 assignee: Option<i64>,
39 #[arg(long)]
41 notify_all: bool,
42 },
43 Update {
45 id: String,
47 #[arg(long)]
49 text: String,
50 #[arg(long)]
52 resolved: bool,
53 #[arg(long)]
55 assignee: Option<i64>,
56 },
57 Delete {
59 id: String,
61 },
62 Replies {
64 id: String,
66 },
67 Reply {
69 id: String,
71 #[arg(long)]
73 text: String,
74 #[arg(long)]
76 assignee: Option<i64>,
77 },
78}
79
80const COMMENT_FIELDS: &[&str] = &["id", "user", "date", "comment_text"];
81
82pub async fn execute(command: CommentCommands, cli: &Cli) -> Result<(), CliError> {
83 let token = resolve_token(cli)?;
84 let client = ClickUpClient::new(&token, cli.timeout)?;
85 let output = OutputConfig::from_cli(&cli.output, &cli.fields, cli.no_header, cli.quiet);
86
87 match command {
88 CommentCommands::List { task, list, view } => {
89 let (url, key) = if let Some(id) = task {
90 (format!("/v2/task/{}/comment", id), "comments")
91 } else if let Some(id) = list {
92 (format!("/v2/list/{}/comment", id), "comments")
93 } else if let Some(id) = view {
94 (format!("/v2/view/{}/comment", id), "comments")
95 } else {
96 return Err(CliError::ClientError {
97 message: "One of --task, --list, or --view is required".to_string(),
98 status: 0,
99 });
100 };
101 let resp = client.get(&url).await?;
102 let comments = resp
103 .get(key)
104 .and_then(|c| c.as_array())
105 .cloned()
106 .unwrap_or_default();
107 let truncated: Vec<serde_json::Value> = comments
108 .into_iter()
109 .map(|mut c| {
110 if let Some(text) = c.get("comment_text").and_then(|v| v.as_str()) {
111 let truncated = if text.len() > 60 {
112 format!("{}…", &text[..60])
113 } else {
114 text.to_string()
115 };
116 c["comment_text"] = serde_json::Value::String(truncated);
117 }
118 c
119 })
120 .collect();
121 output.print_items(&truncated, COMMENT_FIELDS, "id");
122 Ok(())
123 }
124 CommentCommands::Create {
125 task,
126 list,
127 view,
128 text,
129 assignee,
130 notify_all,
131 } => {
132 let (url, resp) = if let Some(id) = task {
133 let mut body = serde_json::json!({
134 "comment_text": text,
135 "notify_all": notify_all,
136 });
137 if let Some(a) = assignee {
138 body["assignee"] = serde_json::json!(a);
139 }
140 let r = client
141 .post(&format!("/v2/task/{}/comment", id), &body)
142 .await?;
143 (format!("/v2/task/{}/comment", id), r)
144 } else if let Some(id) = list {
145 let body = serde_json::json!({ "comment_text": text });
146 let r = client
147 .post(&format!("/v2/list/{}/comment", id), &body)
148 .await?;
149 (format!("/v2/list/{}/comment", id), r)
150 } else if let Some(id) = view {
151 let body = serde_json::json!({ "comment_text": text });
152 let r = client
153 .post(&format!("/v2/view/{}/comment", id), &body)
154 .await?;
155 (format!("/v2/view/{}/comment", id), r)
156 } else {
157 return Err(CliError::ClientError {
158 message: "One of --task, --list, or --view is required".to_string(),
159 status: 0,
160 });
161 };
162 let _ = url;
163 output.print_single(&resp, COMMENT_FIELDS, "id");
164 Ok(())
165 }
166 CommentCommands::Update {
167 id,
168 text,
169 resolved,
170 assignee,
171 } => {
172 let mut body = serde_json::json!({ "comment_text": text });
173 if resolved {
174 body["resolved"] = serde_json::Value::Bool(true);
175 }
176 if let Some(a) = assignee {
177 body["assignee"] = serde_json::json!(a);
178 }
179 let resp = client
180 .put(&format!("/v2/comment/{}", id), &body)
181 .await?;
182 output.print_single(&resp, COMMENT_FIELDS, "id");
183 Ok(())
184 }
185 CommentCommands::Delete { id } => {
186 client.delete(&format!("/v2/comment/{}", id)).await?;
187 output.print_message(&format!("Comment {} deleted", id));
188 Ok(())
189 }
190 CommentCommands::Replies { id } => {
191 let resp = client
192 .get(&format!("/v2/comment/{}/reply", id))
193 .await?;
194 let comments = resp
195 .get("comments")
196 .and_then(|c| c.as_array())
197 .cloned()
198 .unwrap_or_default();
199 output.print_items(&comments, COMMENT_FIELDS, "id");
200 Ok(())
201 }
202 CommentCommands::Reply { id, text, assignee } => {
203 let mut body = serde_json::json!({ "comment_text": text });
204 if let Some(a) = assignee {
205 body["assignee"] = serde_json::json!(a);
206 }
207 let resp = client
208 .post(&format!("/v2/comment/{}/reply", id), &body)
209 .await?;
210 output.print_single(&resp, COMMENT_FIELDS, "id");
211 Ok(())
212 }
213 }
214}