dojo_cli/commands/
download.rs1use crate::config::{get_current_environment, Config};
2use crate::utils::ApiResponseHandler;
3use clap::ArgMatches;
4use std::fs;
5use std::io::Cursor;
6use std::path::Path;
7use zip::ZipArchive;
8
9pub async fn handle_download_command(
10 matches: &ArgMatches,
11) -> Result<(), Box<dyn std::error::Error>> {
12 let config = match Config::load() {
14 Ok(config) => config,
15 Err(_) => {
16 println!("❌ No configuration found. Please run 'dojo-cli init' first.");
17 return Ok(());
18 }
19 };
20
21 let exercise = matches
22 .get_one::<String>("exercise")
23 .map(|s| s.as_str())
24 .unwrap_or("hello-world");
25
26 let client = reqwest::Client::new();
28 let base_url = config.get_api_base_url();
29 let stage = match get_current_environment() {
30 crate::config::Environment::Dev => "dev",
31 crate::config::Environment::Prod => "prod",
32 };
33
34 println!("🔍 Fetching download URL from API...");
35
36 let url = format!("{}/{}/download/{}", base_url, stage, exercise);
37
38 let response = client
39 .get(&url)
40 .bearer_auth(&config.api_token)
41 .header("X-BDJ-Curriculum-Version", config.get_curriculum_version())
42 .send()
43 .await?;
44
45 let url_response = ApiResponseHandler::handle_response(response).await?;
46
47 if let Some(error_msg) = url_response["message"].as_str() {
49 if error_msg == "Forbidden" {
50 println!("❌ Authorization Error: Access forbidden");
51 } else {
52 println!("❌ API Error: {}", error_msg);
53 }
54 return Ok(());
55 }
56
57 let download_url = url_response["url"]
59 .as_str()
60 .ok_or("No 'url' field found in API response")?;
61
62 println!("📥 Downloading scaffold for exercise: {}", exercise);
63
64 let zip_response = client.get(download_url).send().await?;
66
67 if !zip_response.status().is_success() {
68 return Err(format!("Failed to download file: {}", zip_response.status()).into());
69 }
70
71 let zip_bytes = zip_response.bytes().await?;
72
73 if !Path::new(&config.workspace_directory).exists() {
75 return Err("Workspace directory does not exist. Please initialize it first.".into());
76 }
77
78 println!(
79 "📁 Using workspace directory: {}",
80 config.workspace_directory
81 );
82
83 let tests_path = Path::new(&config.workspace_directory).join("tests");
85 if tests_path.exists() {
86 fs::remove_dir_all(&tests_path)?;
87 println!("🗑️ Removed existing 'tests' directory");
88 }
89
90 let scaffold_exercise_path = Path::new(&config.workspace_directory)
92 .join("scaffold")
93 .join(exercise);
94 fs::create_dir_all(&scaffold_exercise_path)?;
95
96 let cursor = Cursor::new(zip_bytes);
98 let mut archive = ZipArchive::new(cursor)?;
99
100 for i in 0..archive.len() {
101 let mut file = archive.by_index(i)?;
102 let file_name = file.name();
103
104 let outpath = if file_name == "stubs.rs" {
106 scaffold_exercise_path.join("stubs.rs")
107 } else if file_name == "README.md" {
108 scaffold_exercise_path.join("README.md")
109 } else if file_name.starts_with("tests/") || file_name == "tests" {
110 Path::new(&config.workspace_directory)
111 .join("bitcoin_dojo")
112 .join(file_name)
113 } else if file_name.ends_with('/') {
114 Path::new(&config.workspace_directory).join(file_name)
116 } else {
117 continue;
119 };
120
121 if file.name().ends_with('/') {
122 fs::create_dir_all(&outpath)?;
124 } else {
125 if let Some(parent) = outpath.parent() {
126 fs::create_dir_all(parent)?;
127 }
128 let mut outfile = fs::File::create(&outpath)?;
129 std::io::copy(&mut file, &mut outfile)?;
130 }
131 }
132
133 println!("✅ Successfully extracted scaffold to workspace directory");
134 println!(
135 "🎯 Next, follow the instructions in: {}/scaffold/{}/README.md",
136 config.workspace_directory, exercise
137 );
138
139 Ok(())
140}