cellstate/entity/
team.rs

1//! `cellstate team` — Manage teams (group-based access control).
2//!
3//! Subcommands: create, list, get, update, delete, members, add-member, remove-member.
4
5use anyhow::{anyhow, Result};
6use clap::{Arg, ArgMatches, Command};
7
8use cellstate_core::api_types::{
9    AddTeamMemberRequest, CreateTeamRequest, ListTeamMembersResponse, ListTeamsResponse,
10    TeamMemberResponse, TeamResponse, UpdateTeamRequest,
11};
12
13use super::client::ApiClient;
14use super::output::OutputConfig;
15
16const BASE: &str = "/api/v1/teams";
17
18pub fn build_command() -> Command {
19    Command::new("team")
20        .about("Manage teams (group-based access control)")
21        .subcommand_required(true)
22        .subcommand(
23            Command::new("create")
24                .about("Create a new team")
25                .arg(Arg::new("name").required(true).help("Team name"))
26                .arg(
27                    Arg::new("description")
28                        .long("description")
29                        .short('d')
30                        .help("Description"),
31                ),
32        )
33        .subcommand(Command::new("list").about("List teams"))
34        .subcommand(
35            Command::new("get")
36                .about("Get team details")
37                .arg(Arg::new("id").required(true).help("Team ID")),
38        )
39        .subcommand(
40            Command::new("update")
41                .about("Update a team")
42                .arg(Arg::new("id").required(true).help("Team ID"))
43                .arg(Arg::new("name").long("name").help("New name"))
44                .arg(
45                    Arg::new("description")
46                        .long("description")
47                        .short('d')
48                        .help("New description"),
49                ),
50        )
51        .subcommand(
52            Command::new("delete")
53                .about("Delete a team")
54                .arg(Arg::new("id").required(true).help("Team ID")),
55        )
56        .subcommand(
57            Command::new("members")
58                .about("List members of a team")
59                .arg(Arg::new("id").required(true).help("Team ID")),
60        )
61        .subcommand(
62            Command::new("add-member")
63                .about("Add a member to a team")
64                .arg(Arg::new("id").required(true).help("Team ID"))
65                .arg(Arg::new("user-id").required(true).help("User ID to add"))
66                .arg(
67                    Arg::new("role")
68                        .long("role")
69                        .default_value("member")
70                        .help("Role within the team (owner, admin, member)"),
71                ),
72        )
73        .subcommand(
74            Command::new("remove-member")
75                .about("Remove a member from a team")
76                .arg(Arg::new("id").required(true).help("Team ID"))
77                .arg(Arg::new("user-id").required(true).help("User ID to remove")),
78        )
79}
80
81pub async fn dispatch(
82    matches: &ArgMatches,
83    client: &ApiClient,
84    output: &OutputConfig,
85    _session: &crate::session::CliSession,
86) -> Result<()> {
87    match matches.subcommand() {
88        Some(("create", sub)) => {
89            let name = sub.get_one::<String>("name").unwrap().clone();
90            let req = CreateTeamRequest {
91                name,
92                description: sub.get_one::<String>("description").cloned(),
93            };
94            let resp: TeamResponse = client.post(BASE, &req).await?;
95            output.print(&resp);
96        }
97        Some(("list", _sub)) => {
98            let resp: ListTeamsResponse = client.get(BASE).await?;
99            let total = resp.teams.len() as i64;
100            output.print_list(&resp.teams, total);
101        }
102        Some(("get", sub)) => {
103            let id = sub.get_one::<String>("id").unwrap();
104            let resp: TeamResponse = client.get(&format!("{BASE}/{id}")).await?;
105            output.print(&resp);
106        }
107        Some(("update", sub)) => {
108            let id = sub.get_one::<String>("id").unwrap();
109            let req = UpdateTeamRequest {
110                name: sub.get_one::<String>("name").cloned(),
111                description: sub
112                    .get_one::<String>("description")
113                    .map(|s| Some(s.clone())),
114            };
115            let resp: TeamResponse = client.patch(&format!("{BASE}/{id}"), &req).await?;
116            output.print(&resp);
117        }
118        Some(("delete", sub)) => {
119            let id = sub.get_one::<String>("id").unwrap();
120            client.delete(&format!("{BASE}/{id}")).await?;
121            if output.json {
122                println!(r#"{{"deleted": "{id}"}}"#);
123            } else {
124                println!("Deleted team {id}");
125            }
126        }
127        Some(("members", sub)) => {
128            let id = sub.get_one::<String>("id").unwrap();
129            let resp: ListTeamMembersResponse = client.get(&format!("{BASE}/{id}/members")).await?;
130            let total = resp.members.len() as i64;
131            output.print_list(&resp.members, total);
132        }
133        Some(("add-member", sub)) => {
134            let id = sub.get_one::<String>("id").unwrap();
135            let user_id = sub.get_one::<String>("user-id").unwrap().clone();
136            let role = sub
137                .get_one::<String>("role")
138                .unwrap()
139                .parse()
140                .map_err(|e: String| anyhow!(e))?;
141            let req = AddTeamMemberRequest { user_id, role };
142            let resp: TeamMemberResponse =
143                client.post(&format!("{BASE}/{id}/members"), &req).await?;
144            output.print(&resp);
145        }
146        Some(("remove-member", sub)) => {
147            let id = sub.get_one::<String>("id").unwrap();
148            let user_id = sub.get_one::<String>("user-id").unwrap();
149            client
150                .delete(&format!("{BASE}/{id}/members/{user_id}"))
151                .await?;
152            if output.json {
153                println!(r#"{{"removed": "{user_id}", "team": "{id}"}}"#);
154            } else {
155                println!("Removed member {user_id} from team {id}");
156            }
157        }
158        _ => unreachable!("subcommand_required(true) prevents this"),
159    }
160    Ok(())
161}