auths_cli/commands/
index.rs1use anyhow::{Context, Result};
2use clap::{Args, Subcommand};
3use std::path::PathBuf;
4
5use auths_id::storage::layout::{self, StorageLayoutConfig};
6use auths_index::{AttestationIndex, rebuild_attestations_from_git};
7
8#[derive(Args, Debug, Clone)]
9#[command(about = "Manage the device authorization index for fast lookups.")]
10pub struct IndexCommand {
11 #[command(subcommand)]
12 pub command: IndexSubcommand,
13}
14
15#[derive(Subcommand, Debug, Clone)]
16pub enum IndexSubcommand {
17 Rebuild,
19
20 Stats,
22
23 #[command(name = "query-device")]
25 QueryDevice {
26 #[arg(long)]
28 device_did: String,
29 },
30
31 #[command(name = "query-issuer")]
33 QueryIssuer {
34 #[arg(long)]
36 issuer_did: String,
37 },
38}
39
40pub fn handle_index(
42 cmd: IndexCommand,
43 repo_opt: Option<PathBuf>,
44 attestation_prefix_override: Option<String>,
45 attestation_blob_name_override: Option<String>,
46) -> Result<()> {
47 let repo_path = layout::resolve_repo_path(repo_opt)?;
48 let index_path = repo_path.join(".auths-index.db");
49
50 let mut config = StorageLayoutConfig::default();
51 if let Some(prefix) = attestation_prefix_override {
52 config.device_attestation_prefix = prefix.into();
53 }
54 if let Some(blob_name) = attestation_blob_name_override {
55 config.attestation_blob_name = blob_name.into();
56 }
57
58 match cmd.command {
59 IndexSubcommand::Rebuild => {
60 println!("Rebuilding device authorization index...");
61 println!(" Repository: {:?}", repo_path);
62 println!(" Index path: {:?}", index_path);
63 println!(
64 " Authorization prefix: {}",
65 config.device_attestation_prefix
66 );
67
68 let index = AttestationIndex::open_or_create(&index_path)
69 .context("Failed to open or create index")?;
70
71 let stats = rebuild_attestations_from_git(
72 &index,
73 &repo_path,
74 &config.device_attestation_prefix,
75 &config.attestation_blob_name,
76 )
77 .context("Failed to rebuild index from Git")?;
78
79 println!("\nRebuild complete:");
80 println!(" Refs scanned: {}", stats.refs_scanned);
81 println!(" Attestations indexed: {}", stats.attestations_indexed);
82 if stats.errors > 0 {
83 println!(" Errors: {}", stats.errors);
84 }
85
86 Ok(())
87 }
88
89 IndexSubcommand::Stats => {
90 if !index_path.exists() {
91 println!("No index found at {:?}", index_path);
92 println!("Run 'auths index rebuild' to create one.");
93 return Ok(());
94 }
95
96 let index =
97 AttestationIndex::open_or_create(&index_path).context("Failed to open index")?;
98
99 let stats = index.stats().context("Failed to get index stats")?;
100
101 println!("Attestation Index Statistics");
102 println!(" Index path: {:?}", index_path);
103 println!();
104 println!(" Total attestations: {}", stats.total_attestations);
105 println!(" Active attestations: {}", stats.active_attestations);
106 println!(" Revoked attestations: {}", stats.revoked_attestations);
107 println!(" With expiry: {}", stats.with_expiry);
108 println!(" Unique devices: {}", stats.unique_devices);
109 println!(" Unique issuers: {}", stats.unique_issuers);
110
111 Ok(())
112 }
113
114 IndexSubcommand::QueryDevice { device_did } => {
115 if !index_path.exists() {
116 println!("No index found at {:?}", index_path);
117 println!("Run 'auths index rebuild' to create one.");
118 return Ok(());
119 }
120
121 let index =
122 AttestationIndex::open_or_create(&index_path).context("Failed to open index")?;
123
124 let results = index
125 .query_by_device(&device_did)
126 .context("Failed to query by device")?;
127
128 if results.is_empty() {
129 println!("No attestations found for device: {}", device_did);
130 } else {
131 println!(
132 "Found {} attestation(s) for device {}:",
133 results.len(),
134 device_did
135 );
136 for att in results {
137 println!();
138 println!(" RID: {}", att.rid);
139 println!(" Issuer: {}", att.issuer_did);
140 println!(" Revoked At: {:?}", att.revoked_at);
141 if let Some(expires) = att.expires_at {
142 println!(" Expires: {}", expires);
143 }
144 println!(" Git ref: {}", att.git_ref);
145 }
146 }
147
148 Ok(())
149 }
150
151 IndexSubcommand::QueryIssuer { issuer_did } => {
152 if !index_path.exists() {
153 println!("No index found at {:?}", index_path);
154 println!("Run 'auths index rebuild' to create one.");
155 return Ok(());
156 }
157
158 let index =
159 AttestationIndex::open_or_create(&index_path).context("Failed to open index")?;
160
161 let results = index
162 .query_by_issuer(&issuer_did)
163 .context("Failed to query by issuer")?;
164
165 if results.is_empty() {
166 println!("No attestations found for issuer: {}", issuer_did);
167 } else {
168 println!(
169 "Found {} attestation(s) for issuer {}:",
170 results.len(),
171 issuer_did
172 );
173 for att in results {
174 println!();
175 println!(" RID: {}", att.rid);
176 println!(" Device: {}", att.device_did);
177 println!(" Revoked At: {:?}", att.revoked_at);
178 if let Some(expires) = att.expires_at {
179 println!(" Expires: {}", expires);
180 }
181 println!(" Git ref: {}", att.git_ref);
182 }
183 }
184
185 Ok(())
186 }
187 }
188}
189
190impl crate::commands::executable::ExecutableCommand for IndexCommand {
191 fn execute(&self, ctx: &crate::config::CliConfig) -> anyhow::Result<()> {
192 handle_index(self.clone(), ctx.repo_path.clone(), None, None)
193 }
194}