fix: align local-first resource boundaries

This commit is contained in:
lix-2026
2026-05-20 11:57:23 +08:00
parent e246006c81
commit dd5578c28e
6 changed files with 194 additions and 5 deletions
@@ -52,6 +52,8 @@ pub struct WorkspaceTrashRequest {
#[serde(rename_all = "camelCase")]
pub struct MindmapTrashRequest {
pub action: String,
pub source_kind: Option<String>,
pub root_uri: Option<String>,
}
#[derive(Debug, Deserialize)]
@@ -827,6 +829,7 @@ pub async fn mindmap_trash_action(
State(state): State<AppState>,
Extension(context): Extension<RequestContext>,
Path((doc_id, mindmap_id)): Path<(String, String)>,
Query(query): Query<MindmapLocalQuery>,
Json(body): Json<MindmapTrashRequest>,
) -> Result<(StatusCode, HeaderMap, Json<Value>), WebError> {
let action = body.action.trim();
@@ -845,6 +848,36 @@ pub async fn mindmap_trash_action(
let user_id = current_user_id(&state, &context).await;
let doc_id = require_id(&context, &doc_id, "docId")?;
let mindmap_id = require_id(&context, &mindmap_id, "mindmapId")?;
let local_source_kind = body
.source_kind
.as_deref()
.or(query.source_kind.as_deref())
.map(str::trim);
if local_source_kind == Some("local_folder") {
let root_uri = query
.root_uri
.as_deref()
.or(body.root_uri.as_deref())
.map(str::trim)
.filter(|value| !value.is_empty())
.ok_or_else(|| {
WebError::bad_request_code("local_folder_root_required", "缺少本地文件夹 rootUri")
.with_context(&context)
})?;
ensure_local_workspace_access(&context, root_uri)
.map_err(|error| error.with_context(&context))?;
let local_action = if action == "restore" {
"restore"
} else {
"purge"
};
let execution = execute_local_tree_command(root_uri, local_action, mindmap_id, None, None)
.map_err(|error| error.with_context(&context))?;
return Ok(ok_response(
&context,
annotate_resource_lifecycle_result(execution, command_name, "mindmap"),
));
}
let workspace_id = fetch_document_workspace_id(&state, &context, doc_id).await;
let command = resource_command(
&context,
@@ -1284,6 +1317,103 @@ mod tests {
.expect("trash index");
assert!(trash_index.contains("local-file:Page/map.mindmap.json"));
let restored = app()
.oneshot(
Request::builder()
.method("PATCH")
.uri("/api/mindmap/local-md:Page~2FPage.md/local-file:Page%2Fmap.mindmap.json")
.header("content-type", "application/json")
.header("x-mnote-actor-id", "user_real")
.header("x-mnote-actor-type", "user")
.body(Body::from(
json!({
"action": "restore",
"sourceKind": "local_folder",
"rootUri": root_uri,
})
.to_string(),
))
.expect("request"),
)
.await
.expect("restore response");
let restore_status = restored.status();
let restore_body = to_bytes(restored.into_body(), usize::MAX)
.await
.expect("restore body");
let restored_payload: Value = serde_json::from_slice(&restore_body).expect("json");
assert_eq!(
restore_status,
StatusCode::OK,
"{}",
String::from_utf8_lossy(&restore_body)
);
assert_eq!(restored_payload["result"]["sourceKind"], "local_folder");
assert_eq!(
restored_payload["result"]["canonicalCommand"],
"tree.resource.restore"
);
assert!(root.join("Page").join("map.mindmap.json").exists());
let delete_again = app()
.oneshot(
Request::builder()
.method("DELETE")
.uri(format!(
"/api/mindmap/local-md:Page~2FPage.md/local-file:Page%2Fmap.mindmap.json?sourceKind=local_folder&rootUri={root_uri}"
))
.header("x-mnote-actor-id", "user_real")
.header("x-mnote-actor-type", "user")
.body(Body::empty())
.expect("request"),
)
.await
.expect("delete again response");
assert_eq!(delete_again.status(), StatusCode::OK);
let _ = to_bytes(delete_again.into_body(), usize::MAX).await;
let purged = app()
.oneshot(
Request::builder()
.method("PATCH")
.uri("/api/mindmap/local-md:Page~2FPage.md/local-file:Page%2Fmap.mindmap.json")
.header("content-type", "application/json")
.header("x-mnote-actor-id", "user_real")
.header("x-mnote-actor-type", "user")
.body(Body::from(
json!({
"action": "purge",
"sourceKind": "local_folder",
"rootUri": root_uri,
})
.to_string(),
))
.expect("request"),
)
.await
.expect("purge response");
let purge_status = purged.status();
let purge_body = to_bytes(purged.into_body(), usize::MAX)
.await
.expect("purge body");
let purged_payload: Value = serde_json::from_slice(&purge_body).expect("json");
assert_eq!(
purge_status,
StatusCode::OK,
"{}",
String::from_utf8_lossy(&purge_body)
);
assert_eq!(purged_payload["result"]["sourceKind"], "local_folder");
assert_eq!(
purged_payload["result"]["canonicalCommand"],
"tree.resource.purge"
);
assert!(!root
.join(".mnote")
.join("trash")
.join("map.mindmap.json")
.exists());
let _ = std::fs::remove_dir_all(&root);
}