//! `pic apps` subcommands: `ls`, `create`, `show`, `delete`. use anyhow::Result; use picloud_shared::AppRole; use crate::client::{Client, CreateAppBody}; use crate::config; use crate::output::{KvBlock, OutputMode, Table}; pub async fn ls(mode: OutputMode) -> Result<()> { let creds = config::resolve()?; let client = Client::from_creds(&creds)?; let apps = client.apps_list().await?; let mut table = Table::new(["slug", "name", "my_role", "created_at"]); for app in apps { // The list endpoint returns App without my_role. We do a per-app // lookup only on demand; for `ls` we leave the column dashed so // the call stays cheap (one HTTP request). table.row([ app.slug.clone(), app.name.clone(), "-".to_string(), app.created_at.to_rfc3339(), ]); } table.print(mode); Ok(()) } pub async fn create(slug: &str, name: Option<&str>, description: Option<&str>) -> Result<()> { let creds = config::resolve()?; let client = Client::from_creds(&creds)?; let body = CreateAppBody { slug, name: name.unwrap_or(slug), description, }; let app = client.apps_create(&body).await?; println!("Created app {}", app.slug); Ok(()) } /// `pic apps show ` — single-app inspect using the lookup /// endpoint, which carries `my_role` for the caller (the `ls` endpoint /// doesn't). pub async fn show(ident: &str, mode: OutputMode) -> Result<()> { let creds = config::resolve()?; let client = Client::from_creds(&creds)?; let lookup = client.apps_get(ident).await?; let mut block = KvBlock::new(); block .field("id", lookup.app.id.to_string()) .field("slug", lookup.app.slug.clone()) .field("name", lookup.app.name.clone()) .field( "description", lookup.app.description.clone().unwrap_or_else(|| "-".into()), ) .field("my_role", role_label(lookup.my_role.as_ref())) .field("created_at", lookup.app.created_at.to_rfc3339()) .field("updated_at", lookup.app.updated_at.to_rfc3339()); block.print(mode); Ok(()) } /// `pic apps delete [--force]`. Without `--force` the server /// returns 409 if the app still owns scripts — surface that as a /// useful error rather than swallowing. pub async fn delete(ident: &str, force: bool) -> Result<()> { let creds = config::resolve()?; let client = Client::from_creds(&creds)?; client.apps_delete(ident, force).await?; println!("Deleted app {ident}"); Ok(()) } fn role_label(role: Option<&AppRole>) -> String { // Use the wire form so the CLI label matches what the dashboard // shows and what the membership APIs accept. match role { Some(r) => r.as_str().to_string(), None => "-".into(), } }