use std::path::Path;
use anyhow::Result;
use reqwest::blocking::Client;
use serde::Deserialize;
use crate::http;
use crate::kv::bulk;
use crate::settings::global_user::GlobalUser;
use crate::settings::toml::Target;
use crate::sites::{add_namespace, sync, AssetManifest};
use crate::terminal::message::{Message, StdOut};
use crate::upload;
#[derive(Debug, Deserialize)]
struct Preview {
id: String,
}
impl From<ApiPreview> for Preview {
fn from(api_preview: ApiPreview) -> Preview {
Preview {
id: api_preview.preview_id,
}
}
}
#[derive(Debug, Deserialize)]
struct ApiPreview {
pub preview_id: String,
}
#[derive(Debug, Deserialize)]
struct V4ApiResponse {
pub result: ApiPreview,
}
const SITES_UNAUTH_PREVIEW_ERR: &str =
"Unauthenticated preview does not work for previewing Workers Sites; you need to \
authenticate to upload your site contents.";
pub fn upload(
target: &mut Target,
user: Option<&GlobalUser>,
sites_preview: bool,
verbose: bool,
) -> Result<String> {
let preview = match &user {
Some(user) => {
log::info!("GlobalUser set, running with authentication");
let missing_fields = validate(target);
if missing_fields.is_empty() {
let client = http::legacy_auth_client(user);
if let Some(site_config) = target.site.clone() {
let site_namespace = add_namespace(user, target, true)?;
let path = Path::new(&site_config.bucket);
let (to_upload, to_delete, asset_manifest) =
sync(target, user, &site_namespace.id, path)?;
if verbose {
StdOut::info("Uploading updated files...");
}
bulk::put(target, user, &site_namespace.id, to_upload, &None)?;
let preview = authenticated_upload(&client, target, Some(asset_manifest))?;
if !to_delete.is_empty() {
if verbose {
StdOut::info("Deleting stale files...");
}
bulk::delete(target, user, &site_namespace.id, to_delete, &None)?;
}
preview
} else {
authenticated_upload(&client, target, None)?
}
} else {
StdOut::warn(&format!(
"Your configuration file is missing the following fields: {:?}",
missing_fields
));
StdOut::warn("Falling back to unauthenticated preview.");
if sites_preview {
anyhow::bail!(SITES_UNAUTH_PREVIEW_ERR)
}
unauthenticated_upload(target)?
}
}
None => {
StdOut::info("Running preview without authentication.");
if sites_preview {
anyhow::bail!(SITES_UNAUTH_PREVIEW_ERR)
}
unauthenticated_upload(target)?
}
};
Ok(preview.id)
}
fn validate(target: &Target) -> Vec<&str> {
let mut missing_fields = Vec::new();
if target.account_id.maybe_load().is_none() {
missing_fields.push("account_id")
};
if target.name.is_empty() {
missing_fields.push("name")
};
for kv in &target.kv_namespaces {
if kv.binding.is_empty() {
missing_fields.push("kv-namespace binding")
}
if kv.id.is_empty() {
missing_fields.push("kv-namespace id")
}
}
missing_fields
}
fn authenticated_upload(
client: &Client,
target: &Target,
asset_manifest: Option<AssetManifest>,
) -> Result<Preview> {
let create_address = format!(
"https://api.cloudflare.com/client/v4/accounts/{}/workers/scripts/{}/preview",
target.account_id.load()?,
target.name
);
log::info!("address: {}", create_address);
let script_upload_form = upload::form::build(target, asset_manifest, None)?;
let res = client
.post(&create_address)
.multipart(script_upload_form)
.send()?;
let status = res.status();
let text = res.text()?;
if !status.is_success() {
anyhow::bail!(crate::format_api_errors(text))
}
log::info!("Response from preview: {:#?}", text);
let response: V4ApiResponse =
serde_json::from_str(&text).expect("could not create a script on cloudflareworkers.com");
Ok(Preview::from(response.result))
}
fn unauthenticated_upload(target: &Target) -> Result<Preview> {
let create_address = "https://cloudflareworkers.com/script";
log::info!("address: {}", create_address);
let mut target = target.clone();
if !target.kv_namespaces.is_empty() {
StdOut::warn(
"KV Namespaces are not supported in preview without setting API credentials and account_id",
);
target.kv_namespaces = Vec::new();
}
if target.site.is_some() {
StdOut::warn(
"Sites are not supported in preview without setting API credentials and account_id",
);
target.site = None;
}
let script_upload_form = upload::form::build(&target, None, None)?;
let client = http::client();
let res = client
.post(create_address)
.multipart(script_upload_form)
.send()?;
let status = res.status();
let text = res.text()?;
if !status.is_success() {
anyhow::bail!(crate::format_api_errors(text))
}
log::info!("Response from preview: {:#?}", text);
let preview: Preview =
serde_json::from_str(&text).expect("could not create a script on cloudflareworkers.com");
Ok(preview)
}