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 ItemCommands {
20 Info {
22 project_id: String,
24 item_id: String,
26 },
27
28 Versions {
30 project_id: String,
32 item_id: String,
34 },
35
36 #[command(name = "create-from-oss")]
38 CreateFromOss {
39 project_id: String,
41 folder_id: String,
43 #[arg(short, long)]
45 name: String,
46 #[arg(long)]
48 object_id: String,
49 },
50
51 Delete {
53 project_id: String,
55 item_id: String,
57 },
58
59 Rename {
61 project_id: String,
63 item_id: String,
65 #[arg(short, long)]
67 name: String,
68 },
69}
70
71impl ItemCommands {
72 pub async fn execute(
73 self,
74 client: &DataManagementClient,
75 output_format: OutputFormat,
76 ) -> Result<()> {
77 match self {
78 ItemCommands::Info {
79 project_id,
80 item_id,
81 } => item_info(client, &project_id, &item_id, output_format).await,
82 ItemCommands::Versions {
83 project_id,
84 item_id,
85 } => list_versions(client, &project_id, &item_id, output_format).await,
86 ItemCommands::CreateFromOss {
87 project_id,
88 folder_id,
89 name,
90 object_id,
91 } => {
92 create_from_oss(
93 client,
94 &project_id,
95 &folder_id,
96 &name,
97 &object_id,
98 output_format,
99 )
100 .await
101 }
102 ItemCommands::Delete {
103 project_id,
104 item_id,
105 } => delete_item(client, &project_id, &item_id, output_format).await,
106 ItemCommands::Rename {
107 project_id,
108 item_id,
109 name,
110 } => rename_item(client, &project_id, &item_id, &name, output_format).await,
111 }
112 }
113}
114
115#[derive(Serialize)]
116struct ItemInfoOutput {
117 id: String,
118 name: String,
119 item_type: String,
120 create_time: Option<String>,
121 modified_time: Option<String>,
122 extension_type: Option<String>,
123 extension_version: Option<String>,
124}
125
126async fn item_info(
127 client: &DataManagementClient,
128 project_id: &str,
129 item_id: &str,
130 output_format: OutputFormat,
131) -> Result<()> {
132 let item = tracked_op("Fetching item details", output_format, || {
133 client.get_item(project_id, item_id)
134 })
135 .await?;
136
137 let extension_type = item
138 .attributes
139 .extension
140 .as_ref()
141 .and_then(|e| e.extension_type.clone());
142 let extension_version = item
143 .attributes
144 .extension
145 .as_ref()
146 .and_then(|e| e.version.clone());
147
148 let output = ItemInfoOutput {
149 id: item.id.clone(),
150 name: item.attributes.display_name.clone(),
151 item_type: item.item_type.clone(),
152 create_time: item.attributes.create_time.clone(),
153 modified_time: item.attributes.last_modified_time.clone(),
154 extension_type,
155 extension_version,
156 };
157
158 match output_format {
159 OutputFormat::Table => {
160 println!("\n{}", "Item Details".bold());
161 println!("{}", "-".repeat(60));
162 println!(" {} {}", "Name:".bold(), output.name.cyan());
163 println!(" {} {}", "ID:".bold(), output.id);
164 println!(" {} {}", "Type:".bold(), output.item_type);
165
166 if let Some(ref create_time) = output.create_time {
167 println!(" {} {}", "Created:".bold(), create_time);
168 }
169
170 if let Some(ref modified_time) = output.modified_time {
171 println!(" {} {}", "Modified:".bold(), modified_time);
172 }
173
174 if let Some(ref ext_type) = output.extension_type {
175 println!(" {} {}", "Extension:".bold(), ext_type);
176 }
177 if let Some(version) = output.extension_version {
178 println!(" {} {}", "Ext Version:".bold(), version);
179 }
180
181 println!("{}", "-".repeat(60));
182 println!(
183 "\n{}",
184 "Use 'raps item versions' to see version history".dimmed()
185 );
186 }
187 _ => {
188 output_format.write(&output)?;
189 }
190 }
191 Ok(())
192}
193
194#[derive(Serialize)]
195struct VersionOutput {
196 version_number: Option<i32>,
197 name: String,
198 size: Option<u64>,
199 size_human: Option<String>,
200 create_time: Option<String>,
201}
202
203async fn list_versions(
204 client: &DataManagementClient,
205 project_id: &str,
206 item_id: &str,
207 output_format: OutputFormat,
208) -> Result<()> {
209 let versions = tracked_op("Fetching item versions", output_format, || {
210 client.get_item_versions(project_id, item_id)
211 })
212 .await?;
213
214 let version_outputs: Vec<VersionOutput> = versions
215 .iter()
216 .map(|v| {
217 let name = v
218 .attributes
219 .display_name
220 .as_ref()
221 .or(Some(&v.attributes.name))
222 .cloned()
223 .unwrap_or_default();
224 VersionOutput {
225 version_number: v.attributes.version_number,
226 name,
227 size: v.attributes.storage_size.map(|s| s as u64),
228 size_human: v.attributes.storage_size.map(|s| format_size(s as u64)),
229 create_time: v.attributes.create_time.clone(),
230 }
231 })
232 .collect();
233
234 if version_outputs.is_empty() {
235 match output_format {
236 OutputFormat::Table => println!("{}", "No versions found.".yellow()),
237 _ => {
238 output_format.write(&Vec::<VersionOutput>::new())?;
239 }
240 }
241 return Ok(());
242 }
243
244 match output_format {
245 OutputFormat::Table => {
246 println!("\n{}", "Item Versions:".bold());
247 println!("{}", "-".repeat(80));
248 println!(
249 "{:<6} {:<40} {:>12} {}",
250 "Ver".bold(),
251 "Name".bold(),
252 "Size".bold(),
253 "Created".bold()
254 );
255 println!("{}", "-".repeat(80));
256
257 for version in &version_outputs {
258 let ver_num = version
259 .version_number
260 .map(|n| n.to_string())
261 .unwrap_or_else(|| "-".to_string());
262 let name = truncate_str(&version.name, 40);
263 let size = version.size_human.as_deref().unwrap_or("-");
264 let created = version.create_time.as_deref().unwrap_or("-");
265
266 println!(
267 "{:<6} {:<40} {:>12} {}",
268 ver_num.cyan(),
269 name,
270 size,
271 created.dimmed()
272 );
273 }
274
275 println!("{}", "-".repeat(80));
276 }
277 _ => {
278 output_format.write(&version_outputs)?;
279 }
280 }
281 Ok(())
282}
283
284fn format_size(bytes: u64) -> String {
286 const KB: u64 = 1024;
287 const MB: u64 = KB * 1024;
288 const GB: u64 = MB * 1024;
289
290 if bytes >= GB {
291 format!("{:.2} GB", bytes as f64 / GB as f64)
292 } else if bytes >= MB {
293 format!("{:.2} MB", bytes as f64 / MB as f64)
294 } else if bytes >= KB {
295 format!("{:.2} KB", bytes as f64 / KB as f64)
296 } else {
297 format!("{} B", bytes)
298 }
299}
300
301fn truncate_str(s: &str, max_len: usize) -> String {
303 if s.len() <= max_len {
304 s.to_string()
305 } else {
306 format!("{}...", &s[..max_len - 3])
307 }
308}
309
310#[derive(Serialize)]
311struct CreateFromOssOutput {
312 success: bool,
313 item_id: String,
314 name: String,
315 message: String,
316}
317
318async fn create_from_oss(
319 client: &DataManagementClient,
320 project_id: &str,
321 folder_id: &str,
322 name: &str,
323 object_id: &str,
324 output_format: OutputFormat,
325) -> Result<()> {
326 if output_format.supports_colors() {
327 println!("{}", "Creating item from OSS object...".dimmed());
328 println!(" {} {}", "Project:".bold(), project_id);
329 println!(" {} {}", "Folder:".bold(), folder_id);
330 println!(" {} {}", "Name:".bold(), name.cyan());
331 println!(" {} {}", "Object ID:".bold(), object_id.dimmed());
332 }
333
334 let item = client
336 .create_item_from_storage(project_id, folder_id, name, object_id)
337 .await?;
338
339 let output = CreateFromOssOutput {
340 success: true,
341 item_id: item.id.clone(),
342 name: item.attributes.display_name.clone(),
343 message: format!("Item '{}' created successfully from OSS object", name),
344 };
345
346 match output_format {
347 OutputFormat::Table => {
348 println!("\n{} {}", "✓".green().bold(), output.message);
349 println!(" {} {}", "Item ID:".bold(), output.item_id);
350 println!(" {} {}", "Name:".bold(), output.name.cyan());
351 }
352 _ => {
353 output_format.write(&output)?;
354 }
355 }
356
357 Ok(())
358}
359
360async fn delete_item(
361 client: &DataManagementClient,
362 project_id: &str,
363 item_id: &str,
364 output_format: OutputFormat,
365) -> Result<()> {
366 if output_format.supports_colors() {
367 println!("{}", "Deleting item...".dimmed());
368 }
369
370 client.delete_item(project_id, item_id).await?;
371
372 match output_format {
373 OutputFormat::Table => {
374 println!("\n{} Item deleted successfully!", "✓".green().bold());
375 println!(" {} {}", "Item ID:".bold(), item_id.cyan());
376 }
377 _ => {
378 output_format.write(&serde_json::json!({
379 "id": item_id,
380 "deleted": true
381 }))?;
382 }
383 }
384
385 Ok(())
386}
387
388#[derive(Serialize)]
389struct RenameItemOutput {
390 id: String,
391 name: String,
392 renamed: bool,
393}
394
395async fn rename_item(
396 client: &DataManagementClient,
397 project_id: &str,
398 item_id: &str,
399 new_name: &str,
400 output_format: OutputFormat,
401) -> Result<()> {
402 if output_format.supports_colors() {
403 println!("{}", "Renaming item...".dimmed());
404 }
405
406 let item = client.rename_item(project_id, item_id, new_name).await?;
407
408 let output = RenameItemOutput {
409 id: item.id.clone(),
410 name: item.attributes.display_name.clone(),
411 renamed: true,
412 };
413
414 match output_format {
415 OutputFormat::Table => {
416 println!("\n{} Item renamed successfully!", "✓".green().bold());
417 println!(" {} {}", "Item ID:".bold(), output.id.cyan());
418 println!(" {} {}", "Name:".bold(), output.name.cyan());
419 }
420 _ => {
421 output_format.write(&output)?;
422 }
423 }
424
425 Ok(())
426}