|
1 | 1 | use crate::config; |
2 | 2 | use indicatif::{ProgressBar, ProgressStyle}; |
3 | | -use serde::Deserialize; |
| 3 | +use serde::{Deserialize, Serialize}; |
4 | 4 | use serde_json::json; |
5 | 5 | use std::path::Path; |
6 | 6 |
|
7 | | -#[derive(Deserialize)] |
| 7 | +#[derive(Deserialize, Serialize)] |
8 | 8 | struct Dataset { |
9 | 9 | id: String, |
10 | 10 | label: String, |
11 | 11 | table_name: String, |
| 12 | + created_at: String, |
| 13 | + updated_at: String, |
| 14 | +} |
| 15 | + |
| 16 | +#[derive(Deserialize)] |
| 17 | +struct ListResponse { |
| 18 | + datasets: Vec<Dataset>, |
| 19 | + count: u64, |
| 20 | + has_more: bool, |
| 21 | +} |
| 22 | + |
| 23 | +#[derive(Deserialize, Serialize)] |
| 24 | +struct Column { |
| 25 | + name: String, |
| 26 | + data_type: String, |
| 27 | + nullable: bool, |
| 28 | +} |
| 29 | + |
| 30 | +#[derive(Deserialize, Serialize)] |
| 31 | +struct DatasetDetail { |
| 32 | + id: String, |
| 33 | + label: String, |
| 34 | + schema_name: String, |
| 35 | + table_name: String, |
| 36 | + source_type: String, |
| 37 | + created_at: String, |
| 38 | + updated_at: String, |
| 39 | + columns: Vec<Column>, |
12 | 40 | } |
13 | 41 |
|
14 | 42 | struct FileType { |
@@ -293,3 +321,152 @@ pub fn create( |
293 | 321 | println!("label: {}", dataset.label); |
294 | 322 | println!("table_name: {}", dataset.table_name); |
295 | 323 | } |
| 324 | + |
| 325 | +pub fn list(workspace_id: &str, limit: Option<u32>, offset: Option<u32>, format: &str) { |
| 326 | + let profile_config = match config::load("default") { |
| 327 | + Ok(c) => c, |
| 328 | + Err(e) => { |
| 329 | + eprintln!("{e}"); |
| 330 | + std::process::exit(1); |
| 331 | + } |
| 332 | + }; |
| 333 | + |
| 334 | + let api_key = match &profile_config.api_key { |
| 335 | + Some(key) if key != "PLACEHOLDER" => key.clone(), |
| 336 | + _ => { |
| 337 | + eprintln!("error: not authenticated. Run 'hotdata auth login' to log in."); |
| 338 | + std::process::exit(1); |
| 339 | + } |
| 340 | + }; |
| 341 | + |
| 342 | + let mut url = format!("{}/datasets", profile_config.api_url); |
| 343 | + let mut params = vec![]; |
| 344 | + if let Some(l) = limit { params.push(format!("limit={l}")); } |
| 345 | + if let Some(o) = offset { params.push(format!("offset={o}")); } |
| 346 | + if !params.is_empty() { url = format!("{url}?{}", params.join("&")); } |
| 347 | + |
| 348 | + let client = reqwest::blocking::Client::new(); |
| 349 | + let resp = match client |
| 350 | + .get(&url) |
| 351 | + .header("Authorization", format!("Bearer {api_key}")) |
| 352 | + .header("X-Workspace-Id", workspace_id) |
| 353 | + .send() |
| 354 | + { |
| 355 | + Ok(r) => r, |
| 356 | + Err(e) => { |
| 357 | + eprintln!("error connecting to API: {e}"); |
| 358 | + std::process::exit(1); |
| 359 | + } |
| 360 | + }; |
| 361 | + |
| 362 | + if !resp.status().is_success() { |
| 363 | + use crossterm::style::Stylize; |
| 364 | + eprintln!("{}", api_error(resp.text().unwrap_or_default()).red()); |
| 365 | + std::process::exit(1); |
| 366 | + } |
| 367 | + |
| 368 | + let body: ListResponse = match resp.json() { |
| 369 | + Ok(v) => v, |
| 370 | + Err(e) => { |
| 371 | + eprintln!("error parsing response: {e}"); |
| 372 | + std::process::exit(1); |
| 373 | + } |
| 374 | + }; |
| 375 | + |
| 376 | + match format { |
| 377 | + "json" => println!("{}", serde_json::to_string_pretty(&body.datasets).unwrap()), |
| 378 | + "yaml" => print!("{}", serde_yaml::to_string(&body.datasets).unwrap()), |
| 379 | + "table" => { |
| 380 | + let mut table = crate::util::make_table(); |
| 381 | + table.set_header(["ID", "LABEL", "TABLE NAME", "CREATED AT"]); |
| 382 | + table.column_mut(1).unwrap().set_constraint( |
| 383 | + comfy_table::ColumnConstraint::UpperBoundary(comfy_table::Width::Fixed(30)) |
| 384 | + ); |
| 385 | + for d in &body.datasets { |
| 386 | + let created_at = d.created_at.split('.').next().unwrap_or(&d.created_at).replace('T', " "); |
| 387 | + table.add_row([&d.id, &d.label, &d.table_name, &created_at]); |
| 388 | + } |
| 389 | + println!("{table}"); |
| 390 | + if body.has_more { |
| 391 | + let next = offset.unwrap_or(0) + body.count as u32; |
| 392 | + use crossterm::style::Stylize; |
| 393 | + eprintln!("{}", format!("showing {} results — use --offset {next} for more", body.count).dark_grey()); |
| 394 | + } |
| 395 | + } |
| 396 | + _ => unreachable!(), |
| 397 | + } |
| 398 | +} |
| 399 | + |
| 400 | +pub fn get(dataset_id: &str, workspace_id: &str, format: &str) { |
| 401 | + let profile_config = match config::load("default") { |
| 402 | + Ok(c) => c, |
| 403 | + Err(e) => { |
| 404 | + eprintln!("{e}"); |
| 405 | + std::process::exit(1); |
| 406 | + } |
| 407 | + }; |
| 408 | + |
| 409 | + let api_key = match &profile_config.api_key { |
| 410 | + Some(key) if key != "PLACEHOLDER" => key.clone(), |
| 411 | + _ => { |
| 412 | + eprintln!("error: not authenticated. Run 'hotdata auth login' to log in."); |
| 413 | + std::process::exit(1); |
| 414 | + } |
| 415 | + }; |
| 416 | + |
| 417 | + let url = format!("{}/datasets/{dataset_id}", profile_config.api_url); |
| 418 | + let client = reqwest::blocking::Client::new(); |
| 419 | + |
| 420 | + let resp = match client |
| 421 | + .get(&url) |
| 422 | + .header("Authorization", format!("Bearer {api_key}")) |
| 423 | + .header("X-Workspace-Id", workspace_id) |
| 424 | + .send() |
| 425 | + { |
| 426 | + Ok(r) => r, |
| 427 | + Err(e) => { |
| 428 | + eprintln!("error connecting to API: {e}"); |
| 429 | + std::process::exit(1); |
| 430 | + } |
| 431 | + }; |
| 432 | + |
| 433 | + if !resp.status().is_success() { |
| 434 | + use crossterm::style::Stylize; |
| 435 | + eprintln!("{}", api_error(resp.text().unwrap_or_default()).red()); |
| 436 | + std::process::exit(1); |
| 437 | + } |
| 438 | + |
| 439 | + let d: DatasetDetail = match resp.json() { |
| 440 | + Ok(v) => v, |
| 441 | + Err(e) => { |
| 442 | + eprintln!("error parsing response: {e}"); |
| 443 | + std::process::exit(1); |
| 444 | + } |
| 445 | + }; |
| 446 | + |
| 447 | + match format { |
| 448 | + "json" => println!("{}", serde_json::to_string_pretty(&d).unwrap()), |
| 449 | + "yaml" => print!("{}", serde_yaml::to_string(&d).unwrap()), |
| 450 | + "table" => { |
| 451 | + let created_at = d.created_at.split('.').next().unwrap_or(&d.created_at).replace('T', " "); |
| 452 | + let updated_at = d.updated_at.split('.').next().unwrap_or(&d.updated_at).replace('T', " "); |
| 453 | + println!("id: {}", d.id); |
| 454 | + println!("label: {}", d.label); |
| 455 | + println!("schema: {}", d.schema_name); |
| 456 | + println!("table: {}", d.table_name); |
| 457 | + println!("source_type: {}", d.source_type); |
| 458 | + println!("created_at: {created_at}"); |
| 459 | + println!("updated_at: {updated_at}"); |
| 460 | + if !d.columns.is_empty() { |
| 461 | + println!(); |
| 462 | + let mut table = crate::util::make_table(); |
| 463 | + table.set_header(["COLUMN", "DATA TYPE", "NULLABLE"]); |
| 464 | + for col in &d.columns { |
| 465 | + table.add_row([&col.name, &col.data_type, &col.nullable.to_string()]); |
| 466 | + } |
| 467 | + println!("{table}"); |
| 468 | + } |
| 469 | + } |
| 470 | + _ => unreachable!(), |
| 471 | + } |
| 472 | +} |
0 commit comments