jax_daemon/http_server/api/v0/bucket/
update.rs1use axum::extract::{Multipart, State};
2use axum::response::{IntoResponse, Response};
3use serde::{Deserialize, Serialize};
4use std::io::Cursor;
5use std::path::PathBuf;
6use uuid::Uuid;
7
8use common::prelude::{Link, MountError};
9
10use crate::ServiceState;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct UpdateResponse {
14 pub mount_path: String,
15 pub link: Link,
16 pub mime_type: String,
17}
18
19pub async fn handler(
20 State(state): State<ServiceState>,
21 mut multipart: Multipart,
22) -> Result<impl IntoResponse, UpdateError> {
23 let mut bucket_id: Option<Uuid> = None;
24 let mut mount_path: Option<String> = None;
25 let mut file_data: Option<Vec<u8>> = None;
26
27 while let Some(field) = multipart
29 .next_field()
30 .await
31 .map_err(|e| UpdateError::MultipartError(e.to_string()))?
32 {
33 let field_name = field.name().unwrap_or("").to_string();
34
35 match field_name.as_str() {
36 "bucket_id" => {
37 let text = field
38 .text()
39 .await
40 .map_err(|e| UpdateError::MultipartError(e.to_string()))?;
41 bucket_id = Some(
42 Uuid::parse_str(&text)
43 .map_err(|_| UpdateError::InvalidRequest("Invalid bucket_id".into()))?,
44 );
45 }
46 "mount_path" => {
47 mount_path = Some(
48 field
49 .text()
50 .await
51 .map_err(|e| UpdateError::MultipartError(e.to_string()))?,
52 );
53 }
54 "file" => {
55 file_data = Some(
56 field
57 .bytes()
58 .await
59 .map_err(|e| UpdateError::MultipartError(e.to_string()))?
60 .to_vec(),
61 );
62 }
63 _ => {}
64 }
65 }
66
67 let bucket_id =
68 bucket_id.ok_or_else(|| UpdateError::InvalidRequest("bucket_id is required".into()))?;
69 let mount_path =
70 mount_path.ok_or_else(|| UpdateError::InvalidRequest("mount_path is required".into()))?;
71 let file_data =
72 file_data.ok_or_else(|| UpdateError::InvalidRequest("file is required".into()))?;
73
74 let mount_path_buf = PathBuf::from(&mount_path);
76 if !mount_path_buf.is_absolute() {
77 return Err(UpdateError::InvalidPath(
78 "Mount path must be absolute".into(),
79 ));
80 }
81
82 tracing::info!(
83 "UPDATE API: Updating file in bucket {} at {}",
84 bucket_id,
85 mount_path
86 );
87
88 let mime_type = mime_guess::from_path(&mount_path_buf)
90 .first_or_octet_stream()
91 .to_string();
92
93 let mut mount = state.peer().mount(bucket_id).await?;
95
96 let file_exists = mount.get(&mount_path_buf).await.is_ok();
98 if file_exists {
99 tracing::info!("UPDATE API: Removing existing file at {}", mount_path);
100 mount.rm(&mount_path_buf).await.map_err(|e| {
102 tracing::error!("UPDATE API: Failed to remove existing file: {}", e);
103 UpdateError::Mount(e)
104 })?;
105 } else {
106 tracing::info!("UPDATE API: File doesn't exist, will create new");
107 }
108
109 let reader = Cursor::new(file_data);
111 mount.add(&mount_path_buf, reader).await?;
112
113 tracing::info!("UPDATE API: Added new content to {}", mount_path);
114
115 let new_bucket_link = state.peer().save_mount(&mount, None).await?;
117
118 tracing::info!(
119 "UPDATE API: Updated {} in bucket {}, new link: {}",
120 mount_path,
121 bucket_id,
122 new_bucket_link.hash()
123 );
124
125 Ok((
126 http::StatusCode::OK,
127 axum::Json(UpdateResponse {
128 mount_path,
129 link: new_bucket_link,
130 mime_type,
131 }),
132 )
133 .into_response())
134}
135
136#[derive(Debug, thiserror::Error)]
137pub enum UpdateError {
138 #[error("Invalid path: {0}")]
139 InvalidPath(String),
140 #[error("Invalid request: {0}")]
141 InvalidRequest(String),
142 #[error("Multipart error: {0}")]
143 MultipartError(String),
144 #[error("Mount error: {0}")]
145 Mount(#[from] MountError),
146}
147
148impl IntoResponse for UpdateError {
149 fn into_response(self) -> Response {
150 match self {
151 UpdateError::InvalidPath(msg)
152 | UpdateError::InvalidRequest(msg)
153 | UpdateError::MultipartError(msg) => (
154 http::StatusCode::BAD_REQUEST,
155 format!("Bad request: {}", msg),
156 )
157 .into_response(),
158 UpdateError::Mount(_) => (
159 http::StatusCode::INTERNAL_SERVER_ERROR,
160 "Unexpected error".to_string(),
161 )
162 .into_response(),
163 }
164 }
165}