piecework_cli 0.2.0

Client to interact with a piecework application running on holochain
Documentation
mod error;
mod templates;
use crate::{ham::Ham, CommonOpts};
use anyhow::Result;
use colored::*;
use error::{ProgenitorError, ProgenitorResult};
use holochain_types::prelude::*;
use indicatif::{ProgressBar, ProgressStyle};
use rave_engine::types::entries::{
    AddressBook, CodeTemplateExt, ExecutableAgreement, ExecutableAgreementExt, PieceDefinition,
};
use serde_json::Value;

pub async fn init(value: CommonOpts, library_path: String) -> ProgenitorResult<()> {
    let agent = Ham::connect(value.clone()).await.map_err(|e| {
        ProgenitorError::TemplateCreationError(format!("Failed to connect to conductor: {}", e))
    })?;

    println!("\n{}", "Starting initialization...".bold());

    // Setup progress bar
    let pb = ProgressBar::new(4);
    pb.set_style(
        ProgressStyle::default_bar()
            .template("[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg}")
            .unwrap(),
    );

    // 1. Check initialization status
    pb.set_message("Checking initialization status...");
    match check_initialization_status(value.clone()).await {
        Ok(_) => println!("System not initialized, proceeding with initialization..."),
        Err(ProgenitorError::AlreadyInitialized) => {
            println!("System already initialized, checking remaining steps...")
        }
        Err(e) => return Err(e),
    }
    pb.inc(1);

    // 2. Create system templates and agreements
    pb.set_message("Creating system templates...");
    let (__system_credit_limit_computation, __system_transaction_fee_collection) =
        match create_system_templates(&agent, library_path).await {
            Ok(templates) => templates,
            Err(e) => {
                println!(
                    "Templates might already exist, attempting to retrieve existing ones... {}",
                    e
                );
                // You'll need to implement this function to fetch existing templates
                get_existing_system_templates(&agent).await.map_err(|e| {
                    ProgenitorError::TemplateCreationError(format!(
                        "Failed to create or retrieve system templates: {}",
                        e
                    ))
                })?
            }
        };
    pb.inc(1);

    // 3. Initialize pool definition
    pb.set_message("Initializing pool definition...");
    match initialize_pool_definition(
        value.clone(),
        __system_credit_limit_computation,
        __system_transaction_fee_collection,
    )
    .await
    {
        Ok(_) => println!("Pool definition initialized successfully"),
        Err(e) => {
            if e.to_string().contains("already exists") {
                println!("Pool definition already exists, continuing...");
            } else {
                return Err(ProgenitorError::TemplateCreationError(format!(
                    "Failed to initialize pool definition: {}",
                    e
                )));
            }
        }
    }
    pb.inc(1);

    // 4. Add pool pieces
    pb.set_message("Setting up pool pieces...");
    match add_pool_pieces(&agent, value.clone()).await {
        Ok(_) => println!("Pool pieces added successfully"),
        Err(e) => {
            if e.to_string().contains("already exists") {
                println!("Pool pieces already exist, continuing...");
            } else {
                return Err(ProgenitorError::TemplateCreationError(format!(
                    "Failed to add pool pieces: {}",
                    e
                )));
            }
        }
    }
    pb.finish_with_message("Initialization complete!");

    print_initialization_summary(value).await.map_err(|e| {
        ProgenitorError::TemplateCreationError(format!(
            "Failed to print initialization summary: {}",
            e
        ))
    })?;
    Ok(())
}

async fn check_initialization_status(value: CommonOpts) -> ProgenitorResult<()> {
    let library: Vec<CodeTemplateExt> =
        super::zome_call::rave::get_code_templates_lib(value.clone())
            .await
            .map_err(|e| ProgenitorError::TemplateLoadError(e.to_string()))?;

    if !library.is_empty() {
        return Err(ProgenitorError::AlreadyInitialized);
    }
    Ok(())
}

