use axum::{ Json, extract::{Multipart, State}, http::HeaderMap, }; use std::{fs, sync::Arc}; use tracing::{error, info, warn}; use crate::{ AppState, auth::check_auth, error::AppError, models::{FileInfo, UploadResponse}, }; pub async fn list_uploads( State(state): State>, headers: HeaderMap, ) -> Result>, AppError> { check_auth(&headers, &state.admin_token)?; let uploads_dir = state.data_dir.join("uploads"); let mut files = Vec::new(); if let Ok(entries) = fs::read_dir(uploads_dir) { for entry in entries.flatten() { let path = entry.path(); if path.is_file() { if let Some(name) = path.file_name().and_then(|n| n.to_str()) { files.push(FileInfo { name: name.to_string(), url: format!("/uploads/{}", name), }); } } } } Ok(Json(files)) } pub async fn upload_file( State(state): State>, headers: HeaderMap, mut multipart: Multipart, ) -> Result, AppError> { check_auth(&headers, &state.admin_token)?; info!("Upload requested"); while let Ok(Some(field)) = multipart.next_field().await { let file_name = match field.file_name() { Some(name) => name.to_string(), None => continue, }; info!("Processing upload for: {}", file_name); let slugified_name = slug::slugify(&file_name); let extension = std::path::Path::new(&file_name) .extension() .and_then(|e| e.to_str()) .unwrap_or(""); let final_name = if !extension.is_empty() { format!("{}.{}", slugified_name, extension) } else { slugified_name }; let uploads_dir = state.data_dir.join("uploads"); let file_path = uploads_dir.join(&final_name); let final_path = if file_path.exists() { let timestamp = chrono::Utc::now().timestamp(); uploads_dir.join(format!("{}_{}", timestamp, final_name)) } else { file_path }; let final_name_str = final_path .file_name() .unwrap() .to_str() .unwrap() .to_string(); let data = match field.bytes().await { Ok(bytes) => bytes, Err(e) => { error!("Failed to read multipart bytes: {}", e); return Err(AppError::BadRequest(format!("Read error: {}", e))); } }; if let Err(e) = fs::write(&final_path, &data) { error!("Failed to write file to {:?}: {}", final_path, e); return Err(AppError::Internal( "Write error".to_string(), Some(e.to_string()), )); } info!("File uploaded successfully to {:?}", final_path); return Ok(Json(UploadResponse { url: format!("/uploads/{}", final_name_str), })); } warn!("Upload failed: no file found in multipart stream"); Err(AppError::BadRequest("No file found".to_string())) }