Fix domains mapping missing
This commit is contained in:
@@ -257,11 +257,23 @@ fn config_encryption_to_ui(value: Option<&Value>) -> Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn config_website_to_ui(value: Option<&Value>) -> Value {
|
fn config_website_to_ui(value: Option<&Value>) -> Value {
|
||||||
match value {
|
let parsed = match value {
|
||||||
Some(Value::Object(map)) => Value::Object(map.clone()),
|
Some(Value::Object(map)) => Value::Object(map.clone()),
|
||||||
Some(Value::String(s)) => serde_json::from_str(s).unwrap_or(Value::Null),
|
Some(Value::String(s)) => serde_json::from_str(s).unwrap_or(Value::Null),
|
||||||
_ => Value::Null,
|
_ => Value::Null,
|
||||||
}
|
};
|
||||||
|
|
||||||
|
let Some(map) = parsed.as_object() else {
|
||||||
|
return Value::Null;
|
||||||
|
};
|
||||||
|
|
||||||
|
json!({
|
||||||
|
"index_document": map
|
||||||
|
.get("index_document")
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.unwrap_or("index.html"),
|
||||||
|
"error_document": map.get("error_document").and_then(Value::as_str),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bucket_access_descriptor(
|
fn bucket_access_descriptor(
|
||||||
|
|||||||
@@ -5,11 +5,33 @@ use std::path::PathBuf;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
struct DomainData {
|
struct DomainData {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
mappings: HashMap<String, String>,
|
mappings: HashMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
enum DomainDataFile {
|
||||||
|
Wrapped(DomainData),
|
||||||
|
Flat(HashMap<String, String>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DomainDataFile {
|
||||||
|
fn into_domain_data(self) -> DomainData {
|
||||||
|
match self {
|
||||||
|
Self::Wrapped(data) => data,
|
||||||
|
Self::Flat(mappings) => DomainData {
|
||||||
|
mappings: mappings
|
||||||
|
.into_iter()
|
||||||
|
.map(|(domain, bucket)| (normalize_domain(&domain), bucket))
|
||||||
|
.collect(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct WebsiteDomainStore {
|
pub struct WebsiteDomainStore {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
data: Arc<RwLock<DomainData>>,
|
data: Arc<RwLock<DomainData>>,
|
||||||
@@ -24,7 +46,8 @@ impl WebsiteDomainStore {
|
|||||||
let data = if path.exists() {
|
let data = if path.exists() {
|
||||||
std::fs::read_to_string(&path)
|
std::fs::read_to_string(&path)
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|s| serde_json::from_str(&s).ok())
|
.and_then(|s| serde_json::from_str::<DomainDataFile>(&s).ok())
|
||||||
|
.map(DomainDataFile::into_domain_data)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
} else {
|
} else {
|
||||||
DomainData::default()
|
DomainData::default()
|
||||||
@@ -40,7 +63,7 @@ impl WebsiteDomainStore {
|
|||||||
if let Some(parent) = self.path.parent() {
|
if let Some(parent) = self.path.parent() {
|
||||||
let _ = std::fs::create_dir_all(parent);
|
let _ = std::fs::create_dir_all(parent);
|
||||||
}
|
}
|
||||||
if let Ok(json) = serde_json::to_string_pretty(&*data) {
|
if let Ok(json) = serde_json::to_string_pretty(&data.mappings) {
|
||||||
let _ = std::fs::write(&self.path, json);
|
let _ = std::fs::write(&self.path, json);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,19 +83,22 @@ impl WebsiteDomainStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_bucket(&self, domain: &str) -> Option<String> {
|
pub fn get_bucket(&self, domain: &str) -> Option<String> {
|
||||||
self.data.read().mappings.get(domain).cloned()
|
let domain = normalize_domain(domain);
|
||||||
|
self.data.read().mappings.get(&domain).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_mapping(&self, domain: &str, bucket: &str) {
|
pub fn set_mapping(&self, domain: &str, bucket: &str) {
|
||||||
|
let domain = normalize_domain(domain);
|
||||||
self.data
|
self.data
|
||||||
.write()
|
.write()
|
||||||
.mappings
|
.mappings
|
||||||
.insert(domain.to_string(), bucket.to_string());
|
.insert(domain, bucket.to_string());
|
||||||
self.save();
|
self.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_mapping(&self, domain: &str) -> bool {
|
pub fn delete_mapping(&self, domain: &str) -> bool {
|
||||||
let removed = self.data.write().mappings.remove(domain).is_some();
|
let domain = normalize_domain(domain);
|
||||||
|
let removed = self.data.write().mappings.remove(&domain).is_some();
|
||||||
if removed {
|
if removed {
|
||||||
self.save();
|
self.save();
|
||||||
}
|
}
|
||||||
@@ -105,3 +131,67 @@ pub fn is_valid_domain(domain: &str) -> bool {
|
|||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::WebsiteDomainStore;
|
||||||
|
use serde_json::json;
|
||||||
|
use tempfile::tempdir;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn loads_legacy_flat_mapping_file() {
|
||||||
|
let tmp = tempdir().expect("tempdir");
|
||||||
|
let config_dir = tmp.path().join(".myfsio.sys").join("config");
|
||||||
|
std::fs::create_dir_all(&config_dir).expect("create config dir");
|
||||||
|
std::fs::write(
|
||||||
|
config_dir.join("website_domains.json"),
|
||||||
|
r#"{"Example.COM":"site-bucket"}"#,
|
||||||
|
)
|
||||||
|
.expect("write config");
|
||||||
|
|
||||||
|
let store = WebsiteDomainStore::new(tmp.path());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
store.get_bucket("example.com"),
|
||||||
|
Some("site-bucket".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn loads_wrapped_mapping_file() {
|
||||||
|
let tmp = tempdir().expect("tempdir");
|
||||||
|
let config_dir = tmp.path().join(".myfsio.sys").join("config");
|
||||||
|
std::fs::create_dir_all(&config_dir).expect("create config dir");
|
||||||
|
std::fs::write(
|
||||||
|
config_dir.join("website_domains.json"),
|
||||||
|
r#"{"mappings":{"example.com":"site-bucket"}}"#,
|
||||||
|
)
|
||||||
|
.expect("write config");
|
||||||
|
|
||||||
|
let store = WebsiteDomainStore::new(tmp.path());
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
store.get_bucket("example.com"),
|
||||||
|
Some("site-bucket".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn saves_in_shared_plain_mapping_format() {
|
||||||
|
let tmp = tempdir().expect("tempdir");
|
||||||
|
let store = WebsiteDomainStore::new(tmp.path());
|
||||||
|
|
||||||
|
store.set_mapping("Example.COM", "site-bucket");
|
||||||
|
|
||||||
|
let saved = std::fs::read_to_string(
|
||||||
|
tmp.path()
|
||||||
|
.join(".myfsio.sys")
|
||||||
|
.join("config")
|
||||||
|
.join("website_domains.json"),
|
||||||
|
)
|
||||||
|
.expect("read config");
|
||||||
|
let json: serde_json::Value = serde_json::from_str(&saved).expect("parse config");
|
||||||
|
|
||||||
|
assert_eq!(json, json!({"example.com": "site-bucket"}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -341,3 +341,84 @@ fn render_bucket_detail() {
|
|||||||
ctx.insert("objects_stream_url", &"");
|
ctx.insert("objects_stream_url", &"");
|
||||||
render_or_panic("bucket_detail.html", &ctx);
|
render_or_panic("bucket_detail.html", &ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn render_bucket_detail_without_error_document() {
|
||||||
|
let mut ctx = base_ctx();
|
||||||
|
ctx.insert("bucket_name", &"site-bucket");
|
||||||
|
ctx.insert(
|
||||||
|
"bucket",
|
||||||
|
&json!({
|
||||||
|
"name": "site-bucket",
|
||||||
|
"creation_date": "2025-01-01T00:00:00Z",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
ctx.insert("objects", &Vec::<Value>::new());
|
||||||
|
ctx.insert("prefixes", &Vec::<Value>::new());
|
||||||
|
ctx.insert("total_objects", &0u64);
|
||||||
|
ctx.insert("total_bytes", &0u64);
|
||||||
|
ctx.insert("current_objects", &0u64);
|
||||||
|
ctx.insert("current_bytes", &0u64);
|
||||||
|
ctx.insert("version_count", &0u64);
|
||||||
|
ctx.insert("version_bytes", &0u64);
|
||||||
|
ctx.insert("max_objects", &Value::Null);
|
||||||
|
ctx.insert("max_bytes", &Value::Null);
|
||||||
|
ctx.insert("has_max_objects", &false);
|
||||||
|
ctx.insert("has_max_bytes", &false);
|
||||||
|
ctx.insert("obj_pct", &0);
|
||||||
|
ctx.insert("bytes_pct", &0);
|
||||||
|
ctx.insert("has_quota", &false);
|
||||||
|
ctx.insert("versioning_enabled", &false);
|
||||||
|
ctx.insert("versioning_status", &"Disabled");
|
||||||
|
ctx.insert("encryption_config", &json!({"Rules": []}));
|
||||||
|
ctx.insert("enc_rules", &Vec::<Value>::new());
|
||||||
|
ctx.insert("enc_algorithm", &"");
|
||||||
|
ctx.insert("enc_kms_key", &"");
|
||||||
|
ctx.insert("replication_rules", &Vec::<Value>::new());
|
||||||
|
ctx.insert("replication_rule", &Value::Null);
|
||||||
|
ctx.insert("website_config", &json!({"index_document": "index.html"}));
|
||||||
|
ctx.insert("bucket_policy", &"");
|
||||||
|
ctx.insert("bucket_policy_text", &"");
|
||||||
|
ctx.insert("connections", &Vec::<Value>::new());
|
||||||
|
ctx.insert("current_prefix", &"");
|
||||||
|
ctx.insert("parent_prefix", &"");
|
||||||
|
ctx.insert("has_more", &false);
|
||||||
|
ctx.insert("next_token", &"");
|
||||||
|
ctx.insert("active_tab", &"objects");
|
||||||
|
ctx.insert("multipart_uploads", &Vec::<Value>::new());
|
||||||
|
ctx.insert("target_conn", &Value::Null);
|
||||||
|
ctx.insert("target_conn_name", &"");
|
||||||
|
ctx.insert("preset_choice", &"");
|
||||||
|
ctx.insert("default_policy", &"");
|
||||||
|
ctx.insert("can_manage_cors", &true);
|
||||||
|
ctx.insert("can_manage_lifecycle", &true);
|
||||||
|
ctx.insert("can_manage_quota", &true);
|
||||||
|
ctx.insert("can_manage_versioning", &true);
|
||||||
|
ctx.insert("can_manage_website", &true);
|
||||||
|
ctx.insert("can_edit_policy", &true);
|
||||||
|
ctx.insert("is_replication_admin", &true);
|
||||||
|
ctx.insert("lifecycle_enabled", &false);
|
||||||
|
ctx.insert("site_sync_enabled", &false);
|
||||||
|
ctx.insert("website_hosting_enabled", &true);
|
||||||
|
ctx.insert("website_domains", &Vec::<Value>::new());
|
||||||
|
ctx.insert("kms_keys", &Vec::<Value>::new());
|
||||||
|
ctx.insert(
|
||||||
|
"bucket_stats",
|
||||||
|
&json!({
|
||||||
|
"bytes": 0, "objects": 0, "total_bytes": 0, "total_objects": 0,
|
||||||
|
"version_bytes": 0, "version_count": 0
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
ctx.insert(
|
||||||
|
"bucket_quota",
|
||||||
|
&json!({ "max_bytes": null, "max_objects": null }),
|
||||||
|
);
|
||||||
|
ctx.insert("buckets_for_copy_url", &"");
|
||||||
|
ctx.insert("acl_url", &"");
|
||||||
|
ctx.insert("cors_url", &"");
|
||||||
|
ctx.insert("folders_url", &"");
|
||||||
|
ctx.insert("lifecycle_url", &"");
|
||||||
|
ctx.insert("objects_api_url", &"");
|
||||||
|
ctx.insert("objects_stream_url", &"");
|
||||||
|
render_or_panic("bucket_detail.html", &ctx);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user