async fn create_system_templates(
    agent: &Ham,
    library_path: String,
) -> Result<(ActionHashB64, ActionHashB64)> {
    println!("\nCreating system templates...");

    // Read templates from embedded files or configuration
    let templates = templates::SystemTemplateConfig::load(library_path)?;
    let mut __system_credit_limit_computation = None;
    let mut __system_transaction_fee_collection = None;

    for template_config in templates {
        let title = template_config.template.title.clone();
        let template_id: ActionHashB64 = agent
            .zome_call(
                "alliance",
                "transactor",
                "create_code_template",
                template_config.template,
            )
            .await?;

        // Create a vector to store all agreement IDs for this template
        let mut template_agreement_ids = Vec::new();
        for agreement in template_config.agreements {
            let agreement_id: ActionHashB64 = agent
                .zome_call(
                    "alliance",
                    "transactor",
                    "create_executable_agreement",
                    ExecutableAgreement {
                        title: agreement.title.clone(),
                        code_template_id: template_id.clone().into(),
                        input_rules: agreement.input_rules,
                        roles: agreement.roles,
                        executor_rules: agreement.executor_rules,
                        one_time_run: agreement.one_time_run,
                        aggregate_execution: agreement.aggregate_execution,
                    },
                )
                .await?;
            template_agreement_ids.push(agreement_id);
        }

        // Store the first agreement ID (assuming that's what we need)
        match title.as_str() {
            "__system_credit_limit_computation" => {
                __system_credit_limit_computation = Some(
                    template_agreement_ids
                        .first()
                        .ok_or_else(|| {
                            anyhow::anyhow!("No agreements created for credit limit computation")
                        })?
                        .clone(),
                );
            }
            "__system_transaction_fee_collection" => {
                __system_transaction_fee_collection = Some(
                    template_agreement_ids
                        .first()
                        .ok_or_else(|| {
                            anyhow::anyhow!("No agreements created for transaction fee collection")
                        })?
                        .clone(),
                );
            }
            _ => {}
        }
    }

    Ok((
        __system_credit_limit_computation.unwrap(),
        __system_transaction_fee_collection.unwrap(),
    ))
}

async fn initialize_pool_definition(
    value: CommonOpts,
    __system_credit_limit_computation: ActionHashB64,
    __system_transaction_fee_collection: ActionHashB64,
) -> Result<()> {
    println!("\nInitializing pool definition...");

    let agent = super::dashboard::whoami(value.clone()).await?;
    let effective_start_date = Timestamp::now().as_micros() as u64;
    super::zome_call::pool::initialize_pool_definition(
        value,
        Some(effective_start_date),
        Some(30),
        Some(__system_transaction_fee_collection),
        Some(__system_credit_limit_computation),
        Some(Some(AddressBook {
            pub_key: agent,
            address_book_data: Value::Null,
        })),
        Some(vec![]),
    )
    .await?;

    Ok(())
}

async fn add_pool_pieces(agent: &Ham, value: CommonOpts) -> Result<()> {
    println!("\nChecking pool pieces...");

    let current_pieces: Vec<PieceDefinition> = agent
        .zome_call("alliance", "transactor", "get_pool_pieces_details", ())
        .await?;

    if current_pieces.iter().any(|p| p.piece_symbol == "Z") {
        let pieces = vec![
            PieceDefinition {
                piece_symbol: "HF".to_string(),
                piece_name: "Holofuel".to_string(),
                piece_description: "a mutual credit accounting unit".to_string(),
            },
            PieceDefinition {
                piece_symbol: "BW".to_string(),
                piece_name: "Bandwidth".to_string(),
                piece_description: "your bandwidth logs".to_string(),
            },
            PieceDefinition {
                piece_symbol: "REQ".to_string(),
                piece_name: "Request".to_string(),
                piece_description: "your request logs".to_string(),
            },
        ];

        for piece in pieces {
            super::zome_call::pool::add_pool_pieces(value.clone(), piece).await?;
        }
    }

    Ok(())
}

async fn print_initialization_summary(value: CommonOpts) -> Result<()> {
    println!("\n{}", "Initialization Summary:".bold().underline());

    // Display templates
    super::zome_call::rave::get_code_templates_lib(value.clone()).await?;

    // Display pool pieces
    super::zome_call::pool::get_pool_pieces_details(value).await?;

    Ok(())
}

// New helper function to retrieve existing system templates
async fn get_existing_system_templates(agent: &Ham) -> Result<(ActionHashB64, ActionHashB64)> {
    // Implement logic to fetch existing system templates
    let templates: Vec<CodeTemplateExt> = agent
        .zome_call("alliance", "transactor", "get_code_templates_lib", ())
        .await?;

    let mut credit_limit_computation = None;
    let mut transaction_fee_collection = None;

    for template in templates {
        match template.title.as_str() {
            "__system_credit_limit_computation" => {
                // Get the first agreement ID for this template
                let agreements: Vec<ExecutableAgreementExt> = agent
                    .zome_call(
                        "alliance",
                        "transactor",
                        "get_executable_agreements_for_code_template",
                        (),
                    )
                    .await?;

                credit_limit_computation = Some(agreements[0].id.clone())
            }
            "__system_transaction_fee_collection" => {
                let agreements: Vec<ExecutableAgreementExt> = agent
                    .zome_call(
                        "alliance",
                        "transactor",
                        "get_executable_agreements_for_code_template",
                        (),
                    )
                    .await?;

                transaction_fee_collection = Some(agreements[0].id.clone())
            }
            _ => continue,
        }
    }

    match (credit_limit_computation, transaction_fee_collection) {
        (Some(clc), Some(tfc)) => Ok((clc, tfc)),
        _ => Err(anyhow::anyhow!("Could not find existing system templates")),
    }
}