ordinary-api 0.6.0-pre.10

API server for Ordinary
// Copyright (C) 2026 Ordinary Labs, LLC.
//
// SPDX-License-Identifier: AGPL-3.0-only

use crate::client::{OrdinaryApiClient, compress_zstd, ops};
use anyhow::bail;
use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD as b64};
use ordinary_config::OrdinaryConfig;
use std::path::Path;

pub async fn install(
    api_client: &OrdinaryApiClient<'_>,
    proj_path: &str,
    action_name: &str,
) -> anyhow::Result<()> {
    let config = OrdinaryConfig::get(proj_path)?;

    let correlation_id = api_client
        .correlation_id
        .unwrap_or(uuid::Uuid::new_v4())
        .to_string();

    if let Some(actions) = &config.actions {
        if let Some(action) = actions.iter().find(|t| t.name == action_name) {
            let Some(dir_path) = &action.dir_path else {
                tracing::error!("no dir_path provided for action");
                bail!("no dir_path provided for action");
            };

            let access_token = api_client
                .get_access(None, Some(correlation_id.clone()))
                .await?;

            ops::assets::write_frontend(
                api_client,
                proj_path,
                &config,
                &access_token,
                &correlation_id,
            )
            .await?;

            let access_token = api_client
                .get_access(None, Some(correlation_id.clone()))
                .await?;

            let bin_path = Path::new(proj_path)
                .join(dir_path)
                .join("target/wasm32-wasip1/release/action.wasm");

            tracing::info!("{:?}, {}", bin_path, bin_path.exists());

            let action_bytes = fs_err::read(bin_path)?;

            tracing::info!(
                "installing action '{}' to {} with idx {}...",
                action.name,
                config.domain,
                action.idx
            );

            api_client
                .client
                .put(format!("{}/v1/actions", api_client.addr))
                .query(&[("d", config.domain), ("i", action.idx.to_string())])
                .body(compress_zstd(&action_bytes[..])?)
                .header("x-correlation-id", correlation_id.as_str())
                .header("Content-Encoding", "zstd")
                .header(
                    "Authorization",
                    format!("Bearer {}", b64.encode(&access_token)),
                )
                .send()
                .await?
                .bytes()
                .await?;

            tracing::info!("successfully installed action '{}'.", action.name);
        }
    } else {
        tracing::warn!("no \"actions\" field in ordinary.json");
    }

    Ok(())
}

pub async fn install_all(
    api_client: &OrdinaryApiClient<'_>,
    proj_path: &str,
) -> anyhow::Result<()> {
    let config = OrdinaryConfig::get(proj_path)?;

    let correlation_id = api_client
        .correlation_id
        .unwrap_or(uuid::Uuid::new_v4())
        .to_string();

    if let Some(actions) = &config.actions {
        let access_token = api_client
            .get_access(None, Some(correlation_id.clone()))
            .await?;

        ops::assets::write_frontend(
            api_client,
            proj_path,
            &config,
            &access_token,
            &correlation_id,
        )
        .await?;

        for action in actions {
            let Some(dir_path) = &action.dir_path else {
                tracing::error!("no dir_path provided for action");
                bail!("no dir_path provided for action");
            };

            let access_token = api_client
                .get_access(None, Some(correlation_id.clone()))
                .await?;

            let bin_path = Path::new(proj_path)
                .join(dir_path)
                .join("target/wasm32-wasip1/release/action.wasm");

            let action_bytes = fs_err::read(bin_path)?;

            // todo: use tracing fields instead of interpolating everything
            tracing::info!(
                "installing action '{}' to {} with idx {}...",
                &action.name,
                &config.domain,
                &action.idx
            );
            api_client
                .client
                .put(format!("{}/v1/actions", api_client.addr))
                .body(compress_zstd(&action_bytes[..])?)
                .query(&[("d", config.domain.clone()), ("i", action.idx.to_string())])
                .header("x-correlation-id", correlation_id.as_str())
                .header("Content-Encoding", "zstd")
                .header(
                    "Authorization",
                    format!("Bearer {}", b64.encode(&access_token)),
                )
                .send()
                .await?
                .bytes()
                .await?;
        }

        tracing::info!("all actions successfully installed.");
    } else {
        tracing::warn!("no \"actions\" field in ordinary.json");
    }

    Ok(())
}