//! `pic members ls | add | set | rm` — manage app membership (G2). //! //! Wraps `/api/v1/admin/apps/{id}/members*`. All gated on //! `AppAdmin(app)` server-side; Editors/Viewers get a 403. use anyhow::Result; use picloud_shared::AppRole; use crate::client::Client; use crate::config; use crate::output::{KvBlock, OutputMode, Table}; pub async fn ls(app: &str, mode: OutputMode) -> Result<()> { let creds = config::resolve()?; let client = Client::from_creds(&creds)?; let members = client.members_list(app).await?; let mut table = Table::new(["user_id", "username", "role", "instance_role", "active"]); for m in members { table.row([ m.user_id.to_string(), m.username, app_role_str(m.role).to_string(), format!("{:?}", m.instance_role).to_lowercase(), m.is_active.to_string(), ]); } table.print(mode); Ok(()) } pub async fn add(app: &str, user_id: &str, role: &str, mode: OutputMode) -> Result<()> { let creds = config::resolve()?; let client = Client::from_creds(&creds)?; let m = client .members_grant(app, user_id, parse_role(role)?) .await?; print_member(&m, mode); Ok(()) } pub async fn set(app: &str, user_id: &str, role: &str, mode: OutputMode) -> Result<()> { let creds = config::resolve()?; let client = Client::from_creds(&creds)?; let m = client .members_set_role(app, user_id, parse_role(role)?) .await?; print_member(&m, mode); Ok(()) } pub async fn rm(app: &str, user_id: &str) -> Result<()> { let creds = config::resolve()?; let client = Client::from_creds(&creds)?; client.members_remove(app, user_id).await?; println!("Removed {user_id} from {app}"); Ok(()) } fn print_member(m: &crate::client::AppMemberDto, mode: OutputMode) { let mut block = KvBlock::new(); block .field("user_id", m.user_id.to_string()) .field("username", m.username.clone()) .field("role", app_role_str(m.role)) .field("created_at", m.created_at.to_rfc3339()); block.print(mode); } fn parse_role(s: &str) -> Result { match s.to_ascii_lowercase().as_str() { "app_admin" | "admin" => Ok(AppRole::AppAdmin), "editor" => Ok(AppRole::Editor), "viewer" => Ok(AppRole::Viewer), other => Err(anyhow::anyhow!( "unknown role {other:?} (want app_admin | editor | viewer)" )), } } fn app_role_str(r: AppRole) -> &'static str { match r { AppRole::AppAdmin => "app_admin", AppRole::Editor => "editor", AppRole::Viewer => "viewer", } }