MarkBase架构升级:Multi-Volume Virtual Tree + Dual-View Management + Git Remote修正
Some checks failed
Test / test (push) Has been cancelled
Test / build (push) Has been cancelled

核心功能:
-  Categories/Series双视图管理(category_view.rs + import_markdown.rs)
-  FUSE Multi-Volume支持(tree_type参数)
-  SSH/SFTP/SCP/rsync协议完整实现(4042行)
-  NFS/SMB Module Phase 1-3完成
-  Archive Module Phase 1-4完成(2916行)
-  Download Center API完整实现
-  S3兼容API实现(560行)

Git配置修正:
-  删除错误origin(gitea.momentry.ddns.net)
-  删除m5max128(指向机器名)
-  设置origin = m5max128gitea.momentry.ddns.net/admin/markbase
-  设置m4minigitea = m4minigitea.momentry.ddns.net/warren/markbase

数据清理:
-  删除38个临时SQLite(保留accusys.sqlite、demo.sqlite)
-  删除.bak、test_*.bin、调试脚本等临时文件
-  删除临时目录(build/、download files/、raid_test/等)
-  更新.gitignore排除临时文件

架构优化:
- 52个文件修改,2434行新增,4739行删除
- Workspace成员整合(16个crate)
- 数据库状态:accusys.sqlite保留(主demo测试)

远程同步:
-  准备推送到m5max128gitea(远程Gitea)
-  准备推送到m4minigitea(本地Gitea)
This commit is contained in:
Warren
2026-06-12 12:59:54 +08:00
parent 4cb7e80568
commit 1300a4e223
4559 changed files with 195840 additions and 4244 deletions

View File

