MarkBase架构升级:Multi-Volume Virtual Tree + Dual-View Management + Git Remote修正
核心功能: - ✅ 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:
@@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user