clickup_cli/commands/
group.rs1use crate::client::ClickUpClient;
2use crate::commands::auth::resolve_token;
3use crate::commands::workspace::resolve_workspace;
4use crate::error::CliError;
5use crate::output::OutputConfig;
6use crate::Cli;
7use clap::Subcommand;
8
9#[derive(Subcommand)]
10pub enum GroupCommands {
11 List,
13 Create {
15 #[arg(long)]
17 name: String,
18 #[arg(long = "member")]
20 members: Vec<String>,
21 },
22 Update {
24 id: String,
26 #[arg(long)]
28 name: Option<String>,
29 #[arg(long = "add-member")]
31 add_members: Vec<String>,
32 #[arg(long = "rem-member")]
34 rem_members: Vec<String>,
35 },
36 Delete {
38 id: String,
40 },
41}
42
43const GROUP_FIELDS: &[&str] = &["id", "name", "members"];
44
45pub async fn execute(command: GroupCommands, cli: &Cli) -> Result<(), CliError> {
46 let token = resolve_token(cli)?;
47 let client = ClickUpClient::new(&token, cli.timeout)?;
48 let output = OutputConfig::from_cli(&cli.output, &cli.fields, cli.no_header, cli.quiet);
49
50 match command {
51 GroupCommands::List => {
52 let team_id = resolve_workspace(cli)?;
54 let resp = client
55 .get(&format!("/v2/group?team_id={}", team_id))
56 .await?;
57 let groups = resp
58 .get("groups")
59 .and_then(|g| g.as_array())
60 .cloned()
61 .unwrap_or_default();
62 output.print_items(&groups, GROUP_FIELDS, "id");
63 Ok(())
64 }
65 GroupCommands::Create { name, members } => {
66 let team_id = resolve_workspace(cli)?;
67 let member_ids: Result<Vec<i64>, _> = members
71 .iter()
72 .map(|m| m.parse::<i64>().map_err(|_| m.clone()))
73 .collect();
74 let member_ids = member_ids.map_err(|bad| CliError::ClientError {
75 message: format!("--member must be a numeric user id, got '{}'", bad),
76 status: 0,
77 })?;
78 let body = serde_json::json!({
79 "name": name,
80 "members": member_ids,
81 });
82 let resp = client
83 .post(&format!("/v2/team/{}/group", team_id), &body)
84 .await?;
85 let group = resp.get("group").cloned().unwrap_or(resp);
86 output.print_single(&group, GROUP_FIELDS, "id");
87 Ok(())
88 }
89 GroupCommands::Update {
90 id,
91 name,
92 add_members,
93 rem_members,
94 } => {
95 let mut body = serde_json::Map::new();
96 if let Some(n) = name {
97 body.insert("name".into(), serde_json::Value::String(n));
98 }
99 if !add_members.is_empty() || !rem_members.is_empty() {
100 let parse = |ids: Vec<String>| -> Result<Vec<serde_json::Value>, CliError> {
102 ids.into_iter()
103 .map(|s| {
104 s.parse::<i64>().map(|n| serde_json::json!(n)).map_err(|_| {
105 CliError::ClientError {
106 message: format!(
107 "member id must be a numeric user id, got '{}'",
108 s
109 ),
110 status: 0,
111 }
112 })
113 })
114 .collect()
115 };
116 let add = parse(add_members)?;
117 let rem = parse(rem_members)?;
118 body.insert(
119 "members".into(),
120 serde_json::json!({ "add": add, "rem": rem }),
121 );
122 }
123 let resp = client
124 .put(
125 &format!("/v2/group/{}", id),
126 &serde_json::Value::Object(body),
127 )
128 .await?;
129 let group = resp.get("group").cloned().unwrap_or(resp);
130 output.print_single(&group, GROUP_FIELDS, "id");
131 Ok(())
132 }
133 GroupCommands::Delete { id } => {
134 client.delete(&format!("/v2/group/{}", id)).await?;
135 output.print_message(&format!("Group {} deleted", id));
136 Ok(())
137 }
138 }
139}