113 lines
3.2 KiB
Rust
113 lines
3.2 KiB
Rust
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<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()))
|
|
}
|