Skip to main content

ironflow_cli/commands/
user.rs

1//! User subcommands: list, create, delete, set-role, groups, set-groups.
2
3use std::slice;
4
5use anyhow::{Context, Result};
6use clap::{ArgGroup, Args, Subcommand};
7use ironflow_sdk::IronflowClient;
8use ironflow_sdk::types::{CreateUserRequest, UpdateRoleRequest};
9use uuid::Uuid;
10
11use crate::confirm::{confirm, resolve_secret_value};
12use crate::output;
13
14/// Arguments for the `user` command group.
15#[derive(Debug, Args)]
16pub struct UserArgs {
17    /// User subcommand.
18    #[command(subcommand)]
19    pub command: UserCommands,
20}
21
22/// Available user subcommands.
23#[derive(Debug, Subcommand)]
24pub enum UserCommands {
25    /// List users.
26    List,
27    /// Create a user.
28    Create {
29        /// Display username.
30        username: String,
31        /// Email address.
32        #[arg(long)]
33        email: String,
34        /// Plaintext password (min 8 characters). Read from stdin when
35        /// omitted, which keeps it out of the shell history.
36        #[arg(long)]
37        password: Option<String>,
38        /// Grant admin rights to the new user.
39        #[arg(long)]
40        admin: bool,
41    },
42    /// Delete a user.
43    Delete {
44        /// User UUID.
45        id: Uuid,
46        /// Skip the interactive confirmation.
47        #[arg(long)]
48        yes: bool,
49    },
50    /// Promote a user to admin or demote them to member.
51    #[command(group(ArgGroup::new("role").required(true).args(["admin", "member"])))]
52    SetRole {
53        /// User UUID.
54        id: Uuid,
55        /// Grant admin rights.
56        #[arg(long)]
57        admin: bool,
58        /// Revoke admin rights.
59        #[arg(long)]
60        member: bool,
61    },
62    /// Show the groups a user belongs to.
63    Groups {
64        /// User UUID.
65        id: Uuid,
66    },
67    /// Replace the groups a user belongs to.
68    ///
69    /// Group membership restricts who may vote on an approval gate whose
70    /// approvers list groups. Without any `--group`, the user leaves every
71    /// group.
72    SetGroups {
73        /// User UUID.
74        id: Uuid,
75        /// Group name. Repeat the flag for several groups.
76        #[arg(long = "group")]
77        groups: Vec<String>,
78    },
79}
80
81/// Execute a user subcommand.
82///
83/// # Errors
84///
85/// Returns an error on API failure, on an empty password, or when a
86/// destructive command is not confirmed.
87pub async fn execute(client: &IronflowClient, args: &UserArgs, json_mode: bool) -> Result<()> {
88    match &args.command {
89        UserCommands::List => {
90            let response = client.list_users().await?;
91            output::print_output(json_mode, &response, || output::users_table(&response.data))?;
92        }
93        UserCommands::Create {
94            username,
95            email,
96            password,
97            admin,
98        } => {
99            let password = resolve_secret_value(password.as_deref(), "password")?;
100            let request: CreateUserRequest = CreateUserRequest::builder()
101                .username(username.clone())
102                .email(email.clone())
103                .password(password)
104                .is_admin(*admin)
105                .try_into()
106                .context("failed to build CreateUserRequest")?;
107
108            let response = client.create_user(&request).await?;
109            output::print_output(json_mode, &response, || {
110                output::users_table(slice::from_ref(&response.data))
111            })?;
112        }
113        UserCommands::Delete { id, yes } => {
114            confirm(&format!("Delete user '{id}'?"), *yes)?;
115            client.delete_user(*id).await?;
116            output::report_deletion(json_mode, "user", id.to_string())?;
117        }
118        // `--admin` and `--member` are an exclusive, required clap group, so
119        // `admin` alone carries the whole decision.
120        UserCommands::SetRole { id, admin, .. } => {
121            let request: UpdateRoleRequest = UpdateRoleRequest::builder()
122                .is_admin(*admin)
123                .try_into()
124                .context("failed to build UpdateRoleRequest")?;
125
126            let response = client.update_role(*id, &request).await?;
127            output::print_output(json_mode, &response, || {
128                output::users_table(slice::from_ref(&response.data))
129            })?;
130        }
131        UserCommands::Groups { id } => {
132            let response = client.get_user_groups(*id).await?;
133            output::print_output(json_mode, &response, || {
134                output::user_groups_table(&response.data)
135            })?;
136        }
137        UserCommands::SetGroups { id, groups } => {
138            let response = client.update_user_groups(*id, groups).await?;
139            output::print_output(json_mode, &response, || {
140                output::user_groups_table(&response.data)
141            })?;
142        }
143    }
144    Ok(())
145}