use clap::Subcommand;
use crate::client::ClickUpClient;
use crate::commands::auth::resolve_token;
use crate::error::CliError;
use crate::output::OutputConfig;
use crate::Cli;
#[derive(Subcommand)]
pub enum ListCommands {
List {
#[arg(long)]
folder: Option<String>,
#[arg(long)]
space: Option<String>,
#[arg(long)]
archived: bool,
},
Get {
id: String,
},
Create {
#[arg(long)]
folder: Option<String>,
#[arg(long)]
space: Option<String>,
#[arg(long)]
name: String,
#[arg(long)]
content: Option<String>,
#[arg(long)]
priority: Option<u8>,
#[arg(long)]
due_date: Option<String>,
},
Update {
id: String,
#[arg(long)]
name: Option<String>,
#[arg(long)]
content: Option<String>,
},
Delete {
id: String,
},
AddTask {
list_id: String,
task_id: String,
},
RemoveTask {
list_id: String,
task_id: String,
},
}
pub async fn execute(command: ListCommands, cli: &Cli) -> Result<(), CliError> {
let token = resolve_token(cli)?;
let client = ClickUpClient::new(&token, cli.timeout)?;
let output = OutputConfig::from_cli(&cli.output, &cli.fields, cli.no_header, cli.quiet);
let default_fields = &["id", "name", "task_count", "status", "due_date"];
match command {
ListCommands::List { folder, space, archived } => {
let path = match (&folder, &space) {
(Some(f), _) => format!("/v2/folder/{}/list?archived={}", f, archived),
(_, Some(s)) => format!("/v2/space/{}/list?archived={}", s, archived),
_ => {
return Err(CliError::ClientError {
message: "Provide --folder or --space".into(),
status: 0,
});
}
};
let resp = client.get(&path).await?;
let lists = resp
.get("lists")
.and_then(|l| l.as_array())
.cloned()
.unwrap_or_default();
output.print_items(&lists, default_fields, "id");
Ok(())
}
ListCommands::Get { id } => {
let resp = client.get(&format!("/v2/list/{}", id)).await?;
output.print_single(&resp, default_fields, "id");
Ok(())
}
ListCommands::Create {
folder,
space,
name,
content,
priority,
due_date,
} => {
let path = match (&folder, &space) {
(Some(f), _) => format!("/v2/folder/{}/list", f),
(_, Some(s)) => format!("/v2/space/{}/list", s),
_ => {
return Err(CliError::ClientError {
message: "Provide --folder or --space".into(),
status: 0,
});
}
};
let mut body = serde_json::json!({ "name": name });
if let Some(c) = content {
body["content"] = serde_json::Value::String(c);
}
if let Some(p) = priority {
body["priority"] = serde_json::json!(p);
}
if let Some(d) = due_date {
body["due_date"] = serde_json::Value::String(date_to_ms(&d)?);
}
let resp = client.post(&path, &body).await?;
output.print_single(&resp, default_fields, "id");
Ok(())
}
ListCommands::Update { id, name, content } => {
let mut body = serde_json::Map::new();
if let Some(n) = name {
body.insert("name".into(), serde_json::Value::String(n));
}
if let Some(c) = content {
body.insert("content".into(), serde_json::Value::String(c));
}
let resp = client
.put(&format!("/v2/list/{}", id), &serde_json::Value::Object(body))
.await?;
output.print_single(&resp, default_fields, "id");
Ok(())
}
ListCommands::Delete { id } => {
client.delete(&format!("/v2/list/{}", id)).await?;
output.print_message(&format!("List {} deleted", id));
Ok(())
}
ListCommands::AddTask { list_id, task_id } => {
client
.post(
&format!("/v2/list/{}/task/{}", list_id, task_id),
&serde_json::json!({}),
)
.await?;
output.print_message(&format!("Task {} added to list {}", task_id, list_id));
Ok(())
}
ListCommands::RemoveTask { list_id, task_id } => {
client
.delete(&format!("/v2/list/{}/task/{}", list_id, task_id))
.await?;
output.print_message(&format!("Task {} removed from list {}", task_id, list_id));
Ok(())
}
}
}
fn date_to_ms(date_str: &str) -> Result<String, CliError> {
let naive = chrono::NaiveDate::parse_from_str(date_str, "%Y-%m-%d")
.map_err(|_| CliError::ClientError {
message: format!("Invalid date '{}'. Use YYYY-MM-DD format.", date_str),
status: 0,
})?;
let dt = naive
.and_hms_opt(0, 0, 0)
.unwrap()
.and_utc();
Ok((dt.timestamp_millis()).to_string())
}