Merge origin SMB fixes with local Phase 21-22 features
Some checks failed
Test / test (push) Has been cancelled
Test / build (push) Has been cancelled

Origin changes merged:
- SMB performance optimization (pread/pwrite, tokio Mutex)
- macOS SMB mount fix (AAPL caps, credit grant)
- Compound request integration tests
- CTDB architecture analysis

Local changes preserved:
- upload_path config (deployed, tested stable)
- delete_file + preview_file routes (MyFiles UI)
- SSH async I/O (cipher.rs, packet.rs, server.rs)
- auth.sqlite (86016 bytes, important user data)
- Admin WebDAV + CorsLayer
- api/admin.rs + api/config.rs (new endpoints)

Conflicts resolved:
- myfiles.rs: kept upload_path + OnceLock static
- auth.sqlite: preserved local version (important data)

Test results: 393 passed, 5 auth tests failed
- PG tests require external PostgreSQL
- Auth tests expect specific password hashes
- auth.sqlite preserved with actual user credentials
This commit is contained in:
Warren
2026-06-30 07:25:04 +08:00
parent deac3b9b6e
commit 4fa8fd8c1f
17 changed files with 1246 additions and 716 deletions

View File

@@ -0,0 +1,208 @@
use axum::{
extract::Query,
http::StatusCode,
response::{IntoResponse, Json},
};
#[derive(Debug, serde::Deserialize)]
pub struct EditConfigQuery {
pub key: String,
pub value: String,
}
pub async fn get_config_handler() -> impl IntoResponse {
let config_path = std::path::Path::new("config/markbase.toml");
// Return defaults if config file doesn't exist yet (loadSettings in admin UI needs it)
if !config_path.exists() {
let mut config = crate::config::MarkBaseConfig::default_config();
config.merge_env();
return (
StatusCode::OK,
Json(serde_json::to_value(&config).unwrap_or_default()),
)
.into_response();
}
match crate::config::MarkBaseConfig::load(config_path) {
Ok(config) => (
StatusCode::OK,
Json(serde_json::to_value(&config).unwrap_or_default()),
)
.into_response(),
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({"error": e.to_string()})),
)
.into_response(),
}
}
pub async fn edit_config_handler(Query(params): Query<EditConfigQuery>) -> impl IntoResponse {
let config_path = std::path::Path::new("config/markbase.toml");
// Load existing or use defaults, so admin can save settings without a pre-existing file
let mut config = if config_path.exists() {
match crate::config::MarkBaseConfig::load(config_path) {
Ok(c) => c,
Err(e) => {
return (StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({"error": e.to_string()}))).into_response();
}
}
} else {
let mut defaults = crate::config::MarkBaseConfig::default_config();
defaults.merge_env();
defaults
};
let old_value = config.get(&params.key).unwrap_or_default();
match config.set(&params.key, &params.value) {
Ok(_) => match config.validate() {
Ok(_) => match config.save(config_path) {
Ok(_) => {
let audit = crate::audit::AuditLogger::default();
if let Err(e) = audit.log_config_change(
"markbase",
&params.key,
&old_value,
&params.value,
"system",
None,
) {
log::warn!("Failed to write audit log: {}", e);
}
(StatusCode::OK, Json(serde_json::json!({"ok": true}))).into_response()
}
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({"error": e.to_string()})),
)
.into_response(),
},
Err(e) => (
StatusCode::BAD_REQUEST,
Json(serde_json::json!({"error": e.to_string()})),
)
.into_response(),
},
Err(e) => (
StatusCode::BAD_REQUEST,
Json(serde_json::json!({"error": e.to_string()})),
)
.into_response(),
}
}
pub async fn validate_config_handler() -> impl IntoResponse {
let config_path = std::path::Path::new("config/markbase.toml");
if !config_path.exists() {
return (
StatusCode::NOT_FOUND,
Json(serde_json::json!({"ok": false, "error": "Config file not found"})),
)
.into_response();
}
match crate::config::MarkBaseConfig::load(config_path) {
Ok(config) => match config.validate() {
Ok(_) => (StatusCode::OK, Json(serde_json::json!({"ok": true}))).into_response(),
Err(e) => (
StatusCode::BAD_REQUEST,
Json(serde_json::json!({"ok": false, "error": e.to_string()})),
)
.into_response(),
},
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({"ok": false, "error": e.to_string()})),
)
.into_response(),
}
}
pub async fn get_s3_config_handler() -> impl IntoResponse {
match crate::s3_config::S3Config::load_default() {
Ok(config) => (
StatusCode::OK,
Json(serde_json::to_value(&config).unwrap_or_default()),
)
.into_response(),
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({"error": e.to_string()})),
)
.into_response(),
}
}
pub async fn edit_s3_config_handler(Query(params): Query<EditConfigQuery>) -> impl IntoResponse {
match crate::s3_config::S3Config::load_default() {
Ok(mut config) => {
let old_value = config.get(&params.key).unwrap_or_default();
match config.set(&params.key, &params.value) {
Ok(_) => match config.validate() {
Ok(_) => match config.save("config/s3.toml") {
Ok(_) => {
let audit = crate::audit::AuditLogger::default();
if let Err(e) = audit.log_config_change(
"s3",
&params.key,
&old_value,
&params.value,
"system",
None,
) {
log::warn!("Failed to write audit log: {}", e);
}
(StatusCode::OK, Json(serde_json::json!({"ok": true}))).into_response()
}
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({"error": e.to_string()})),
)
.into_response(),
},
Err(e) => (
StatusCode::BAD_REQUEST,
Json(serde_json::json!({"error": e.to_string()})),
)
.into_response(),
},
Err(e) => (
StatusCode::BAD_REQUEST,
Json(serde_json::json!({"error": e.to_string()})),
)
.into_response(),
}
}
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({"error": e.to_string()})),
)
.into_response(),
}
}
pub async fn validate_s3_config_handler() -> impl IntoResponse {
match crate::s3_config::S3Config::load_default() {
Ok(config) => match config.validate() {
Ok(_) => (StatusCode::OK, Json(serde_json::json!({"ok": true}))).into_response(),
Err(e) => (
StatusCode::BAD_REQUEST,
Json(serde_json::json!({"ok": false, "error": e.to_string()})),
)
.into_response(),
},
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({"ok": false, "error": e.to_string()})),
)
.into_response(),
}
}