cufflink-cli 0.11.1

CLI for the Cufflink CRUD microservice platform — deploy, init, and manage services
use crate::config::CliConfig;
use std::process::Command;

pub async fn run(env: Option<&str>) -> eyre::Result<()> {
    let config = CliConfig::load_with_env(env)?;

    if let Some(ref name) = config.env_name {
        println!("Environment: {}", name);
    }

    // Get service name from manifest
    let output = Command::new("cargo")
        .args(["run", "--", "--emit-manifest"])
        .output()?;

    if !output.status.success() {
        eyre::bail!("Failed to build service. Run from a cufflink service directory.");
    }

    let stdout = String::from_utf8(output.stdout)?;
    let manifest: serde_json::Value = serde_json::from_str(stdout.trim())?;
    let service_name = manifest["name"].as_str().unwrap_or("unknown");

    // Find the service by listing and matching name
    let client = config.http_client();
    let resp = config
        .auth_request(
            &client,
            reqwest::Method::GET,
            &format!("{}/api/services", config.api_url),
        )
        .send()
        .await?;

    let body: serde_json::Value = resp.json().await?;
    let services = body["services"].as_array();

    let service = services.and_then(|svcs| {
        svcs.iter()
            .find(|s| s["name"].as_str() == Some(service_name))
    });

    match service {
        Some(svc) => {
            let id = svc["id"].as_str().unwrap_or("?");
            println!("Service: {}", service_name);
            println!("  ID:      {}", id);
            println!("  Version: v{}", svc["current_version"]);
            println!("  Status:  {}", svc["status"].as_str().unwrap_or("?"));
            println!("  Tenant:  {}", svc["tenant_slug"].as_str().unwrap_or("?"));
            println!("  Created: {}", svc["created_at"].as_str().unwrap_or("?"));
            println!("  Updated: {}", svc["updated_at"].as_str().unwrap_or("?"));

            // Get deployment history
            let deploy_resp = config
                .auth_request(
                    &client,
                    reqwest::Method::GET,
                    &format!("{}/api/services/{}/deployments", config.api_url, id),
                )
                .send()
                .await?;

            if deploy_resp.status().is_success() {
                let deploy_body: serde_json::Value = deploy_resp.json().await?;
                if let Some(deployments) = deploy_body["deployments"].as_array() {
                    println!("\n  Deployments ({}):", deployments.len());
                    for d in deployments.iter().take(5) {
                        let commit_suffix = d["commit_hash"]
                            .as_str()
                            .map(|h| {
                                let short = if h.len() > 7 { &h[..7] } else { h };
                                format!(" ({})", short)
                            })
                            .unwrap_or_default();
                        println!(
                            "    v{}{} ({}){}",
                            d["version"],
                            d["status"].as_str().unwrap_or("?"),
                            d["deployed_at"].as_str().unwrap_or("?"),
                            commit_suffix,
                        );
                    }
                }
            }

            // Show tables from manifest
            if let Some(tables) = manifest["tables"].as_array() {
                println!("\n  Tables:");
                for t in tables {
                    let cols = t["columns"].as_array().map(|c| c.len()).unwrap_or(0);
                    println!(
                        "    {} ({} columns)",
                        t["name"].as_str().unwrap_or("?"),
                        cols
                    );
                }
            }

            // Show endpoints
            if let Some(tables) = manifest["tables"].as_array() {
                let tenant = svc["tenant_slug"].as_str().unwrap_or("default");
                println!("\n  Endpoints:");
                for t in tables {
                    let table_name = t["name"].as_str().unwrap_or("?");
                    println!("    GET    /svc/{}/{}/{}", tenant, service_name, table_name);
                    println!("    POST   /svc/{}/{}/{}", tenant, service_name, table_name);
                    println!(
                        "    GET    /svc/{}/{}/{}/:id",
                        tenant, service_name, table_name
                    );
                    println!(
                        "    PUT    /svc/{}/{}/{}/:id",
                        tenant, service_name, table_name
                    );
                    println!(
                        "    DELETE /svc/{}/{}/{}/:id",
                        tenant, service_name, table_name
                    );
                }
            }
        }
        None => {
            println!("Service '{}' not found on the platform.", service_name);
            println!("Use `cufflink deploy` to deploy it.");
        }
    }

    Ok(())
}