1use 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#[derive(Debug, Subcommand)]
19pub enum HubCommands {
20 List,
22
23 Info {
25 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
186fn 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}