Skip to main content

systemprompt_cli/commands/admin/users/
mod.rs

1//! User administration command tree.
2//!
3//! [`UsersCommands`] groups the user CRUD, search, export, stats, merge, and
4//! the `bulk`, `role`, `session`, `ban`, and `webauthn` subcommand trees. On a
5//! `--database-url` invocation only the read-only commands are served; write
6//! operations require a full profile context.
7//!
8//! Copyright (c) systemprompt.io — Business Source License 1.1.
9//! See <https://systemprompt.io> for licensing details.
10
11mod apikey;
12mod ban;
13mod bulk;
14mod count;
15mod create;
16pub(crate) mod delete;
17mod export;
18mod list;
19mod merge;
20mod role;
21mod search;
22mod session;
23mod show;
24mod stats;
25mod types;
26mod update;
27mod webauthn;
28
29use crate::context::CommandContext;
30use crate::shared::{CommandOutput, render_result};
31use anyhow::{Result, bail};
32use clap::Subcommand;
33
34pub use types::*;
35
36#[derive(Debug, Subcommand)]
37pub enum UsersCommands {
38    #[command(about = "List users with pagination and filtering")]
39    List(list::ListArgs),
40
41    #[command(about = "Show detailed user information")]
42    Show(show::ShowArgs),
43
44    #[command(about = "Search users by name, email, or full name")]
45    Search(search::SearchArgs),
46
47    #[command(about = "Create a new user")]
48    Create(create::CreateArgs),
49
50    #[command(about = "Update user fields")]
51    Update(update::UpdateArgs),
52
53    #[command(about = "Delete a user")]
54    Delete(delete::DeleteArgs),
55
56    #[command(about = "Get total user count")]
57    Count(count::CountArgs),
58
59    #[command(about = "Export users to JSON")]
60    Export(export::ExportArgs),
61
62    #[command(about = "Show user statistics dashboard")]
63    Stats,
64
65    #[command(about = "Merge source user into target user")]
66    Merge(merge::MergeArgs),
67
68    #[command(subcommand, about = "Bulk operations on users")]
69    Bulk(bulk::BulkCommands),
70
71    #[command(subcommand, about = "Role management commands")]
72    Role(role::RoleCommands),
73
74    #[command(subcommand, about = "Session management commands")]
75    Session(session::SessionCommands),
76
77    #[command(subcommand, about = "IP ban management commands")]
78    Ban(ban::BanCommands),
79
80    #[command(subcommand, about = "WebAuthn credential management commands")]
81    Webauthn(webauthn::WebauthnCommands),
82
83    #[command(
84        subcommand,
85        name = "api-key",
86        about = "Personal access token (sp-live-) management"
87    )]
88    ApiKey(apikey::ApiKeyCommands),
89}
90
91pub async fn execute(cmd: UsersCommands, ctx: &CommandContext) -> Result<()> {
92    if ctx.is_database_scoped()
93        && matches!(
94            cmd,
95            UsersCommands::Create(_)
96                | UsersCommands::Update(_)
97                | UsersCommands::Delete(_)
98                | UsersCommands::Merge(_)
99                | UsersCommands::Bulk(_)
100                | UsersCommands::Webauthn(_)
101                | UsersCommands::ApiKey(_)
102        )
103    {
104        bail!("Write operations require full profile context");
105    }
106
107    match cmd {
108        UsersCommands::Bulk(cmd) => Box::pin(bulk::execute(cmd, ctx)).await,
109        UsersCommands::Role(cmd) => Box::pin(role::execute(cmd, ctx)).await,
110        UsersCommands::Session(cmd) => Box::pin(session::execute(cmd, ctx)).await,
111        UsersCommands::Ban(cmd) => Box::pin(ban::execute(cmd, ctx)).await,
112        UsersCommands::Webauthn(cmd) => Box::pin(webauthn::execute(cmd, ctx)).await,
113        other => {
114            let output = Box::pin(render_output(other, ctx)).await?;
115            render_result(&output, &ctx.cli);
116            Ok(())
117        },
118    }
119}
120
121async fn render_output(cmd: UsersCommands, ctx: &CommandContext) -> Result<CommandOutput> {
122    match cmd {
123        UsersCommands::List(args) => list::execute(args, ctx).await,
124        UsersCommands::Show(args) => show::execute(args, ctx).await,
125        UsersCommands::Search(args) => search::execute(args, ctx).await,
126        UsersCommands::Create(args) => create::execute(args, ctx).await,
127        UsersCommands::Update(args) => update::execute(args, ctx).await,
128        UsersCommands::Delete(args) => delete::execute(args, ctx).await,
129        UsersCommands::Count(args) => count::execute(args, ctx).await,
130        UsersCommands::Export(args) => export::execute(args, ctx).await,
131        UsersCommands::Stats => stats::execute(ctx).await,
132        UsersCommands::Merge(args) => merge::execute(args, ctx).await,
133        UsersCommands::ApiKey(cmd) => apikey::execute(cmd, ctx).await,
134        UsersCommands::Bulk(_)
135        | UsersCommands::Role(_)
136        | UsersCommands::Session(_)
137        | UsersCommands::Ban(_)
138        | UsersCommands::Webauthn(_) => bail!(
139            "internal: a users subgroup reached the rendering dispatch, which only serves \
140             commands that produce a single output"
141        ),
142    }
143}