use crate::client::{OrdinaryApiClient, compress_zstd};
use anyhow::bail;
use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD as b64};
use ordinary_config::{OrdinaryConfig, TemplateConfig, TemplateFfiVersion};
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, true)?;
let correlation_id = api_client
.correlation_id
.unwrap_or(uuid::Uuid::new_v4())
.to_string();
if let Some(templates) = &config.templates {
if let Some(template_config) = templates
.iter()
.find(|t| t.name_validated() == template_name)
{
let access_token = api_client
.get_access(None, Some(correlation_id.clone()))
.await?;
let (query, template_bytes) =
get_template_bin_and_csp(proj_path, &config, template_config)?;
let template_idx = template_config.idx_validated();
let template_name = template_config.name_validated();
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, true)?;
let correlation_id = api_client
.correlation_id
.unwrap_or(uuid::Uuid::new_v4())
.to_string();
if let Some(templates) = &config.templates {
for template_config in templates {
let access_token = api_client
.get_access(None, Some(correlation_id.clone()))
.await?;
let (query, template_bytes) =
get_template_bin_and_csp(proj_path, &config, template_config)?;
let template_idx = template_config.idx_validated();
let template_name = template_config.name_validated();
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(())
}
#[allow(clippy::type_complexity)]
fn get_template_bin_and_csp(
proj_path: &str,
config: &OrdinaryConfig,
template_config: &TemplateConfig,
) -> anyhow::Result<(Vec<(String, String)>, Vec<u8>)> {
let mut template_config = template_config.clone();
template_config.load_managed();
let template_idx = template_config.idx_validated();
let template_name = template_config.name_validated();
let (bin_path, style_path, script_path) = match template_config.ffi_validated().version {
TemplateFfiVersion::V1 => {
tracing::warn!(
"V1 template FFI will be deprecated in a future version of ordinary(d). Upgrade to V2."
);
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");
(bin_path, style_path, script_path)
}
TemplateFfiVersion::V2 => {
let build_dir = if let Some(reference) = &template_config.r#ref {
let Some(reference_dir) = Path::new(reference).parent() else {
bail!("no parent dir for reference on template '{reference}'");
};
reference_dir
} else {
Path::new(proj_path)
};
let ordinary_dir = build_dir.join(".ordinary");
let template_dir = ordinary_dir.join(template_name);
let bin_path = build_dir.join(
template_config
.bin
.as_ref()
.expect("missing 'bin' path. cannot upload"),
);
let style_path = template_dir.join("style-src.json");
let script_path = template_dir.join("script-src.json");
(bin_path, style_path, script_path)
}
};
let mut query = vec![
("d".to_string(), config.domain.clone()),
("i".to_string(), template_idx.to_string()),
];
if style_path.exists() {
let csp_style_hashes: Vec<String> =
simd_json::from_slice(fs_err::read(style_path)?.as_mut_slice())?;
if !csp_style_hashes.is_empty() {
for hash in csp_style_hashes {
query.push(("style_csp".to_string(), hash));
}
}
}
if script_path.exists() {
let csp_script_hashes: Vec<String> =
simd_json::from_slice(fs_err::read(script_path)?.as_mut_slice())?;
if !csp_script_hashes.is_empty() {
for hash in csp_script_hashes {
query.push(("script_csp".to_string(), hash));
}
}
}
let template_bytes = fs_err::read(bin_path)?;
Ok((query, template_bytes))
}