1use 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}