suture-cli 1.0.0

A patch-based version control system with semantic merge for structured files
use crate::RemoteAction;

pub(crate) async fn cmd_remote(
    action: &crate::RemoteAction,
) -> Result<(), Box<dyn std::error::Error>> {
    let mut repo = suture_core::repository::Repository::open(std::path::Path::new("."))?;
    match action {
        RemoteAction::Add { name, url } => {
            repo.add_remote(name, url)?;
            println!("Remote '{}' added -> {}", name, url);
        }
        RemoteAction::List => {
            let remotes = repo.list_remotes()?;
            if remotes.is_empty() {
                println!("No remotes configured.");
            } else {
                for (name, url) in &remotes {
                    println!("{}\t{}", name, url);
                }
            }
        }
        RemoteAction::Remove { name } => {
            repo.remove_remote(name)?;
            println!("Remote '{}' removed", name);
        }
        RemoteAction::Login { name } => {
            let remote_url = repo.get_remote_url(name)?;

            eprintln!("Authenticating with {}...", remote_url);

            let client = reqwest::Client::new();
            let response = client
                .post(format!("{}/auth/token", remote_url))
                .send()
                .await?;

            if !response.status().is_success() {
                let status = response.status();
                let body = response.text().await.unwrap_or_default();
                return Err(format!("login failed (HTTP {}): {}", status, body).into());
            }

            let body: serde_json::Value = response.json().await?;
            let token = body["token"]
                .as_str()
                .ok_or("invalid response from server")?;

            repo.set_config(&format!("remote.{}.token", name), token)?;

            eprintln!("Authentication successful. Token stored in config.");
        }
        RemoteAction::Mirror {
            url,
            repo: upstream_repo,
            name: local_name,
        } => {
            let local_repo_name = local_name.as_deref().unwrap_or(upstream_repo);

            #[derive(serde::Serialize)]
            struct MirrorSetupReq {
                repo_name: String,
                upstream_url: String,
                upstream_repo: String,
            }
            #[derive(serde::Deserialize)]
            struct MirrorSetupResp {
                success: bool,
                error: Option<String>,
                mirror_id: Option<i64>,
            }
            #[derive(serde::Serialize)]
            struct MirrorSyncReq {
                mirror_id: i64,
            }
            #[derive(serde::Deserialize)]
            struct MirrorSyncResp {
                success: bool,
                error: Option<String>,
                patches_synced: u64,
                branches_synced: u64,
            }

            let client = reqwest::Client::new();

            let setup_body = MirrorSetupReq {
                repo_name: local_repo_name.to_string(),
                upstream_url: url.clone(),
                upstream_repo: upstream_repo.clone(),
            };

            let hub_url = repo
                .get_remote_url("origin")
                .unwrap_or_else(|_| url.clone());
            let setup_resp = client
                .post(format!("{}/mirror/setup", hub_url))
                .json(&setup_body)
                .send()
                .await?;

            let setup_result: MirrorSetupResp = setup_resp.json().await?;
            if !setup_result.success {
                return Err(setup_result
                    .error
                    .unwrap_or_else(|| "mirror setup failed".to_string())
                    .into());
            }

            let mirror_id = setup_result.mirror_id.ok_or("no mirror id returned")?;
            println!("Mirror registered (id: {mirror_id}), syncing...");

            let sync_resp = client
                .post(format!("{}/mirror/sync", hub_url))
                .json(&MirrorSyncReq { mirror_id })
                .send()
                .await?;

            let sync_result: MirrorSyncResp = sync_resp.json().await?;
            if !sync_result.success {
                return Err(sync_result
                    .error
                    .unwrap_or_else(|| "mirror sync failed".to_string())
                    .into());
            }

            println!(
                "Mirror sync complete: {} patch(es), {} branch(es)",
                sync_result.patches_synced, sync_result.branches_synced
            );
        }
    }
    Ok(())
}