openserve 2.0.3

A modern, high-performance, AI-enhanced file server built in Rust
Documentation
//! File handlers
//! 
//! HTTP handlers for file operations.

use axum::{
    extract::{Path, State, Query, Multipart},
    http::StatusCode,
    response::IntoResponse,

};
use serde::Deserialize;
use tracing::{debug, error};
use serde_json;

use crate::{
    server::AppState,
    models::{UploadResponse},
    handlers::{error_response, success_response},
};

/// Query parameters for file listing operations.
#[derive(Debug, Deserialize)]
pub struct ListQuery {
    /// The field to sort the results by.
    pub sort: Option<String>,
    /// The order to sort the results in (asc or desc).
    pub order: Option<String>,
    /// The maximum number of results to return.
    pub limit: Option<usize>,
}

/// List files in directory
pub async fn list(
    Path(path): Path<String>,
    Query(_query): Query<ListQuery>,
    State(state): State<AppState>,
) -> impl IntoResponse {
    debug!("Listing files at path: {}", path);

    match state.file_service.list_directory(&path).await {
        Ok(directory) => success_response(StatusCode::OK, &serde_json::to_string(&directory).unwrap_or_default()),
        Err(e) => {
            error!("Failed to list directory: {}", e);
            error_response(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string())
        }
    }
}

/// Upload file
pub async fn upload(
    Path(path): Path<String>,
    State(state): State<AppState>,
    mut multipart: Multipart,
) -> impl IntoResponse {
    debug!("Uploading file to path: {}", path);

    let mut file_data = Vec::new();
    let mut filename = String::new();

    while let Some(field) = multipart.next_field().await.unwrap_or(None) {
        let field_name = field.name().unwrap_or("").to_string();
        
        if field_name == "file" {
            filename = field.file_name().unwrap_or("upload").to_string();
            file_data = field.bytes().await.unwrap_or_default().to_vec();
        }
    }

    if file_data.is_empty() {
        return error_response(StatusCode::BAD_REQUEST, "No file data received");
    }

    let full_path = if path.ends_with('/') {
        format!("{}{}", path, filename)
    } else {
        path
    };

    match state.file_service.write_file(&full_path, &file_data).await {
        Ok(_) => {
            let response = UploadResponse {
                path: full_path,
                size: file_data.len() as u64,
                message: "File uploaded successfully".to_string(),
            };
            success_response(StatusCode::CREATED, &serde_json::to_string(&response).unwrap_or_default())
        }
        Err(e) => {
            error!("Failed to upload file: {}", e);
            error_response(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string())
        }
    }
}

/// Update file content
pub async fn update(
    Path(path): Path<String>,
    State(state): State<AppState>,
    body: axum::body::Bytes,
) -> impl IntoResponse {
    debug!("Updating file at path: {}", path);

    match state.file_service.write_file(&path, &body).await {
        Ok(_) => success_response(StatusCode::OK, "File updated successfully"),
        Err(e) => {
            error!("Failed to update file: {}", e);
            error_response(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string())
        }
    }
}

/// Delete file or directory
pub async fn delete(
    Path(path): Path<String>,
    State(state): State<AppState>,
) -> impl IntoResponse {
    debug!("Deleting path: {}", path);

    match state.file_service.delete(&path).await {
        Ok(_) => success_response(StatusCode::OK, "File deleted successfully"),
        Err(e) => {
            error!("Failed to delete file: {}", e);
            error_response(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string())
        }
    }
}

/// Get file metadata
pub async fn metadata(
    Path(path): Path<String>,
    State(state): State<AppState>,
) -> impl IntoResponse {
    debug!("Getting metadata for path: {}", path);

    match state.file_service.get_metadata(&path).await {
        Ok(metadata) => success_response(StatusCode::OK, &serde_json::to_string(&metadata).unwrap_or_default()),
        Err(e) => {
            error!("Failed to get metadata: {}", e);
            error_response(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string())
        }
    }
}

/// Download file
pub async fn download(
    Path(path): Path<String>,
    State(state): State<AppState>,
) -> impl IntoResponse {
    debug!("Downloading file: {}", path);

    match state.file_service.read_file(&path).await {
        Ok(content) => {
            let mime_type = mime_guess::from_path(&path)
                .first_or_octet_stream()
                .to_string();
            
            (
                StatusCode::OK,
                [("Content-Type", mime_type.as_str())],
                content
            ).into_response()
        }
        Err(e) => {
            error!("Failed to download file: {}", e);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                [("Content-Type", "application/json")],
                format!(r#"{{"error": "{}"}}"#, e).into_bytes()
            ).into_response()
        }
    }
}