1use anyhow::{Context, Result};
9use clap::Subcommand;
10use colored::Colorize;
11#[allow(unused_imports)]
12use raps_kernel::prompts;
13use serde::Serialize;
14
15use crate::commands::interactive;
16use crate::commands::tracked::tracked_op;
17use crate::output::OutputFormat;
18use raps_dm::DataManagementClient;
19#[derive(Debug, Subcommand)]
22pub enum ProjectCommands {
23 List {
25 hub_id: Option<String>,
27 },
28
29 Info {
31 hub_id: Option<String>,
33 project_id: Option<String>,
35 },
36}
37
38impl ProjectCommands {
39 pub async fn execute(
40 self,
41 client: &DataManagementClient,
42 output_format: OutputFormat,
43 ) -> Result<()> {
44 match self {
45 ProjectCommands::List { hub_id } => list_projects(client, hub_id, output_format).await,
46 ProjectCommands::Info { hub_id, project_id } => {
47 project_info(client, &hub_id, &project_id, output_format).await
48 }
49 }
50 }
51}
52
53#[derive(Serialize)]
54struct ProjectListOutput {
55 id: String,
56 name: String,
57 project_type: String,
58 scopes: Option<Vec<String>>,
59}
60
61async fn list_projects(
62 client: &DataManagementClient,
63 hub_id: Option<String>,
64 output_format: OutputFormat,
65) -> Result<()> {
66 let hub = match hub_id {
68 Some(h) => h,
69 None => interactive::prompt_for_hub(client).await?,
70 };
71
72 let projects = tracked_op("Fetching projects", output_format, || async {
73 client.list_projects(&hub).await.context(format!(
74 "Failed to list projects in hub '{}'. Verify the hub ID and your permissions",
75 hub
76 ))
77 })
78 .await?;
79
80 let project_outputs: Vec<ProjectListOutput> = projects
81 .iter()
82 .map(|p| ProjectListOutput {
83 id: p.id.clone(),
84 name: p.attributes.name.clone(),
85 project_type: p.project_type.clone(),
86 scopes: p.attributes.scopes.clone(),
87 })
88 .collect();
89
90 if project_outputs.is_empty() {
91 match output_format {
92 OutputFormat::Table => println!("{}", "No projects found in this hub.".yellow()),
93 _ => {
94 output_format.write(&Vec::<ProjectListOutput>::new())?;
95 }
96 }
97 return Ok(());
98 }
99
100 match output_format {
101 OutputFormat::Table => {
102 println!("\n{}", "Projects:".bold());
103 println!("{}", "-".repeat(80));
104
105 for project in &project_outputs {
106 println!(" {} {}", "-".cyan(), project.name.bold());
107 println!(" {} {}", "ID:".dimmed(), project.id);
108 if let Some(ref scopes) = project.scopes {
109 println!(" {} {:?}", "Scopes:".dimmed(), scopes);
110 }
111 }
112
113 println!("{}", "-".repeat(80));
114 println!(
115 "\n{}",
116 "Use 'raps folder list <hub-id> <project-id>' to see folders".dimmed()
117 );
118 }
119 _ => {
120 output_format.write(&project_outputs)?;
121 }
122 }
123 Ok(())
124}
125
126#[derive(Serialize)]
127struct ProjectInfoOutput {
128 id: String,
129 name: String,
130 project_type: String,
131 scopes: Option<Vec<String>>,
132 top_folders: Vec<FolderOutput>,
133}
134
135#[derive(Serialize)]
136struct FolderOutput {
137 id: String,
138 name: String,
139 display_name: Option<String>,
140}
141
142async fn project_info(
143 client: &DataManagementClient,
144 opt_hub_id: &Option<String>,
145 opt_project_id: &Option<String>,
146 output_format: OutputFormat,
147) -> Result<()> {
148 let hub_id = match opt_hub_id {
149 Some(h) => h.clone(),
150 None => interactive::prompt_for_hub(client).await?,
151 };
152
153 let project_id = match opt_project_id {
154 Some(p) => p.clone(),
155 None => interactive::prompt_for_project(client, &hub_id).await?,
156 };
157 let (project, folders) = tracked_op("Fetching project details", output_format, || async {
158 let project = client
159 .get_project(&hub_id, &project_id)
160 .await
161 .context(format!(
162 "Failed to get project '{}'. Verify the project ID and your permissions",
163 project_id
164 ))?;
165 let folders = client
166 .get_top_folders(&hub_id, &project_id)
167 .await
168 .context(format!(
169 "Failed to get top folders for project '{}'. You may lack folder-level permissions",
170 project_id
171 ))?;
172 Ok((project, folders))
173 })
174 .await?;
175
176 let folder_outputs: Vec<FolderOutput> = folders
177 .iter()
178 .map(|f| FolderOutput {
179 id: f.id.clone(),
180 name: f.attributes.name.clone(),
181 display_name: f.attributes.display_name.clone(),
182 })
183 .collect();
184
185 let output = ProjectInfoOutput {
186 id: project.id.clone(),
187 name: project.attributes.name.clone(),
188 project_type: project.project_type.clone(),
189 scopes: project.attributes.scopes.clone(),
190 top_folders: folder_outputs,
191 };
192
193 match output_format {
194 OutputFormat::Table => {
195 println!("\n{}", "Project Details".bold());
196 println!("{}", "-".repeat(60));
197 println!(" {} {}", "Name:".bold(), output.name.cyan());
198 println!(" {} {}", "ID:".bold(), output.id);
199 println!(" {} {}", "Type:".bold(), output.project_type);
200
201 if let Some(ref scopes) = output.scopes {
202 println!(" {} {:?}", "Scopes:".bold(), scopes);
203 }
204
205 println!("\n{}", "Top Folders:".bold());
206 for folder in &output.top_folders {
207 println!(
208 " {} {} ({})",
209 "[folder]".dimmed(),
210 folder.display_name.as_ref().unwrap_or(&folder.name),
211 folder.id.dimmed()
212 );
213 }
214
215 println!("{}", "-".repeat(60));
216 }
217 _ => {
218 output_format.write(&output)?;
219 }
220 }
221 Ok(())
222}