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}