use reqwest::RequestBuilder;
use serde::Deserialize;
use spacetimedb_lib::name::{is_address, DnsLookupResponse, RegisterTldResult, ReverseDNSResponse};
use spacetimedb_lib::Identity;
use std::path::Path;
use std::process::exit;
use crate::config::{Config, IdentityConfig};
pub async fn database_address(config: &Config, database: &str) -> Result<String, anyhow::Error> {
if is_address(database) {
return Ok(database.to_string());
}
match spacetime_dns(config, database).await? {
DnsLookupResponse::Success { domain: _, address } => Ok(address),
DnsLookupResponse::Failure { domain } => Err(anyhow::anyhow!("The dns resolution of `{}` failed.", domain)),
}
}
pub async fn spacetime_dns(config: &Config, domain: &str) -> Result<DnsLookupResponse, anyhow::Error> {
let client = reqwest::Client::new();
let url = format!("{}/database/dns/{}", config.get_host_url(), domain);
let res = client.get(url).send().await?.error_for_status()?;
let bytes = res.bytes().await.unwrap();
Ok(serde_json::from_slice(&bytes[..]).unwrap())
}
pub async fn spacetime_register_tld(
config: &mut Config,
tld: &str,
identity: Option<&String>,
) -> Result<RegisterTldResult, anyhow::Error> {
let auth_header = get_auth_header_only(config, false, identity).await.unwrap();
let builder =
reqwest::Client::new().get(format!("{}/database/register_tld?tld={}", config.get_host_url(), tld).as_str());
let builder = add_auth_header_opt(builder, &Some(auth_header));
let res = builder.send().await?.error_for_status()?;
let bytes = res.bytes().await.unwrap();
Ok(serde_json::from_slice(&bytes[..]).unwrap())
}
pub async fn spacetime_reverse_dns(config: &Config, address: &str) -> Result<ReverseDNSResponse, anyhow::Error> {
let client = reqwest::Client::new();
let url = format!("{}/database/reverse_dns/{}", config.get_host_url(), address);
let res = client.get(url).send().await?.error_for_status()?;
let bytes = res.bytes().await.unwrap();
Ok(serde_json::from_slice(&bytes[..]).unwrap())
}
#[derive(Deserialize)]
pub struct IdentityTokenJson {
pub identity: String,
pub token: String,
}
pub enum InitDefaultResultType {
Existing,
SavedNew,
}
pub struct InitDefaultResult {
pub identity_config: IdentityConfig,
pub result_type: InitDefaultResultType,
}
pub async fn init_default(config: &mut Config, nickname: Option<String>) -> Result<InitDefaultResult, anyhow::Error> {
if config.name_exists(nickname.as_ref().unwrap_or(&"".to_string())) {
return Err(anyhow::anyhow!("A default identity already exists."));
}
let client = reqwest::Client::new();
let builder = client.post(format!("{}/identity", config.get_host_url()));
if let Some(identity_config) = config.get_default_identity_config() {
return Ok(InitDefaultResult {
identity_config: identity_config.clone(),
result_type: InitDefaultResultType::Existing,
});
}
let res = builder.send().await?;
let res = res.error_for_status()?;
let body = res.bytes().await?;
let body = String::from_utf8(body.to_vec())?;
let identity_token: IdentityTokenJson = serde_json::from_str(&body)?;
let identity = identity_token.identity.clone();
let identity_config = IdentityConfig {
identity: identity_token.identity,
token: identity_token.token,
nickname: nickname.clone(),
};
config.identity_configs_mut().push(identity_config.clone());
if config.default_identity().is_none() {
config.set_default_identity(identity);
}
config.save();
Ok(InitDefaultResult {
identity_config,
result_type: InitDefaultResultType::SavedNew,
})
}
pub async fn select_identity_config(
config: &mut Config,
identity_or_name: Option<&str>,
) -> Result<IdentityConfig, anyhow::Error> {
let resolve_identity_to_identity_config = |ident: &str| -> Result<IdentityConfig, anyhow::Error> {
config
.get_identity_config_by_identity(ident)
.map(Clone::clone)
.ok_or_else(|| anyhow::anyhow!("Missing identity credentials for identity: {}", ident))
};
if let Some(identity_or_name) = identity_or_name {
if is_hex_identity(identity_or_name) {
resolve_identity_to_identity_config(identity_or_name)
} else {
match config.resolve_name_to_identity(Some(identity_or_name)) {
None => Err(anyhow::anyhow!("No such identity for name: {}", identity_or_name,)),
Some(identity) => resolve_identity_to_identity_config(&identity),
}
}
} else {
Ok(init_default(config, None).await?.identity_config)
}
}
pub fn add_auth_header_opt(mut builder: RequestBuilder, auth_header: &Option<String>) -> RequestBuilder {
if let Some(auth_header) = auth_header {
builder = builder.header("Authorization", auth_header);
}
builder
}
pub async fn get_auth_header_only(
config: &mut Config,
anon_identity: bool,
identity_or_name: Option<&String>,
) -> Option<String> {
get_auth_header(config, anon_identity, identity_or_name.map(|x| x.as_str()))
.await
.map(|(ah, _)| ah)
}
pub async fn get_auth_header(
config: &mut Config,
anon_identity: bool,
identity_or_name: Option<&str>,
) -> Option<(String, Identity)> {
if !anon_identity {
let identity_config = match select_identity_config(config, identity_or_name).await {
Ok(ic) => ic,
Err(err) => {
println!("{}", err);
exit(1);
}
};
let mut auth_header = String::new();
auth_header.push_str(format!("Basic {}", base64::encode(format!("token:{}", identity_config.token))).as_str());
match Identity::from_hex(identity_config.identity.clone()) {
Ok(identity) => Some((auth_header, identity)),
Err(_) => {
println!(
"Local config contains invalid malformed identity: {}",
identity_config.identity
);
exit(1)
}
}
} else {
None
}
}
pub fn is_hex_identity(ident: &str) -> bool {
ident.len() == 64 && ident.chars().all(|c| c.is_ascii_hexdigit())
}
pub fn print_identity_config(ident: &IdentityConfig) {
println!(" IDENTITY {}", ident.identity);
println!(
" NAME {}",
match &ident.nickname {
None => "",
Some(name) => name.as_str(),
}
);
}
pub const VALID_PROTOCOLS: [&str; 2] = ["http", "https"];
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum ModuleLanguage {
Csharp,
Rust,
}
impl clap::ValueEnum for ModuleLanguage {
fn value_variants<'a>() -> &'a [Self] {
&[Self::Csharp, Self::Rust]
}
fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
match self {
Self::Csharp => Some(clap::builder::PossibleValue::new("csharp").aliases(["c#", "cs", "C#", "CSharp"])),
Self::Rust => Some(clap::builder::PossibleValue::new("rust").aliases(["rs", "Rust"])),
}
}
}
pub fn detect_module_language(path_to_project: &Path) -> ModuleLanguage {
if path_to_project.join("Cargo.toml").exists() {
ModuleLanguage::Rust
} else {
ModuleLanguage::Csharp
}
}