A CLI-only walkthrough (E2E_TODO_REPORT.md) found two control-plane
operations with no `pic` command — every CLI-created app 404'd until it
claimed a domain, and external SSE feeds needed a raw topic-registration
call — plus several friction points. All admin APIs already existed; this
adds thin wrappers mirroring `routes`/`triggers`:
- B1: `pic apps domains {ls,add,rm}` over apps/{id}/domains.
- B2: `pic topics {ls,create,update,rm}` over apps/{id}/topics, and a
triggers help note distinguishing a pubsub trigger from topic
registration.
- F2: `pic users {ls,show,reset-password,revoke-sessions}` over the app
end-user admin surface (read + the two admin actions; create/invitations
deferred).
- F1: `apps create` and `deploy` now honor `--output json`, emitting the
created object so scripts can capture the id.
- F3: non-interactive login via `--username` + `--password-stdin` (inline
passwords still rejected, mirroring the `--token` rule).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
86 lines
2.5 KiB
Rust
86 lines
2.5 KiB
Rust
//! `pic topics {ls,create,update,rm}` — manage the pub/sub topic
|
|
//! registry that controls external SSE subscribability.
|
|
//!
|
|
//! Note the distinction from `pic triggers create-from-json --kind pubsub`:
|
|
//! a *pubsub trigger* runs a script when a message is published; a *topic*
|
|
//! (here) is the registry row that lets an outside client subscribe to a
|
|
//! topic over SSE (`/realtime/topics/{name}`). Publishing from a script
|
|
//! needs neither — only external subscribers need the topic registered.
|
|
|
|
use anyhow::Result;
|
|
|
|
use crate::client::{Client, TopicDto};
|
|
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 topics = client.topics_list(app).await?;
|
|
let mut table = Table::new([
|
|
"name",
|
|
"external_subscribable",
|
|
"auth_mode",
|
|
"created_at",
|
|
"updated_at",
|
|
]);
|
|
for t in topics {
|
|
table.row([
|
|
t.name.clone(),
|
|
t.external_subscribable.to_string(),
|
|
t.auth_mode.clone(),
|
|
t.created_at.to_rfc3339(),
|
|
t.updated_at.to_rfc3339(),
|
|
]);
|
|
}
|
|
table.print(mode);
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn create(
|
|
app: &str,
|
|
name: &str,
|
|
external: bool,
|
|
auth_mode: &str,
|
|
mode: OutputMode,
|
|
) -> Result<()> {
|
|
let creds = config::resolve()?;
|
|
let client = Client::from_creds(&creds)?;
|
|
let t = client.topics_create(app, name, external, auth_mode).await?;
|
|
print_topic(&t, mode);
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn update(
|
|
app: &str,
|
|
name: &str,
|
|
external: Option<bool>,
|
|
auth_mode: Option<&str>,
|
|
mode: OutputMode,
|
|
) -> Result<()> {
|
|
let creds = config::resolve()?;
|
|
let client = Client::from_creds(&creds)?;
|
|
let t = client.topics_update(app, name, external, auth_mode).await?;
|
|
print_topic(&t, mode);
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn rm(app: &str, name: &str) -> Result<()> {
|
|
let creds = config::resolve()?;
|
|
let client = Client::from_creds(&creds)?;
|
|
client.topics_delete(app, name).await?;
|
|
println!("Deleted topic {name}");
|
|
Ok(())
|
|
}
|
|
|
|
fn print_topic(t: &TopicDto, mode: OutputMode) {
|
|
let mut block = KvBlock::new();
|
|
block
|
|
.field("name", t.name.clone())
|
|
.field("external_subscribable", t.external_subscribable.to_string())
|
|
.field("auth_mode", t.auth_mode.clone())
|
|
.field("created_at", t.created_at.to_rfc3339())
|
|
.field("updated_at", t.updated_at.to_rfc3339());
|
|
block.print(mode);
|
|
}
|