//! `pic users {ls,show,reset-password,revoke-sessions}` — admin view of //! an app's *end-users* (people who registered via `users::create` in a //! script), distinct from `pic admins` (instance control-plane accounts) //! and app membership/collaborators. //! //! Scoped to read + the two admin actions the E2E report needed. Create, //! delete, and invitations are deferred to a follow-up. use anyhow::Result; use crate::client::Client; use crate::config; use crate::output::{KvBlock, OutputMode, Table}; pub async fn ls(app: &str, limit: u32, mode: OutputMode) -> Result<()> { let creds = config::resolve()?; let client = Client::from_creds(&creds)?; let users = client.app_users_list(app, limit).await?; let mut table = Table::new(["id", "email", "display_name", "last_login_at", "created_at"]); for u in users { table.row([ u.id.to_string(), u.email.clone(), u.display_name.clone().unwrap_or_else(|| "-".into()), u.last_login_at .map(|t| t.to_rfc3339()) .unwrap_or_else(|| "-".into()), u.created_at.to_rfc3339(), ]); } table.print(mode); Ok(()) } pub async fn show(app: &str, user_id: &str, mode: OutputMode) -> Result<()> { let creds = config::resolve()?; let client = Client::from_creds(&creds)?; let u = client.app_user_get(app, user_id).await?; let mut block = KvBlock::new(); block .field("id", u.id.to_string()) .field("email", u.email.clone()) .field( "display_name", u.display_name.clone().unwrap_or_else(|| "-".into()), ) .field( "email_verified_at", u.email_verified_at .map(|t| t.to_rfc3339()) .unwrap_or_else(|| "-".into()), ) .field( "last_login_at", u.last_login_at .map(|t| t.to_rfc3339()) .unwrap_or_else(|| "-".into()), ) .field( "roles", if u.roles.is_empty() { "-".into() } else { u.roles.join(",") }, ) .field("created_at", u.created_at.to_rfc3339()) .field("updated_at", u.updated_at.to_rfc3339()); block.print(mode); Ok(()) } /// Mint a one-shot password-reset token for an end-user. The token is /// returned exactly once — the admin pastes it into a manual reset link. pub async fn reset_password(app: &str, user_id: &str, mode: OutputMode) -> Result<()> { let creds = config::resolve()?; let client = Client::from_creds(&creds)?; let resp = client.app_user_reset_password(app, user_id).await?; let mut block = KvBlock::new(); block .field("token", resp.token.clone()) .field("expires_in_seconds", resp.expires_in_seconds.to_string()); block.print(mode); Ok(()) } pub async fn revoke_sessions(app: &str, user_id: &str, mode: OutputMode) -> Result<()> { let creds = config::resolve()?; let client = Client::from_creds(&creds)?; let resp = client.app_user_revoke_sessions(app, user_id).await?; let mut block = KvBlock::new(); block.field("revoked", resp.revoked.to_string()); block.print(mode); Ok(()) }