diff --git a/README.md b/README.md index dc7a63a..815e780 100644 --- a/README.md +++ b/README.md @@ -53,4 +53,11 @@ slot deployments describe View predeployed accounts ```sh slot deployments account katana +``` + +Manage collaborators with teams +```sh +slot teams list +slot teams add +slot teams remove ``` \ No newline at end of file diff --git a/schema.json b/schema.json index 049c7fd..7b51c17 100644 --- a/schema.json +++ b/schema.json @@ -20847,6 +20847,112 @@ } } }, + { + "args": [ + { + "defaultValue": null, + "description": null, + "name": "teamID", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + }, + { + "defaultValue": null, + "description": null, + "name": "userIDs", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + } + } + } + ], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "addToTeam", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + } + }, + { + "args": [ + { + "defaultValue": null, + "description": null, + "name": "teamID", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + }, + { + "defaultValue": null, + "description": null, + "name": "userIDs", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + } + } + } + ], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "removeFromTeam", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + } + }, { "args": [ { @@ -22626,6 +22732,96 @@ "ofType": null } }, + { + "args": [ + { + "defaultValue": null, + "description": null, + "name": "id", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + } + ], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "team", + "type": { + "kind": "OBJECT", + "name": "Team", + "ofType": null + } + }, + { + "args": [ + { + "defaultValue": null, + "description": null, + "name": "after", + "type": { + "kind": "SCALAR", + "name": "Cursor", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "first", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "before", + "type": { + "kind": "SCALAR", + "name": "Cursor", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "last", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + { + "defaultValue": null, + "description": null, + "name": "where", + "type": { + "kind": "INPUT_OBJECT", + "name": "TeamWhereInput", + "ofType": null + } + } + ], + "deprecationReason": null, + "description": null, + "isDeprecated": false, + "name": "teams", + "type": { + "kind": "OBJECT", + "name": "TeamConnection", + "ofType": null + } + }, { "args": [ { diff --git a/src/command.rs b/src/command.rs index c485dac..782e031 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,11 +1,13 @@ pub mod auth; pub mod deployments; +pub mod teams; use anyhow::Result; use clap::Subcommand; use auth::Auth; use deployments::Deployments; +use teams::Teams; #[allow(clippy::large_enum_variant)] #[derive(Subcommand, Debug)] @@ -16,6 +18,8 @@ pub enum Command { #[command(subcommand)] #[command(about = "Manage Slot deployments.", aliases = ["d"])] Deployments(Deployments), + #[command(about = "Manage Slot team.", aliases = ["t"])] + Teams(Teams), } impl Command { @@ -23,6 +27,7 @@ impl Command { match &self { Command::Auth(cmd) => cmd.run().await, Command::Deployments(cmd) => cmd.run().await, + Command::Teams(cmd) => cmd.run().await, } } } diff --git a/src/command/teams/members.graphql b/src/command/teams/members.graphql new file mode 100644 index 0000000..a94570a --- /dev/null +++ b/src/command/teams/members.graphql @@ -0,0 +1,19 @@ +query TeamMembersList($team: ID!) { + team(id: $team) { + members { + edges { + node { + id + } + } + } + } +} + +mutation TeamMemberAdd($team: ID!, $accounts: [ID!]!) { + addToTeam(teamID: $team, userIDs: $accounts) +} + +mutation TeamMemberRemove($team: ID!, $accounts: [ID!]!) { + removeFromTeam(teamID: $team, userIDs: $accounts) +} diff --git a/src/command/teams/members.rs b/src/command/teams/members.rs new file mode 100644 index 0000000..69fcf01 --- /dev/null +++ b/src/command/teams/members.rs @@ -0,0 +1,121 @@ +use anyhow::Result; +use clap::Args; +use graphql_client::{GraphQLQuery, Response}; + +use crate::api::ApiClient; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "schema.json", + query_path = "src/command/teams/members.graphql", + response_derives = "Debug" +)] +pub struct TeamMembersList; + +#[derive(Debug, Args, serde::Serialize)] +#[command(next_help_heading = "Team list options")] +pub struct TeamListArgs {} + +impl TeamListArgs { + pub async fn run(&self, team: String) -> Result<()> { + let request_body = + TeamMembersList::build_query(self::team_members_list::Variables { team: team.clone() }); + + let client = ApiClient::new(); + let res: Response = client.post(&request_body).await?; + if let Some(errors) = res.errors.clone() { + for err in errors { + println!("Error: {}", err.message); + } + + return Ok(()); + } + + if let Some(data) = res.data { + println!("{} members:", team); + data.team + .and_then(|team_list| team_list.members.edges) + .into_iter() + .flatten() + .for_each(|edge| { + if let Some(node) = edge.and_then(|edge| edge.node) { + println!(" {}", node.id) + } + }); + } + + Ok(()) + } +} + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "schema.json", + query_path = "src/command/teams/members.graphql", + response_derives = "Debug" +)] +pub struct TeamMemberAdd; + +#[derive(Debug, Args, serde::Serialize)] +#[command(next_help_heading = "Team add options")] +pub struct TeamAddArgs { + #[arg(help = "Name of the team member to add.")] + pub account: Vec, +} + +impl TeamAddArgs { + pub async fn run(&self, team: String) -> Result<()> { + let request_body = TeamMemberAdd::build_query(self::team_member_add::Variables { + team, + accounts: self.account.clone(), + }); + + let client = ApiClient::new(); + let res: Response = client.post(&request_body).await?; + if let Some(errors) = res.errors.clone() { + for err in errors { + println!("Error: {}", err.message); + } + + return Ok(()); + } + + Ok(()) + } +} + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "schema.json", + query_path = "src/command/teams/members.graphql", + response_derives = "Debug" +)] +pub struct TeamMemberRemove; + +#[derive(Debug, Args, serde::Serialize)] +#[command(next_help_heading = "Team remove options")] +pub struct TeamRemoveArgs { + #[arg(help = "Name of the team member to add.")] + pub account: Vec, +} + +impl TeamRemoveArgs { + pub async fn run(&self, team: String) -> Result<()> { + let request_body = TeamMemberRemove::build_query(self::team_member_remove::Variables { + team, + accounts: self.account.clone(), + }); + + let client = ApiClient::new(); + let res: Response = client.post(&request_body).await?; + if let Some(errors) = res.errors.clone() { + for err in errors { + println!("Error: {}", err.message); + } + + return Ok(()); + } + + Ok(()) + } +} diff --git a/src/command/teams/mod.rs b/src/command/teams/mod.rs new file mode 100644 index 0000000..6d760c5 --- /dev/null +++ b/src/command/teams/mod.rs @@ -0,0 +1,36 @@ +use anyhow::Result; +use clap::{Args, Subcommand}; + +use self::members::{TeamAddArgs, TeamListArgs, TeamRemoveArgs}; + +mod members; + +#[derive(Debug, Args)] +#[command(next_help_heading = "Team options")] +pub struct Teams { + #[arg(help = "The name of the team.")] + pub name: String, + + #[command(subcommand)] + teams_commands: TeamsCommands, +} + +#[derive(Subcommand, Debug)] +pub enum TeamsCommands { + #[command(about = "List team members.", aliases = ["ls"])] + List(TeamListArgs), + #[command(about = "Add a new team member.")] + Add(TeamAddArgs), + #[command(about = "Remove a team member.")] + Remove(TeamRemoveArgs), +} + +impl Teams { + pub async fn run(&self) -> Result<()> { + match &self.teams_commands { + TeamsCommands::List(args) => args.run(self.name.clone()).await, + TeamsCommands::Add(args) => args.run(self.name.clone()).await, + TeamsCommands::Remove(args) => args.run(self.name.clone()).await, + } + } +}