feat: add klog
This commit is contained in:
151
backend/src/handlers.rs
Normal file
151
backend/src/handlers.rs
Normal file
@@ -0,0 +1,151 @@
|
||||
use axum::{
|
||||
body::Body,
|
||||
extract::{Multipart, State},
|
||||
http::{HeaderValue, StatusCode},
|
||||
response::{IntoResponse, Response},
|
||||
Json,
|
||||
};
|
||||
use std::{io::Write, sync::Arc};
|
||||
use klog_types::{BatchFilesRequest, FilesMetaResponse};
|
||||
use serde_json::json;
|
||||
use crate::{auth::AuthUser, storage, AppState};
|
||||
|
||||
pub async fn list_all_files_meta(
|
||||
State(state): State<Arc<AppState>>,
|
||||
auth: AuthUser,
|
||||
) -> Result<Json<FilesMetaResponse>, AppError> {
|
||||
require_admin(&auth)?;
|
||||
let files = storage::list_all_files(&state.config.data_dir)
|
||||
.await
|
||||
.map_err(AppError::Io)?;
|
||||
Ok(Json(FilesMetaResponse { files }))
|
||||
}
|
||||
|
||||
pub async fn download_files(
|
||||
State(state): State<Arc<AppState>>,
|
||||
auth: AuthUser,
|
||||
Json(req): Json<BatchFilesRequest>,
|
||||
) -> Result<Response, AppError> {
|
||||
require_admin(&auth)?;
|
||||
|
||||
let cursor = std::io::Cursor::new(Vec::new());
|
||||
let mut zip = zip::ZipWriter::new(cursor);
|
||||
let options = zip::write::SimpleFileOptions::default();
|
||||
|
||||
for user_files in &req.users {
|
||||
for filename in &user_files.files {
|
||||
if !storage::validate_filename(filename) {
|
||||
continue;
|
||||
}
|
||||
let data = match storage::read_file(&state.config.data_dir, &user_files.username, filename).await {
|
||||
Ok(d) => d,
|
||||
Err(_) => continue,
|
||||
};
|
||||
let zip_path = format!("{}/{}", user_files.username, filename);
|
||||
zip.start_file(&zip_path, options)
|
||||
.map_err(|e| AppError::Internal(e.to_string()))?;
|
||||
zip.write_all(&data)
|
||||
.map_err(|e| AppError::Internal(e.to_string()))?;
|
||||
}
|
||||
}
|
||||
|
||||
let cursor = zip.finish().map_err(|e| AppError::Internal(e.to_string()))?;
|
||||
let bytes = cursor.into_inner();
|
||||
|
||||
let mut response = Response::new(Body::from(bytes));
|
||||
response
|
||||
.headers_mut()
|
||||
.insert("Content-Type", HeaderValue::from_static("application/zip"));
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub async fn delete_files(
|
||||
State(state): State<Arc<AppState>>,
|
||||
auth: AuthUser,
|
||||
Json(req): Json<BatchFilesRequest>,
|
||||
) -> Result<Json<serde_json::Value>, AppError> {
|
||||
require_admin(&auth)?;
|
||||
|
||||
let mut deleted = 0u32;
|
||||
for user_files in &req.users {
|
||||
for filename in &user_files.files {
|
||||
if !storage::validate_filename(filename) {
|
||||
continue;
|
||||
}
|
||||
if storage::delete_file(&state.config.data_dir, &user_files.username, filename)
|
||||
.await
|
||||
.unwrap_or(false)
|
||||
{
|
||||
deleted += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Json(json!({ "deleted": deleted })))
|
||||
}
|
||||
|
||||
pub async fn upload_file(
|
||||
State(state): State<Arc<AppState>>,
|
||||
auth: AuthUser,
|
||||
mut multipart: Multipart,
|
||||
) -> Result<Json<serde_json::Value>, AppError> {
|
||||
let mut uploaded: Vec<String> = Vec::new();
|
||||
|
||||
while let Some(field) = multipart
|
||||
.next_field()
|
||||
.await
|
||||
.map_err(|e| AppError::BadRequest(e.to_string()))?
|
||||
{
|
||||
let filename = field
|
||||
.file_name()
|
||||
.or_else(|| field.name())
|
||||
.ok_or_else(|| AppError::BadRequest("Missing filename".to_string()))?
|
||||
.to_string();
|
||||
|
||||
if !storage::validate_filename(&filename) {
|
||||
return Err(AppError::BadRequest(format!("Invalid filename: {filename}")));
|
||||
}
|
||||
|
||||
let data = field.bytes().await.map_err(|e| AppError::BadRequest(e.to_string()))?;
|
||||
storage::write_file(&state.config.data_dir, &auth.username, &filename, &data)
|
||||
.await
|
||||
.map_err(AppError::Io)?;
|
||||
|
||||
uploaded.push(filename);
|
||||
}
|
||||
|
||||
Ok(Json(json!({ "uploaded": uploaded })))
|
||||
}
|
||||
|
||||
fn require_admin(auth: &AuthUser) -> Result<(), AppError> {
|
||||
if auth.is_admin {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(AppError::Forbidden)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AppError {
|
||||
Forbidden,
|
||||
BadRequest(String),
|
||||
Internal(String),
|
||||
Io(std::io::Error),
|
||||
}
|
||||
|
||||
impl IntoResponse for AppError {
|
||||
fn into_response(self) -> Response {
|
||||
let (status, msg) = match self {
|
||||
AppError::Forbidden => (StatusCode::FORBIDDEN, "Forbidden".to_string()),
|
||||
AppError::BadRequest(m) => (StatusCode::BAD_REQUEST, m),
|
||||
AppError::Internal(m) => (StatusCode::INTERNAL_SERVER_ERROR, m),
|
||||
AppError::Io(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()),
|
||||
};
|
||||
if status.is_server_error() {
|
||||
tracing::error!(status = status.as_u16(), "{msg}");
|
||||
} else {
|
||||
tracing::warn!(status = status.as_u16(), "{msg}");
|
||||
}
|
||||
(status, msg).into_response()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user