refactor
This commit is contained in:
105
backend/src/handlers/upload.rs
Normal file
105
backend/src/handlers/upload.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
use axum::{
|
||||
extract::{Multipart, State},
|
||||
http::HeaderMap,
|
||||
response::IntoResponse,
|
||||
Json,
|
||||
};
|
||||
use std::{fs, sync::Arc};
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
use crate::{
|
||||
auth::check_auth,
|
||||
error::AppError,
|
||||
models::{FileInfo, UploadResponse},
|
||||
AppState,
|
||||
};
|
||||
|
||||
pub async fn list_uploads(
|
||||
State(state): State<Arc<AppState>>,
|
||||
headers: HeaderMap,
|
||||
) -> Result<Json<Vec<FileInfo>>, 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<Arc<AppState>>,
|
||||
headers: HeaderMap,
|
||||
mut multipart: Multipart,
|
||||
) -> Result<Json<UploadResponse>, 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()))
|
||||
}
|
||||
Reference in New Issue
Block a user