@@ -144,12 +144,15 @@ pub async fn upload_image(
144144 }
145145
146146 let filepath = upload_path_base. join ( & new_filename) ;
147+ // Create a temporary file path
148+ let temp_filename = format ! ( "{}.tmp" , id) ;
149+ let temp_filepath = upload_path_base. join ( & temp_filename) ;
147150
148- // Open the file for writing (using async tokio file)
149- let mut file = match tokio:: fs:: File :: create ( & filepath ) . await {
151+ // Open the temp file for writing
152+ let mut file = match tokio:: fs:: File :: create ( & temp_filepath ) . await {
150153 Ok ( file) => file,
151154 Err ( e) => {
152- tracing:: error!( "Failed to create file {}: {}" , filepath . display( ) , e) ;
155+ tracing:: error!( "Failed to create temp file {}: {}" , temp_filepath . display( ) , e) ;
153156 return Err ( (
154157 StatusCode :: INTERNAL_SERVER_ERROR ,
155158 Json ( ErrorResponse {
@@ -161,10 +164,10 @@ pub async fn upload_image(
161164
162165 use tokio:: io:: AsyncWriteExt ; // Required for write_all and flush
163166
164- // Write the first chunk (the one we peeked at for inference)
167+ // Write the first chunk
165168 if let Err ( e) = file. write_all ( & first_chunk) . await {
166- tracing:: error!( "Failed to write first chunk to {}: {}" , filepath . display( ) , e) ;
167- let _ = tokio:: fs:: remove_file ( & filepath ) . await ; // Cleanup partially written file
169+ tracing:: error!( "Failed to write first chunk to {}: {}" , temp_filepath . display( ) , e) ;
170+ let _ = tokio:: fs:: remove_file ( & temp_filepath ) . await ;
168171 return Err ( (
169172 StatusCode :: INTERNAL_SERVER_ERROR ,
170173 Json ( ErrorResponse {
@@ -181,7 +184,7 @@ pub async fn upload_image(
181184 Ok ( opt) => opt,
182185 Err ( err) => {
183186 tracing:: error!( "Failed to read chunk: {}" , err) ;
184- let _ = tokio:: fs:: remove_file ( & filepath ) . await ; // Cleanup on network/parsing error
187+ let _ = tokio:: fs:: remove_file ( & temp_filepath ) . await ;
185188 return Err ( (
186189 StatusCode :: INTERNAL_SERVER_ERROR ,
187190 Json ( ErrorResponse {
@@ -191,7 +194,6 @@ pub async fn upload_image(
191194 }
192195 } ;
193196
194- // Check if we hit the end of the field
195197 let chunk = match chunk_option {
196198 Some ( c) => c,
197199 None => break ,
@@ -200,7 +202,7 @@ pub async fn upload_image(
200202 // ENFORCEMENT: Track total size to prevent Disk Space exhaustion (DoS)
201203 total_size += chunk. len ( ) ;
202204 if total_size > MAX_FILE_SIZE {
203- let _ = tokio:: fs:: remove_file ( & filepath ) . await ;
205+ let _ = tokio:: fs:: remove_file ( & temp_filepath ) . await ;
204206 return Err ( (
205207 StatusCode :: BAD_REQUEST ,
206208 Json ( ErrorResponse {
@@ -211,8 +213,8 @@ pub async fn upload_image(
211213
212214 // Write chunk to disk
213215 if let Err ( e) = file. write_all ( & chunk) . await {
214- tracing:: error!( "Failed to write chunk to {}: {}" , filepath . display( ) , e) ;
215- let _ = tokio:: fs:: remove_file ( & filepath ) . await ; // Cleanup on disk error
216+ tracing:: error!( "Failed to write chunk to {}: {}" , temp_filepath . display( ) , e) ;
217+ let _ = tokio:: fs:: remove_file ( & temp_filepath ) . await ;
216218 return Err ( (
217219 StatusCode :: INTERNAL_SERVER_ERROR ,
218220 Json ( ErrorResponse {
@@ -224,8 +226,8 @@ pub async fn upload_image(
224226
225227 // Sync buffers to disk
226228 if let Err ( e) = file. flush ( ) . await {
227- tracing:: error!( "Failed to flush file {}: {}" , filepath . display( ) , e) ;
228- let _ = tokio:: fs:: remove_file ( & filepath ) . await ;
229+ tracing:: error!( "Failed to flush file {}: {}" , temp_filepath . display( ) , e) ;
230+ let _ = tokio:: fs:: remove_file ( & temp_filepath ) . await ;
229231 return Err ( (
230232 StatusCode :: INTERNAL_SERVER_ERROR ,
231233 Json ( ErrorResponse {
@@ -234,6 +236,18 @@ pub async fn upload_image(
234236 ) ) ;
235237 }
236238
239+ // Atomic rename from temp to final
240+ if let Err ( e) = tokio:: fs:: rename ( & temp_filepath, & filepath) . await {
241+ tracing:: error!( "Failed to rename temp file {} to {}: {}" , temp_filepath. display( ) , filepath. display( ) , e) ;
242+ let _ = tokio:: fs:: remove_file ( & temp_filepath) . await ;
243+ return Err ( (
244+ StatusCode :: INTERNAL_SERVER_ERROR ,
245+ Json ( ErrorResponse {
246+ error : "Failed to save file" . to_string ( ) ,
247+ } ) ,
248+ ) ) ;
249+ }
250+
237251 // SUCCESS path
238252 tracing:: info!( "Successfully uploaded image: {}" , filepath. display( ) ) ;
239253
0 commit comments