use super::build_api_client;
use crate::{api, config::Config, models::User, output::OutputFormat};
use anyhow::{Context, Result};
use clap::{Args, Subcommand};
use colored::Colorize;
use std::io::{IsTerminal, stdout};
use tokio::runtime::Builder as RuntimeBuilder;
pub const ME_SENTINEL: &str = "me";
#[derive(Subcommand, Debug)]
pub enum UserCommand {
List(UserListArgs),
Show(UserShowArgs),
}
#[derive(Args, Debug)]
pub struct UserListArgs {
#[arg(long)]
pub workspace: Option<String>,
#[arg(long)]
pub limit: Option<usize>,
#[arg(long, value_enum)]
pub output: Option<OutputFormat>,
}
#[derive(Args, Debug)]
pub struct UserShowArgs {
#[arg(value_name = "USER")]
pub user: String,
#[arg(long, value_enum)]
pub output: Option<OutputFormat>,
}
fn determine_output(value: Option<OutputFormat>) -> OutputFormat {
value.unwrap_or_else(|| {
if stdout().is_terminal() {
OutputFormat::Table
} else {
OutputFormat::Json
}
})
}
pub fn handle_user_command(command: UserCommand, config: &Config) -> Result<()> {
let client = build_api_client(config)?;
let runtime = RuntimeBuilder::new_current_thread()
.enable_all()
.build()
.context("failed to initialize async runtime")?;
runtime.block_on(async move {
match command {
UserCommand::List(args) => list_users_command(&client, config, args).await,
UserCommand::Show(args) => show_user_command(&client, args).await,
}
})
}
async fn list_users_command(
client: &api::ApiClient,
config: &Config,
args: UserListArgs,
) -> Result<()> {
let workspace_gid = args
.workspace
.as_deref()
.or_else(|| config.default_workspace())
.context("workspace is required; provide --workspace or set default_workspace in config")?;
let params = crate::models::UserListParams {
workspace_gid: workspace_gid.to_string(),
limit: args.limit,
};
let users = api::list_users(client, params).await?;
if users.is_empty() {
println!("No users found in workspace {workspace_gid}.");
return Ok(());
}
let format = determine_output(args.output);
match format {
OutputFormat::Table => {
if stdout().is_terminal() {
println!(
"{:<20} {:<30} {}",
"GID".bold(),
"Name".bold(),
"Email".bold()
);
println!("{}", "─".repeat(80));
}
for user in &users {
let email = user.email.as_deref().unwrap_or("N/A");
if stdout().is_terminal() {
println!("{:<20} {:<30} {}", user.gid, user.name, email);
} else {
println!("{}\t{}\t{}", user.gid, user.name, email);
}
}
if stdout().is_terminal() {
println!("\n{} users listed.", users.len());
}
}
OutputFormat::Markdown => {
println!("| GID | Name | Email |");
println!("|---|---|---|");
for user in &users {
let email = user.email.as_deref().unwrap_or("");
println!("| {} | {} | {} |", user.gid, user.name, email);
}
}
OutputFormat::Csv => {
println!("gid,name,email");
for user in &users {
let email = user.email.as_deref().unwrap_or("");
println!("{},{},{}", user.gid, user.name, email);
}
}
OutputFormat::Json => {
let json = serde_json::to_string_pretty(&users)
.context("failed to serialize users to JSON")?;
println!("{json}");
}
}
Ok(())
}
async fn show_user_command(client: &api::ApiClient, args: UserShowArgs) -> Result<()> {
let user = if args.user.eq_ignore_ascii_case(ME_SENTINEL) {
api::get_current_user(client).await?
} else {
api::get_user(client, &args.user).await?
};
let format = determine_output(args.output);
if matches!(format, OutputFormat::Json) {
let json =
serde_json::to_string_pretty(&user).context("failed to serialize user to JSON")?;
println!("{json}");
} else {
print_user_detail(&user);
}
Ok(())
}
fn print_user_detail(user: &User) {
let gid = &user.gid;
let name = &user.name;
println!("GID: {gid}");
println!("Name: {name}");
if let Some(ref email) = user.email {
println!("Email: {email}");
}
if !user.workspaces.is_empty() {
println!("\nWorkspaces:");
for workspace in &user.workspaces {
let ws_name = workspace.name.as_deref().unwrap_or(&workspace.gid);
println!(" - {} ({})", ws_name, workspace.gid);
}
}
if let Some(ref photo) = user.photo
&& let Some(ref url) = photo.image_128x128
{
println!("\nPhoto: {url}");
}
}