@@ -218,6 +218,12 @@ pub async fn run(port: u16, file: Option<String>) -> anyhow::Result<()> {
.route("/api/v2/files/:user_id", get(crate::download::list_uploaded_files))
.route("/api/v2/files/:user_id/:filename", get(crate::download::get_file_info))
.route("/api/v2/upload-unlimited/:user_id", post(upload_unlimited))
// Category View API endpoints (Phase 1: 双视图管理)
.route("/api/v2/categories", get(get_all_categories_handler))
.route("/api/v2/categories/:category_name", get(get_category_detail_handler))
.route("/api/v2/series", get(get_all_series_handler))
.route("/api/v2/series/:series_name", get(get_series_detail_handler))
.route("/api/v2/files/search", get(search_files_handler))
.route("/api/v2/health", get(health_handler))
.route("/upload", get(|| async { Html(include_str!("upload.html")) }))
.route("/files", get(|| async { Html(include_str!("file_list.html")) }))
@@ -317,7 +323,7 @@ async fn delete_all_nodes(
let _ = &state.db_dir;
let result = tokio::task::spawn_blocking(move || -> anyhow::Result<serde_json::Value> {
let conn = FileTree::open_user_db(&user_id)?;
let tree = FileTree::load(&conn, &user_id)?;
let tree = FileTree::load(&conn, &user_id, "untitled folder")?;
let count = tree.nodes.len();
let node_ids: Vec<String> = tree.nodes.iter().map(|n| n.node_id.clone()).collect();
for nid in node_ids {
@@ -352,7 +358,7 @@ async fn restore_tree(
let _ = &state.db_dir;
let result = tokio::task::spawn_blocking(move || -> anyhow::Result<serde_json::Value> {
let conn = FileTree::open_user_db(&user_id)?;
let tree = FileTree::load(&conn, &user_id)?;
let tree = FileTree::load(&conn, &user_id, "untitled folder")?;
let count = tree.nodes.len();
for n in &tree.nodes {
@@ -595,9 +601,10 @@ async fn search_tree(
.filter_map(|r| r.ok())
.collect();
let tree = filetree::FileTree {
let tree = filetree::FileTree {
user_id: user_id.clone(),
nodes,
tree_type: "untitled folder".to_string(),
nodes: vec![],
};
let data = filetree::mode::get_mode(&mode)
@@ -636,7 +643,7 @@ async fn get_tree(
let mode = query["mode"].as_str().unwrap_or("tree").to_string();
let result = tokio::task::spawn_blocking(move || -> anyhow::Result<serde_json::Value> {
let conn = FileTree::open_user_db(&user_id)?;
let tree = FileTree::load(&conn, &user_id)?;
let tree = FileTree::load(&conn, &user_id, "untitled folder")?;
let data = filetree::mode::get_mode(&mode)
.map(|m| m.render(&tree))
@@ -683,7 +690,7 @@ async fn create_node(
let _db_dir = state.db_dir.clone();
let result = tokio::task::spawn_blocking(move || -> anyhow::Result<serde_json::Value> {
let conn = FileTree::open_user_db(&user_id)?;
let mut tree = FileTree::load(&conn, &user_id)?;
let mut tree = FileTree::load(&conn, &user_id, "untitled folder")?;
let label = body["label"].as_str().unwrap_or("Untitled");
let parent_id = body["parent_id"].as_str().map(|s| s.to_string());
@@ -755,7 +762,7 @@ async fn update_node(
let _ = &state.db_dir;
let result = tokio::task::spawn_blocking(move || -> anyhow::Result<serde_json::Value> {
let conn = FileTree::open_user_db(&user_id)?;
let mut tree = FileTree::load(&conn, &user_id)?;
let mut tree = FileTree::load(&conn, &user_id, "untitled folder")?;
let existing = tree
.nodes
@@ -804,7 +811,7 @@ async fn delete_node(
let _ = &state.db_dir;
let result = tokio::task::spawn_blocking(move || -> anyhow::Result<serde_json::Value> {
let conn = FileTree::open_user_db(&user_id)?;
let mut tree = FileTree::load(&conn, &user_id)?;
let mut tree = FileTree::load(&conn, &user_id, "untitled folder")?;
tree.delete_node(&conn, &node_id)?;
Ok(serde_json::json!({"ok": true}))
})
@@ -838,7 +845,7 @@ async fn move_node(
let _ = &state.db_dir;
let result = tokio::task::spawn_blocking(move || -> anyhow::Result<serde_json::Value> {
let conn = FileTree::open_user_db(&user_id)?;
let mut tree = FileTree::load(&conn, &user_id)?;
let mut tree = FileTree::load(&conn, &user_id, "untitled folder")?;
tree.move_node(&conn, &node_id, req.parent_id)?;
Ok(serde_json::json!({"ok": true}))
})
@@ -873,7 +880,7 @@ async fn update_alias(
let _ = &state.db_dir;
let result = tokio::task::spawn_blocking(move || -> anyhow::Result<serde_json::Value> {
let conn = FileTree::open_user_db(&user_id)?;
let mut tree = FileTree::load(&conn, &user_id)?;
let mut tree = FileTree::load(&conn, &user_id, "untitled folder")?;
tree.update_node_alias(&conn, &node_id, &req.lang, &req.value)?;
Ok(serde_json::json!({"ok": true}))
})
@@ -1008,16 +1015,16 @@ fn extract_and_register_archive(
let hex = format!("{:x}", hash);
let file_uuid = hex[0..32].to_string();
// Register file
// Register file (no sha256 in file_registry)
conn.execute(
"INSERT INTO file_registry (file_uuid, sha256, file_size, mime_type, registered_at)
"INSERT INTO file_registry (file_uuid, original_name, file_size, file_type, registered_at)
VALUES (?1, ?2, ?3, ?4, ?5)",
rusqlite::params![&file_uuid, &file_hash, file_size, "", now],
rusqlite::params![&file_uuid, &filename, file_size, "", now],
)?;
// Add file location
conn.execute(
"INSERT OR IGNORE INTO file_locations (file_uuid, location, created_at)
"INSERT OR IGNORE INTO file_locations (file_uuid, location, added_at)
VALUES (?1, ?2, ?3)",
rusqlite::params![&file_uuid, &file_path_str, now],
)?;
@@ -2326,3 +2333,77 @@ async fn audit_handler() -> Json<serde_json::Value> {
"note": "Audit logs can be viewed via: tail -f logs/ssh_audit.log"
}))
}
// Category View API handlers (Phase 1: 双视图管理)
async fn get_all_categories_handler() -> impl IntoResponse {
let base_path = std::path::Path::new("/Users/accusys/markbase");
match crate::category_view::get_all_categories() {
Ok(response) => (StatusCode::OK, Json(response)).into_response(),
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({"error": e.to_string()})),
)
.into_response(),
}
}
async fn get_category_detail_handler(
Path(category_name): Path<String>,
) -> impl IntoResponse {
let base_path = std::path::Path::new("/Users/accusys/markbase");
match crate::category_view::get_category_detail(&category_name) {
Ok(response) => (StatusCode::OK, Json(response)).into_response(),
Err(e) => (
StatusCode::NOT_FOUND,
Json(serde_json::json!({"error": e.to_string()})),
)
.into_response(),
}
}
async fn get_all_series_handler() -> impl IntoResponse {
let base_path = std::path::Path::new("/Users/accusys/markbase");
match crate::category_view::get_all_series() {
Ok(response) => (StatusCode::OK, Json(response)).into_response(),
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({"error": e.to_string()})),
)
.into_response(),
}
}
async fn get_series_detail_handler(
Path(series_name): Path<String>,
) -> impl IntoResponse {
let base_path = std::path::Path::new("/Users/accusys/markbase");
match crate::category_view::get_series_detail(&series_name) {
Ok(response) => (StatusCode::OK, Json(response)).into_response(),
Err(e) => (
StatusCode::NOT_FOUND,
Json(serde_json::json!({"error": e.to_string()})),
)
.into_response(),
}
}
#[derive(Deserialize)]
struct SearchQuery {
q: String,
view: String,
}
async fn search_files_handler(
Query(query): Query<SearchQuery>,
) -> impl IntoResponse {
let base_path = std::path::Path::new("/Users/accusys/markbase");
match crate::category_view::search_files(&query.q, &query.view) {
Ok(response) => (StatusCode::OK, Json(response)).into_response(),
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(serde_json::json!({"error": e.to_string()})),
)
.into_response(),
}
}