Add root parameter to backup/snapshot REST API

API Enhancement:
- All snapshot endpoints now accept 'root' query parameter
- Default root: /data (for production)
- Test root: configurable (e.g., /tmp/backup_test)

Endpoints updated:
- GET /api/v2/snapshots?root=<path>
- POST /api/v2/snapshots/:name?root=<path>
- DELETE /api/v2/snapshots/:name?root=<path>
- POST /api/v2/snapshots/:name/restore?root=<path>
- GET /api/v2/storage/stats?root=<path>

Integration Testing Results :
- Create snapshot: test_snap1 created
- List snapshots: ['test_snap1'] returned
- Modify file: 'original content' → 'modified content'
- Restore snapshot: 'modified content' → 'original content' 
- Delete snapshot: test_snap1 removed

Snapshot metadata format:
{
  'name': 'test_snap1',
  'created': {'secs_since_epoch': 1782243041, 'nanos_since_epoch': 344384000},
  'source_path': '/tmp/backup_test'
}

Build: 495 tests pass
Server: Port 11438 running with root parameter support
This commit is contained in:
Warren
2026-06-24 03:31:43 +08:00
parent 26d4199203
commit 55caeabd94
2 changed files with 19 additions and 10 deletions

Binary file not shown.

View File

@@ -2821,45 +2821,54 @@ async fn run_backup_handler() -> Json<serde_json::Value> {
} }
} }
async fn list_snapshots_handler() -> Json<Vec<String>> { async fn list_snapshots_handler(Query(params): Query<std::collections::HashMap<String, String>>) -> Json<Vec<String>> {
let root = params.get("root").map(|p| PathBuf::from(p)).unwrap_or_else(|| PathBuf::from("/data"));
let backend = LocalFs::new(); let backend = LocalFs::new();
let root = PathBuf::from("/data");
match backend.list_snapshots(&root) { match backend.list_snapshots(&root) {
Ok(list) => Json(list), Ok(list) => Json(list),
Err(_) => Json(Vec::new()), Err(_) => Json(Vec::new()),
} }
} }
async fn create_snapshot_handler(Path(name): Path<String>) -> Json<serde_json::Value> { async fn create_snapshot_handler(
Path(name): Path<String>,
Query(params): Query<std::collections::HashMap<String, String>>,
) -> Json<serde_json::Value> {
let root = params.get("root").map(|p| PathBuf::from(p)).unwrap_or_else(|| PathBuf::from("/data"));
let backend = LocalFs::new(); let backend = LocalFs::new();
let root = PathBuf::from("/data");
match backend.create_snapshot(&root, &name) { match backend.create_snapshot(&root, &name) {
Ok(_) => Json(serde_json::json!({"success": true, "name": name})), Ok(_) => Json(serde_json::json!({"success": true, "name": name})),
Err(e) => Json(serde_json::json!({"success": false, "error": e.to_string()})), Err(e) => Json(serde_json::json!({"success": false, "error": e.to_string()})),
} }
} }
async fn delete_snapshot_handler(Path(name): Path<String>) -> Json<serde_json::Value> { async fn delete_snapshot_handler(
Path(name): Path<String>,
Query(params): Query<std::collections::HashMap<String, String>>,
) -> Json<serde_json::Value> {
let root = params.get("root").map(|p| PathBuf::from(p)).unwrap_or_else(|| PathBuf::from("/data"));
let backend = LocalFs::new(); let backend = LocalFs::new();
let root = PathBuf::from("/data");
match backend.delete_snapshot(&root, &name) { match backend.delete_snapshot(&root, &name) {
Ok(_) => Json(serde_json::json!({"success": true, "name": name})), Ok(_) => Json(serde_json::json!({"success": true, "name": name})),
Err(e) => Json(serde_json::json!({"success": false, "error": e.to_string()})), Err(e) => Json(serde_json::json!({"success": false, "error": e.to_string()})),
} }
} }
async fn restore_snapshot_handler(Path(name): Path<String>) -> Json<serde_json::Value> { async fn restore_snapshot_handler(
Path(name): Path<String>,
Query(params): Query<std::collections::HashMap<String, String>>,
) -> Json<serde_json::Value> {
let root = params.get("root").map(|p| PathBuf::from(p)).unwrap_or_else(|| PathBuf::from("/data"));
let backend = LocalFs::new(); let backend = LocalFs::new();
let root = PathBuf::from("/data");
match backend.restore_snapshot(&root, &name) { match backend.restore_snapshot(&root, &name) {
Ok(_) => Json(serde_json::json!({"success": true, "name": name})), Ok(_) => Json(serde_json::json!({"success": true, "name": name})),
Err(e) => Json(serde_json::json!({"success": false, "error": e.to_string()})), Err(e) => Json(serde_json::json!({"success": false, "error": e.to_string()})),
} }
} }
async fn get_storage_stats_handler() -> Json<StorageStatsResponse> { async fn get_storage_stats_handler(Query(params): Query<std::collections::HashMap<String, String>>) -> Json<StorageStatsResponse> {
let root = params.get("root").map(|p| PathBuf::from(p)).unwrap_or_else(|| PathBuf::from("/data"));
let backend = LocalFs::new(); let backend = LocalFs::new();
let root = PathBuf::from("/data");
match backend.stat(&root) { match backend.stat(&root) {
Ok(stat) => Json(StorageStatsResponse { Ok(stat) => Json(StorageStatsResponse {
total_size: stat.size, total_size: stat.size,