ordinary-api 0.6.0-pre.13

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

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

pub async fn upload(
    api_client: &OrdinaryApiClient<'_>,
    proj_path: &str,
    template_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(templates) = &config.templates {
        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?;

        if let Some(template) = templates.iter().find(|t| t.name == template_name) {
            let access_token = api_client
                .get_access(None, Some(correlation_id.clone()))
                .await?;

            let bin_path = Path::new(proj_path)
                .join(".ordinary/gen/server")
                .join(&template.name)
                .join("target/wasm32-wasip1/release/template.wasm");

            let hash_path = Path::new(proj_path)
                .join(".ordinary")
                .join("gen")
                .join("hashes")
                .join(&template.name);

            let style_path = hash_path.join("style-src.json");
            let script_path = hash_path.join("script-src.json");

            let mut query = vec![
                ("d", config.domain.clone()),
                ("i", template.idx.to_string()),
            ];

            if style_path.exists() {
                let csp_style_hashes: Vec<String> =
                    serde_json::from_str(&read_to_string(style_path)?)?;

                if !csp_style_hashes.is_empty() {
                    for hash in csp_style_hashes {
                        query.push(("style_csp", hash));
                    }
                }
            }

            if script_path.exists() {
                let csp_script_hashes: Vec<String> =
                    serde_json::from_str(&read_to_string(script_path)?)?;

                if !csp_script_hashes.is_empty() {
                    for hash in csp_script_hashes {
                        query.push(("script_csp", hash));
                    }
                }
            }

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

            tracing::info!(
                "uploading template '{}' to {} with idx {}...",
                template.name,
                config.domain,
                template.idx
            );
            api_client
                .client
                .put(format!("{}/v1/templates", api_client.addr))
                .body(compress_zstd(&template_bytes[..])?)
                .query(&query)
                .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 uploaded template '{}'.", template.name);
        }
    } else {
        tracing::warn!("no \"templates\" field in ordinary.json");
    }

    Ok(())
}

pub async fn upload_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(templates) = &config.templates {
        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 template in templates {
            let access_token = api_client
                .get_access(None, Some(correlation_id.clone()))
                .await?;

            let bin_path = Path::new(proj_path)
                .join(".ordinary/gen/server")
                .join(&template.name)
                .join("target/wasm32-wasip1/release/template.wasm");

            let hash_path = Path::new(proj_path)
                .join(".ordinary")
                .join("gen")
                .join("hashes")
                .join(&template.name);

            let style_path = hash_path.join("style-src.json");
            let script_path = hash_path.join("script-src.json");

            let mut query = vec![
                ("d", config.domain.clone()),
                ("i", template.idx.to_string()),
            ];

            if style_path.exists() {
                let csp_style_hashes: Vec<String> =
                    serde_json::from_str(&read_to_string(style_path)?)?;

                if !csp_style_hashes.is_empty() {
                    for hash in csp_style_hashes {
                        query.push(("style_csp", hash));
                    }
                }
            }

            if script_path.exists() {
                let csp_script_hashes: Vec<String> =
                    serde_json::from_str(&read_to_string(script_path)?)?;

                if !csp_script_hashes.is_empty() {
                    for hash in csp_script_hashes {
                        query.push(("script_csp", hash));
                    }
                }
            }

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

            tracing::info!(
                "uploading template '{}' to {} with idx {}...",
                template.name,
                config.domain,
                template.idx
            );
            api_client
                .client
                .put(format!("{}/v1/templates", api_client.addr))
                .body(compress_zstd(&template_bytes[..])?)
                .query(&query)
                .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 templates successfully uploaded.");
    } else {
        tracing::warn!("no \"templates\" field in ordinary.json");
    }

    Ok(())
}