Skip to main content

raps_cli/commands/
hub.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2024-2025 Dmytro Yemelianov
3
4//! Hub management commands
5//!
6//! Commands for listing and viewing hubs (requires 3-legged auth).
7
8use anyhow::Result;
9use clap::Subcommand;
10use colored::Colorize;
11use serde::Serialize;
12
13use crate::commands::tracked::tracked_op;
14use crate::output::OutputFormat;
15use raps_dm::DataManagementClient;
16// use raps_kernel::output::OutputFormat;
17
18#[derive(Debug, Subcommand)]
19pub enum HubCommands {
20    /// List all accessible hubs
21    List,
22
23    /// Get hub details
24    Info {
25        /// Hub ID
26        hub_id: String,
27    },
28}
29
30impl HubCommands {
31    pub async fn execute(
32        self,
33        client: &DataManagementClient,
34        output_format: OutputFormat,
35    ) -> Result<()> {
36        match self {
37            HubCommands::List => list_hubs(client, output_format).await,
38            HubCommands::Info { hub_id } => hub_info(client, &hub_id, output_format).await,
39        }
40    }
41}
42
43#[derive(Serialize)]
44struct HubListOutput {
45    id: String,
46    name: String,
47    hub_type: String,
48    extension_type: Option<String>,
49    region: Option<String>,
50}
51
52async fn list_hubs(client: &DataManagementClient, output_format: OutputFormat) -> Result<()> {
53    let hubs = tracked_op(
54        "Fetching hubs (requires 3-legged auth)",
55        output_format,
56        || client.list_hubs(),
57    )
58    .await?;
59
60    let hub_outputs: Vec<HubListOutput> = hubs
61        .iter()
62        .map(|h| {
63            let extension_type = h
64                .attributes
65                .extension
66                .as_ref()
67                .and_then(|e| e.extension_type.as_ref())
68                .map(|t| extract_hub_type(t));
69            HubListOutput {
70                id: h.id.clone(),
71                name: h.attributes.name.clone(),
72                hub_type: h.hub_type.clone(),
73                extension_type,
74                region: h.attributes.region.clone(),
75            }
76        })
77        .collect();
78
79    if hub_outputs.is_empty() {
80        match output_format {
81            OutputFormat::Table => println!("{}", "No hubs found.".yellow()),
82            _ => {
83                output_format.write(&Vec::<HubListOutput>::new())?;
84            }
85        }
86        return Ok(());
87    }
88
89    match output_format {
90        OutputFormat::Table => {
91            println!("\n{}", "Hubs:".bold());
92            println!("{}", "-".repeat(80));
93            println!(
94                "{:<45} {:<15} {}",
95                "Hub Name".bold(),
96                "Type".bold(),
97                "Region".bold()
98            );
99            println!("{}", "-".repeat(80));
100
101            for hub in &hub_outputs {
102                let hub_type = hub.extension_type.as_deref().unwrap_or("Unknown");
103                let region = hub.region.as_deref().unwrap_or("US");
104
105                println!(
106                    "{:<45} {:<15} {}",
107                    hub.name.cyan(),
108                    hub_type,
109                    region.dimmed()
110                );
111                println!("  {} {}", "ID:".dimmed(), hub.id);
112            }
113
114            println!("{}", "-".repeat(80));
115        }
116        _ => {
117            output_format.write(&hub_outputs)?;
118        }
119    }
120    Ok(())
121}
122
123#[derive(Serialize)]
124struct HubInfoOutput {
125    id: String,
126    name: String,
127    hub_type: String,
128    region: Option<String>,
129    extension_type: Option<String>,
130}
131
132async fn hub_info(
133    client: &DataManagementClient,
134    hub_id: &str,
135    output_format: OutputFormat,
136) -> Result<()> {
137    let hub = tracked_op("Fetching hub details", output_format, || {
138        client.get_hub(hub_id)
139    })
140    .await?;
141
142    let extension_type = hub
143        .attributes
144        .extension
145        .as_ref()
146        .and_then(|e| e.extension_type.as_ref())
147        .map(|t| extract_hub_type(t));
148
149    let output = HubInfoOutput {
150        id: hub.id.clone(),
151        name: hub.attributes.name.clone(),
152        hub_type: hub.hub_type.clone(),
153        region: hub.attributes.region.clone(),
154        extension_type,
155    };
156
157    match output_format {
158        OutputFormat::Table => {
159            println!("\n{}", "Hub Details".bold());
160            println!("{}", "-".repeat(60));
161            println!("  {} {}", "Name:".bold(), output.name.cyan());
162            println!("  {} {}", "ID:".bold(), output.id);
163            println!("  {} {}", "Type:".bold(), output.hub_type);
164
165            if let Some(ref region) = output.region {
166                println!("  {} {}", "Region:".bold(), region);
167            }
168
169            if let Some(ref ext_type) = output.extension_type {
170                println!("  {} {}", "Extension:".bold(), ext_type);
171            }
172
173            println!("{}", "-".repeat(60));
174            println!(
175                "\n{}",
176                "Use 'raps project list <hub-id>' to see projects".dimmed()
177            );
178        }
179        _ => {
180            output_format.write(&output)?;
181        }
182    }
183    Ok(())
184}
185
186/// Extract friendly hub type from extension type
187fn extract_hub_type(ext_type: &str) -> String {
188    if ext_type.contains("bim360") {
189        "BIM 360".to_string()
190    } else if ext_type.contains("accproject") {
191        "ACC".to_string()
192    } else if ext_type.contains("a360") {
193        "A360".to_string()
194    } else if ext_type.contains("fusion") {
195        "Fusion".to_string()
196    } else {
197        ext_type
198            .split(':')
199            .next_back()
200            .unwrap_or("Unknown")
201            .to_string()
202    }
203}