jax_daemon/http_server/api/v0/bucket/
delete.rs1use axum::extract::{Json, State};
2use axum::response::{IntoResponse, Response};
3use common::prelude::MountError;
4use reqwest::{Client, RequestBuilder, Url};
5use serde::{Deserialize, Serialize};
6use std::path::PathBuf;
7use uuid::Uuid;
8
9use crate::http_server::api::client::ApiRequest;
10use crate::ServiceState;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct DeleteRequest {
14 pub bucket_id: Uuid,
16 pub path: String,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct DeleteResponse {
22 pub path: String,
23 pub new_bucket_link: String,
24}
25
26pub async fn handler(
27 State(state): State<ServiceState>,
28 Json(req): Json<DeleteRequest>,
29) -> Result<impl IntoResponse, DeleteError> {
30 tracing::info!(
31 "DELETE API: Received delete request for path {} in bucket {}",
32 req.path,
33 req.bucket_id
34 );
35
36 let path = PathBuf::from(&req.path);
38 if !path.is_absolute() {
39 return Err(DeleteError::InvalidPath(format!(
40 "Path must be absolute: {}",
41 req.path
42 )));
43 }
44
45 let mut mount = state.peer().mount(req.bucket_id).await?;
47 tracing::info!("DELETE API: Loaded mount for bucket {}", req.bucket_id);
48
49 if mount.get(&path).await.is_err() {
51 return Err(DeleteError::PathNotFound(req.path.clone()));
52 }
53
54 mount.rm(&path).await.map_err(|e| {
56 tracing::error!("DELETE API: Failed to remove {}: {}", req.path, e);
57 DeleteError::Mount(e)
58 })?;
59
60 tracing::info!("DELETE API: Removed {} from mount", req.path);
61
62 let new_bucket_link = state.peer().save_mount(&mount, false).await?;
64
65 tracing::info!(
66 "DELETE API: Deleted {} from bucket {}, new link: {}",
67 req.path,
68 req.bucket_id,
69 new_bucket_link.hash()
70 );
71
72 Ok((
73 http::StatusCode::OK,
74 Json(DeleteResponse {
75 path: req.path,
76 new_bucket_link: new_bucket_link.hash().to_string(),
77 }),
78 )
79 .into_response())
80}
81
82#[derive(Debug, thiserror::Error)]
83pub enum DeleteError {
84 #[error("Invalid path: {0}")]
85 InvalidPath(String),
86 #[error("Path not found: {0}")]
87 PathNotFound(String),
88 #[error("Mount error: {0}")]
89 Mount(#[from] MountError),
90}
91
92impl IntoResponse for DeleteError {
93 fn into_response(self) -> Response {
94 match self {
95 DeleteError::InvalidPath(msg) => (
96 http::StatusCode::BAD_REQUEST,
97 format!("Invalid path: {}", msg),
98 )
99 .into_response(),
100 DeleteError::PathNotFound(msg) => (
101 http::StatusCode::NOT_FOUND,
102 format!("Path not found: {}", msg),
103 )
104 .into_response(),
105 DeleteError::Mount(_) => (
106 http::StatusCode::INTERNAL_SERVER_ERROR,
107 "Unexpected error".to_string(),
108 )
109 .into_response(),
110 }
111 }
112}
113
114impl ApiRequest for DeleteRequest {
115 type Response = DeleteResponse;
116
117 fn build_request(self, base_url: &Url, client: &Client) -> RequestBuilder {
118 let full_url = base_url.join("/api/v0/bucket/delete").unwrap();
119 client.post(full_url).json(&self)
120 }
121}