Skip to main content

auths_cli/commands/
account.rs

1use anyhow::Result;
2use clap::{Parser, Subcommand};
3use serde::Deserialize;
4
5use super::executable::ExecutableCommand;
6use crate::config::CliConfig;
7
8/// Manage your registry account and view usage.
9#[derive(Parser, Debug, Clone)]
10pub struct AccountCommand {
11    #[clap(subcommand)]
12    pub subcommand: AccountSubcommand,
13}
14
15#[derive(Subcommand, Debug, Clone)]
16pub enum AccountSubcommand {
17    /// Show account status and rate limits
18    Status {
19        /// Registry URL to query
20        #[arg(long, default_value = "https://registry.auths.dev")]
21        registry_url: String,
22    },
23    /// Show API usage history
24    Usage {
25        /// Registry URL to query
26        #[arg(long, default_value = "https://registry.auths.dev")]
27        registry_url: String,
28        /// Number of days to show
29        #[arg(long, default_value = "7")]
30        days: u32,
31    },
32}
33
34#[derive(Debug, Deserialize)]
35struct AccountStatusResponse {
36    did: String,
37    tier: String,
38    daily_limit: i32,
39    daily_used: i32,
40    expires_at: Option<String>,
41}
42
43#[derive(Debug, Deserialize)]
44struct UsageEntry {
45    date: String,
46    request_count: i32,
47}
48
49fn handle_status(registry_url: &str) -> Result<()> {
50    let url = registry_url.trim_end_matches('/');
51
52    println!("Fetching account status...");
53
54    let client = reqwest::blocking::Client::new();
55    let resp = client
56        .get(format!("{url}/v1/account/status"))
57        .send()
58        .map_err(|e| anyhow::anyhow!("Failed to fetch account status: {e}"))?;
59
60    if !resp.status().is_success() {
61        return Err(anyhow::anyhow!("Registry returned {}", resp.status()));
62    }
63
64    let status: AccountStatusResponse = resp
65        .json()
66        .map_err(|e| anyhow::anyhow!("Failed to parse response: {e}"))?;
67
68    println!("\nAccount Status:");
69    println!("  DID:         {}", status.did);
70    println!("  Tier:        {}", status.tier);
71    println!("  Daily Limit: {}", status.daily_limit);
72    println!("  Daily Used:  {}", status.daily_used);
73    if let Some(expires) = status.expires_at {
74        println!("  Expires:     {expires}");
75    }
76
77    Ok(())
78}
79
80fn handle_usage(registry_url: &str, days: u32) -> Result<()> {
81    let url = registry_url.trim_end_matches('/');
82
83    println!("Fetching usage history ({days} days)...");
84
85    let client = reqwest::blocking::Client::new();
86    let resp = client
87        .get(format!("{url}/v1/account/usage?days={days}"))
88        .send()
89        .map_err(|e| anyhow::anyhow!("Failed to fetch usage: {e}"))?;
90
91    if !resp.status().is_success() {
92        return Err(anyhow::anyhow!("Registry returned {}", resp.status()));
93    }
94
95    let entries: Vec<UsageEntry> = resp
96        .json()
97        .map_err(|e| anyhow::anyhow!("Failed to parse response: {e}"))?;
98
99    if entries.is_empty() {
100        println!("\nNo usage data found.");
101        return Ok(());
102    }
103
104    println!("\nUsage History:");
105    for entry in &entries {
106        println!("  {} -- {} requests", entry.date, entry.request_count);
107    }
108
109    Ok(())
110}
111
112impl ExecutableCommand for AccountCommand {
113    fn execute(&self, _ctx: &CliConfig) -> Result<()> {
114        match &self.subcommand {
115            AccountSubcommand::Status { registry_url } => handle_status(registry_url),
116            AccountSubcommand::Usage { registry_url, days } => handle_usage(registry_url, *days),
117        }
118    }
119}