1use anyhow::{Context, Result};
9use clap::Subcommand;
10use colored::Colorize;
11use dialoguer::Input;
12#[allow(unused_imports)]
13use raps_kernel::prompts;
14use serde::Serialize;
15
16use crate::commands::interactive;
17use crate::commands::tracked::tracked_op;
18
19use crate::output::OutputFormat;
20use raps_acc::permissions::FolderPermissionsClient;
21use raps_dm::DataManagementClient;
22#[derive(Debug, Subcommand)]
25pub enum FolderCommands {
26 List {
28 project_id: Option<String>,
30 folder_id: Option<String>,
32 #[arg(long, hide = true)]
34 hub_id: Option<String>,
35 },
36
37 Create {
39 project_id: Option<String>,
41 parent_folder_id: Option<String>,
43 #[arg(short, long)]
45 name: Option<String>,
46 #[arg(long, hide = true)]
48 hub_id: Option<String>,
49 },
50
51 Rename {
53 project_id: Option<String>,
55 folder_id: Option<String>,
57 #[arg(short, long)]
59 name: Option<String>,
60 #[arg(long, hide = true)]
62 hub_id: Option<String>,
63 },
64
65 Delete {
67 project_id: Option<String>,
69 folder_id: Option<String>,
71 #[arg(long, hide = true)]
73 hub_id: Option<String>,
74 },
75
76 Rights {
78 project_id: Option<String>,
80 folder_id: Option<String>,
82 #[arg(long, hide = true)]
84 hub_id: Option<String>,
85 },
86}
87
88impl FolderCommands {
89 pub async fn execute(
90 self,
91 client: &DataManagementClient,
92 permissions_client: &FolderPermissionsClient,
93 output_format: OutputFormat,
94 ) -> Result<()> {
95 match self {
96 FolderCommands::List {
97 project_id,
98 folder_id,
99 hub_id,
100 } => {
101 let (p_id, f_id) =
102 resolve_folder_args(client, hub_id, project_id, folder_id).await?;
103 list_folder_contents(client, &p_id, &f_id, output_format).await
104 }
105 FolderCommands::Create {
106 project_id,
107 parent_folder_id,
108 name,
109 hub_id,
110 } => {
111 let (p_id, f_id) =
112 resolve_folder_args(client, hub_id, project_id, parent_folder_id).await?;
113 create_folder(client, &p_id, &f_id, name, output_format).await
114 }
115 FolderCommands::Rename {
116 project_id,
117 folder_id,
118 name,
119 hub_id,
120 } => {
121 let (p_id, f_id) =
122 resolve_folder_args(client, hub_id, project_id, folder_id).await?;
123 rename_folder(client, &p_id, &f_id, name, output_format).await
124 }
125 FolderCommands::Delete {
126 project_id,
127 folder_id,
128 hub_id,
129 } => {
130 let (p_id, f_id) =
131 resolve_folder_args(client, hub_id, project_id, folder_id).await?;
132 if !raps_kernel::interactive::should_proceed_destructive("delete this folder") {
133 println!("Operation cancelled.");
134 return Ok(());
135 }
136 delete_folder(client, &p_id, &f_id, output_format).await
137 }
138 FolderCommands::Rights {
139 project_id,
140 folder_id,
141 hub_id,
142 } => {
143 let (p_id, f_id) =
144 resolve_folder_args(client, hub_id, project_id, folder_id).await?;
145 folder_rights(permissions_client, &p_id, &f_id, output_format).await
146 }
147 }
148 }
149}
150
151async fn resolve_folder_args(
152 client: &DataManagementClient,
153 opt_hub_id: Option<String>,
154 opt_project_id: Option<String>,
155 opt_folder_id: Option<String>,
156) -> Result<(String, String)> {
157 let hub_id = match (&opt_hub_id, &opt_project_id, &opt_folder_id) {
158 (Some(h), _, _) => h.clone(),
159 (None, Some(_), Some(_)) => String::new(), (None, _, _) => interactive::prompt_for_hub(client).await?,
161 };
162
163 let project_id = match opt_project_id {
164 Some(p) => p,
165 None => interactive::prompt_for_project(client, &hub_id).await?,
166 };
167
168 let folder_id = match opt_folder_id {
169 Some(f) => f,
170 None => interactive::prompt_for_folder(client, &hub_id, &project_id).await?,
171 };
172
173 Ok((project_id, folder_id))
174}
175
176#[derive(Serialize)]
177struct FolderItemOutput {
178 id: String,
179 name: String,
180 item_type: String,
181}
182
183async fn list_folder_contents(
184 client: &DataManagementClient,
185 project_id: &str,
186 folder_id: &str,
187 output_format: OutputFormat,
188) -> Result<()> {
189 let contents = tracked_op("Fetching folder contents", output_format, || async {
190 client
191 .list_folder_contents(project_id, folder_id)
192 .await
193 .context(format!(
194 "Failed to list folder '{}' contents. Verify folder ID and permissions",
195 folder_id
196 ))
197 })
198 .await?;
199
200 let items: Vec<FolderItemOutput> = contents
201 .iter()
202 .map(|item| {
203 let item_type = item
204 .get("type")
205 .and_then(|t| t.as_str())
206 .unwrap_or("unknown");
207 let id = item.get("id").and_then(|i| i.as_str()).unwrap_or("unknown");
208 let name = item
209 .get("attributes")
210 .and_then(|a| a.get("displayName").or(a.get("name")))
211 .and_then(|n| n.as_str())
212 .unwrap_or("Unnamed");
213
214 FolderItemOutput {
215 id: id.to_string(),
216 name: name.to_string(),
217 item_type: item_type.to_string(),
218 }
219 })
220 .collect();
221
222 if items.is_empty() {
223 match output_format {
224 OutputFormat::Table => println!("{}", "Folder is empty.".yellow()),
225 _ => {
226 output_format.write(&Vec::<FolderItemOutput>::new())?;
227 }
228 }
229 return Ok(());
230 }
231
232 match output_format {
233 OutputFormat::Table => {
234 println!("\n{}", "Folder Contents:".bold());
235 println!("{}", "-".repeat(80));
236
237 for item in &items {
238 let icon = if item.item_type == "folders" {
239 "[folder]"
240 } else {
241 "[file]"
242 };
243 let type_label = if item.item_type == "folders" {
244 "folder"
245 } else {
246 "item"
247 };
248
249 println!(" {} {} [{}]", icon, item.name.cyan(), type_label.dimmed());
250 println!(" {} {}", "ID:".dimmed(), item.id);
251 }
252
253 println!("{}", "-".repeat(80));
254 }
255 _ => {
256 output_format.write(&items)?;
257 }
258 }
259 Ok(())
260}
261
262#[derive(Serialize)]
263struct CreateFolderOutput {
264 success: bool,
265 id: String,
266 name: String,
267}
268
269async fn create_folder(
270 client: &DataManagementClient,
271 project_id: &str,
272 parent_folder_id: &str,
273 name: Option<String>,
274 output_format: OutputFormat,
275) -> Result<()> {
276 let folder_name = match name {
277 Some(n) => n,
278 None => {
279 if interactive::is_non_interactive() {
281 anyhow::bail!("Folder name is required in non-interactive mode. Use --name flag.");
282 }
283 Input::new()
284 .with_prompt("Enter folder name")
285 .interact_text()?
286 }
287 };
288
289 if output_format.supports_colors() {
290 println!("{}", "Creating folder...".dimmed());
291 }
292
293 let folder = client
294 .create_folder(project_id, parent_folder_id, &folder_name)
295 .await
296 .context(format!(
297 "Failed to create folder '{}' in project '{}'. Check parent folder permissions",
298 folder_name, project_id
299 ))?;
300
301 let output = CreateFolderOutput {
302 success: true,
303 id: folder.id.clone(),
304 name: folder.attributes.name.clone(),
305 };
306
307 match output_format {
308 OutputFormat::Table => {
309 println!("{} Folder created successfully!", "✓".green().bold());
310 println!(" {} {}", "Name:".bold(), output.name.cyan());
311 println!(" {} {}", "ID:".bold(), output.id);
312 }
313 _ => {
314 output_format.write(&output)?;
315 }
316 }
317
318 Ok(())
319}
320
321#[derive(Serialize)]
322struct RenameFolderOutput {
323 success: bool,
324 id: String,
325 name: String,
326}
327
328async fn rename_folder(
329 client: &DataManagementClient,
330 project_id: &str,
331 folder_id: &str,
332 new_name: Option<String>,
333 output_format: OutputFormat,
334) -> Result<()> {
335 let name_str = match new_name {
336 Some(n) => n,
337 None => {
338 if interactive::is_non_interactive() {
339 anyhow::bail!("Folder name is required in non-interactive mode. Use --name flag.");
340 }
341 Input::new()
342 .with_prompt("Enter new folder name")
343 .interact_text()?
344 }
345 };
346
347 if output_format.supports_colors() {
348 println!("{}", "Renaming folder...".dimmed());
349 }
350
351 let folder = client
352 .rename_folder(project_id, folder_id, &name_str)
353 .await
354 .context(format!(
355 "Failed to rename folder '{}'. Check permissions and that folder exists",
356 folder_id
357 ))?;
358
359 let output = RenameFolderOutput {
360 success: true,
361 id: folder.id.clone(),
362 name: folder.attributes.name.clone(),
363 };
364
365 match output_format {
366 OutputFormat::Table => {
367 println!("{} Folder renamed successfully!", "✓".green().bold());
368 println!(" {} {}", "Name:".bold(), output.name.cyan());
369 println!(" {} {}", "ID:".bold(), output.id);
370 }
371 _ => {
372 output_format.write(&output)?;
373 }
374 }
375
376 Ok(())
377}
378
379#[derive(Serialize)]
380struct DeleteFolderOutput {
381 success: bool,
382 folder_id: String,
383 message: String,
384}
385
386async fn delete_folder(
387 client: &DataManagementClient,
388 project_id: &str,
389 folder_id: &str,
390 output_format: OutputFormat,
391) -> Result<()> {
392 if output_format.supports_colors() {
393 println!("{}", "Deleting folder...".dimmed());
394 }
395
396 client
397 .delete_folder(project_id, folder_id)
398 .await
399 .context(format!(
400 "Failed to delete folder '{}'. Folder may not be empty or you lack permissions",
401 folder_id
402 ))?;
403
404 let output = DeleteFolderOutput {
405 success: true,
406 folder_id: folder_id.to_string(),
407 message: "Folder deleted successfully!".to_string(),
408 };
409
410 match output_format {
411 OutputFormat::Table => {
412 println!("{} {}", "✓".green().bold(), output.message);
413 }
414 _ => {
415 output_format.write(&output)?;
416 }
417 }
418 Ok(())
419}
420
421#[derive(Serialize)]
422struct FolderRightOutput {
423 subject_id: String,
424 subject_type: String,
425 actions: Vec<String>,
426 inherited_from: Option<String>,
427}
428
429async fn folder_rights(
430 client: &FolderPermissionsClient,
431 project_id: &str,
432 folder_id: &str,
433 output_format: OutputFormat,
434) -> Result<()> {
435 let permissions = tracked_op("Fetching folder permissions", output_format, || async {
436 client
437 .get_permissions(project_id, folder_id)
438 .await
439 .context(format!(
440 "Failed to get permissions for folder '{}'",
441 folder_id
442 ))
443 })
444 .await?;
445
446 let items: Vec<FolderRightOutput> = permissions
447 .iter()
448 .map(|p| FolderRightOutput {
449 subject_id: p.subject_id.clone(),
450 subject_type: p.subject_type.clone(),
451 actions: p.actions.clone(),
452 inherited_from: p.inherited_from.clone(),
453 })
454 .collect();
455
456 if items.is_empty() {
457 match output_format {
458 OutputFormat::Table => println!("{}", "No permissions found for this folder.".yellow()),
459 _ => {
460 output_format.write(&Vec::<FolderRightOutput>::new())?;
461 }
462 }
463 return Ok(());
464 }
465
466 match output_format {
467 OutputFormat::Table => {
468 println!("\n{}", "Folder Permissions:".bold());
469 println!("{}", "-".repeat(80));
470
471 for item in &items {
472 let inherited = item.inherited_from.as_deref().unwrap_or("direct");
473 println!(
474 " {} {} [{}]",
475 item.subject_type.cyan(),
476 item.subject_id,
477 inherited.dimmed()
478 );
479 println!(" {} {}", "Actions:".dimmed(), item.actions.join(", "));
480 }
481
482 println!("{}", "-".repeat(80));
483 }
484 _ => {
485 output_format.write(&items)?;
486 }
487 }
488 Ok(())
489}