feat(drive): Implement S3 handlers (list/read/write/delete)
This commit is contained in:
parent
98813fbdc8
commit
df9b228a35
1 changed files with 215 additions and 175 deletions
|
|
@ -1,256 +1,296 @@
|
|||
// Drive HTTP handlers extracted from drive/mod.rs
|
||||
// Drive HTTP handlers implementation using S3
|
||||
// Extracted from drive/mod.rs and using aws-sdk-s3
|
||||
|
||||
use crate::core::shared::state::AppState;
|
||||
use crate::drive::drive_types::*;
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
http::StatusCode,
|
||||
Json,
|
||||
http::{header, StatusCode},
|
||||
response::{IntoResponse, Json, Response},
|
||||
body::Body,
|
||||
};
|
||||
use aws_sdk_s3::primitives::ByteStream;
|
||||
use chrono::Utc;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Open a file for editing
|
||||
|
||||
// Import ReadResponse from parent mod if not in drive_types
|
||||
use super::ReadResponse;
|
||||
|
||||
/// Open a file (get metadata)
|
||||
pub async fn open_file(
|
||||
State(_state): State<Arc<AppState>>,
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(file_id): Path<String>,
|
||||
) -> Result<Json<FileItem>, (StatusCode, Json<serde_json::Value>)> {
|
||||
tracing::debug!("Opening file: {}", file_id);
|
||||
read_metadata(state, file_id).await
|
||||
}
|
||||
|
||||
// TODO: Implement actual file reading
|
||||
let file_item = FileItem {
|
||||
/// Helper to get file metadata
|
||||
async fn read_metadata(
|
||||
state: Arc<AppState>,
|
||||
file_id: String,
|
||||
) -> Result<Json<FileItem>, (StatusCode, Json<serde_json::Value>)> {
|
||||
let client = state.drive.as_ref().ok_or((
|
||||
StatusCode::SERVICE_UNAVAILABLE,
|
||||
Json(serde_json::json!({"error": "Drive not configured"})),
|
||||
))?;
|
||||
let bucket = &state.bucket_name;
|
||||
|
||||
let resp = client.head_object()
|
||||
.bucket(bucket)
|
||||
.key(&file_id)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| (StatusCode::NOT_FOUND, Json(serde_json::json!({"error": e.to_string()}))))?;
|
||||
|
||||
let item = FileItem {
|
||||
id: file_id.clone(),
|
||||
name: "Untitled".to_string(),
|
||||
file_type: "document".to_string(),
|
||||
size: 0,
|
||||
mime_type: "text/plain".to_string(),
|
||||
created_at: Utc::now(),
|
||||
modified_at: Utc::now(),
|
||||
name: file_id.split('/').last().unwrap_or(&file_id).to_string(),
|
||||
file_type: if file_id.ends_with('/') { "folder".to_string() } else { "file".to_string() },
|
||||
size: resp.content_length.unwrap_or(0),
|
||||
mime_type: resp.content_type.unwrap_or_else(|| "application/octet-stream".to_string()),
|
||||
created_at: Utc::now(), // S3 doesn't track creation time easily
|
||||
modified_at: Utc::now(), // Simplified
|
||||
parent_id: None,
|
||||
url: None,
|
||||
thumbnail_url: None,
|
||||
is_favorite: false,
|
||||
is_favorite: false, // Not implemented in S3
|
||||
tags: vec![],
|
||||
metadata: HashMap::new(),
|
||||
};
|
||||
|
||||
Ok(Json(file_item))
|
||||
Ok(Json(item))
|
||||
}
|
||||
|
||||
/// List all buckets
|
||||
/// List all buckets (or configured one)
|
||||
pub async fn list_buckets(
|
||||
State(_state): State<Arc<AppState>>,
|
||||
State(state): State<Arc<AppState>>,
|
||||
) -> Result<Json<Vec<BucketInfo>>, (StatusCode, Json<serde_json::Value>)> {
|
||||
tracing::debug!("Listing buckets");
|
||||
let client = state.drive.as_ref().ok_or((
|
||||
StatusCode::SERVICE_UNAVAILABLE,
|
||||
Json(serde_json::json!({"error": "Drive not configured"})),
|
||||
))?;
|
||||
|
||||
// TODO: Query database for buckets
|
||||
let buckets = vec![];
|
||||
|
||||
Ok(Json(buckets))
|
||||
match client.list_buckets().send().await {
|
||||
Ok(resp) => {
|
||||
let buckets = resp.buckets.unwrap_or_default().iter().map(|b| {
|
||||
BucketInfo {
|
||||
id: b.name.clone().unwrap_or_default(),
|
||||
name: b.name.clone().unwrap_or_default(),
|
||||
created_at: Utc::now(),
|
||||
file_count: 0,
|
||||
total_size: 0,
|
||||
}
|
||||
}).collect();
|
||||
Ok(Json(buckets))
|
||||
},
|
||||
Err(_) => {
|
||||
// Fallback
|
||||
Ok(Json(vec![BucketInfo {
|
||||
id: state.bucket_name.clone(),
|
||||
name: state.bucket_name.clone(),
|
||||
created_at: Utc::now(),
|
||||
file_count: 0,
|
||||
total_size: 0,
|
||||
}]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// List files in a bucket
|
||||
pub async fn list_files(
|
||||
State(_state): State<Arc<AppState>>,
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(req): Json<SearchQuery>,
|
||||
) -> Result<Json<Vec<FileItem>>, (StatusCode, Json<serde_json::Value>)> {
|
||||
let query = req.query.clone().unwrap_or_default();
|
||||
let parent_path = req.parent_path.clone();
|
||||
let client = state.drive.as_ref().ok_or((
|
||||
StatusCode::SERVICE_UNAVAILABLE,
|
||||
Json(serde_json::json!({"error": "Drive not configured"})),
|
||||
))?;
|
||||
let bucket = req.bucket.clone().unwrap_or_else(|| state.bucket_name.clone());
|
||||
let prefix = req.parent_path.clone().unwrap_or_default();
|
||||
|
||||
tracing::debug!("Searching files: query={}, parent={:?}", query, parent_path);
|
||||
let resp = client.list_objects_v2()
|
||||
.bucket(&bucket)
|
||||
.prefix(&prefix)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({"error": e.to_string()}))))?;
|
||||
|
||||
// TODO: Implement actual file search
|
||||
let files = vec![];
|
||||
let files = resp.contents.unwrap_or_default().iter().map(|obj| {
|
||||
let key = obj.key().unwrap_or_default();
|
||||
let name = key.split('/').last().unwrap_or(key).to_string();
|
||||
FileItem {
|
||||
id: key.to_string(),
|
||||
name,
|
||||
file_type: if key.ends_with('/') { "folder".to_string() } else { "file".to_string() },
|
||||
size: obj.size.unwrap_or(0),
|
||||
mime_type: "application/octet-stream".to_string(),
|
||||
created_at: Utc::now(),
|
||||
modified_at: Utc::now(),
|
||||
parent_id: Some(prefix.clone()),
|
||||
url: None,
|
||||
thumbnail_url: None,
|
||||
is_favorite: false,
|
||||
tags: vec![],
|
||||
metadata: HashMap::new(),
|
||||
}
|
||||
}).collect();
|
||||
|
||||
Ok(Json(files))
|
||||
}
|
||||
|
||||
/// Read file content
|
||||
/// Read file content (as text)
|
||||
pub async fn read_file(
|
||||
State(_state): State<Arc<AppState>>,
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(file_id): Path<String>,
|
||||
) -> Result<Json<FileItem>, (StatusCode, Json<serde_json::Value>)> {
|
||||
tracing::debug!("Reading file: {}", file_id);
|
||||
) -> Result<Json<ReadResponse>, (StatusCode, Json<serde_json::Value>)> {
|
||||
let client = state.drive.as_ref().ok_or((
|
||||
StatusCode::SERVICE_UNAVAILABLE,
|
||||
Json(serde_json::json!({"error": "Drive not configured"})),
|
||||
))?;
|
||||
let bucket = &state.bucket_name;
|
||||
|
||||
// TODO: Implement actual file reading
|
||||
let file_item = FileItem {
|
||||
id: file_id.clone(),
|
||||
name: "Untitled".to_string(),
|
||||
file_type: "document".to_string(),
|
||||
size: 0,
|
||||
mime_type: "text/plain".to_string(),
|
||||
created_at: Utc::now(),
|
||||
modified_at: Utc::now(),
|
||||
parent_id: None,
|
||||
url: None,
|
||||
thumbnail_url: None,
|
||||
is_favorite: false,
|
||||
tags: vec![],
|
||||
metadata: HashMap::new(),
|
||||
};
|
||||
let resp = client.get_object()
|
||||
.bucket(bucket)
|
||||
.key(&file_id)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| (StatusCode::NOT_FOUND, Json(serde_json::json!({"error": e.to_string()}))))?;
|
||||
|
||||
Ok(Json(file_item))
|
||||
let data = resp.body.collect().await.map_err(|e|
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({"error": e.to_string()})))
|
||||
)?.into_bytes();
|
||||
|
||||
let content = String::from_utf8(data.to_vec()).unwrap_or_else(|_| "[Binary Content]".to_string());
|
||||
|
||||
Ok(Json(ReadResponse { content }))
|
||||
}
|
||||
|
||||
/// Write file content
|
||||
pub async fn write_file(
|
||||
State(_state): State<Arc<AppState>>,
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(req): Json<WriteRequest>,
|
||||
) -> Result<Json<serde_json::Value>, (StatusCode, Json<serde_json::Value>)> {
|
||||
let file_id = req.file_id.unwrap_or_else(|| Uuid::new_v4().to_string());
|
||||
let client = state.drive.as_ref().ok_or((
|
||||
StatusCode::SERVICE_UNAVAILABLE,
|
||||
Json(serde_json::json!({"error": "Drive not configured"})),
|
||||
))?;
|
||||
let bucket = &state.bucket_name;
|
||||
let key = req.file_id.ok_or((StatusCode::BAD_REQUEST, Json(serde_json::json!({"error": "Missing file_id"}))))?;
|
||||
|
||||
tracing::debug!("Writing file: {}", file_id);
|
||||
client.put_object()
|
||||
.bucket(bucket)
|
||||
.key(&key)
|
||||
.body(ByteStream::from(req.content.into_bytes()))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({"error": e.to_string()}))))?;
|
||||
|
||||
// TODO: Implement actual file writing
|
||||
Ok(Json(serde_json::json!({"success": true})))
|
||||
}
|
||||
|
||||
/// Delete a file
|
||||
pub async fn delete_file(
|
||||
State(_state): State<Arc<AppState>>,
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(file_id): Path<String>,
|
||||
) -> Result<Json<serde_json::Value>, (StatusCode, Json<serde_json::Value>)> {
|
||||
tracing::debug!("Deleting file: {}", file_id);
|
||||
let client = state.drive.as_ref().ok_or((
|
||||
StatusCode::SERVICE_UNAVAILABLE,
|
||||
Json(serde_json::json!({"error": "Drive not configured"})),
|
||||
))?;
|
||||
let bucket = &state.bucket_name;
|
||||
|
||||
client.delete_object()
|
||||
.bucket(bucket)
|
||||
.key(&file_id)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({"error": e.to_string()}))))?;
|
||||
|
||||
// TODO: Implement actual file deletion
|
||||
Ok(Json(serde_json::json!({"success": true})))
|
||||
}
|
||||
|
||||
/// Create a folder
|
||||
pub async fn create_folder(
|
||||
State(_state): State<Arc<AppState>>,
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(req): Json<CreateFolderRequest>,
|
||||
) -> Result<Json<serde_json::Value>, (StatusCode, Json<serde_json::Value>)> {
|
||||
let _parent_id = req.parent_id.clone().unwrap_or_default();
|
||||
let client = state.drive.as_ref().ok_or((
|
||||
StatusCode::SERVICE_UNAVAILABLE,
|
||||
Json(serde_json::json!({"error": "Drive not configured"})),
|
||||
))?;
|
||||
let bucket = &state.bucket_name;
|
||||
|
||||
// Construct folder path/key
|
||||
let mut key = req.parent_id.unwrap_or_default();
|
||||
if !key.ends_with('/') && !key.is_empty() {
|
||||
key.push('/');
|
||||
}
|
||||
key.push_str(&req.name);
|
||||
if !key.ends_with('/') {
|
||||
key.push('/');
|
||||
}
|
||||
|
||||
tracing::debug!("Creating folder: {:?}", req.name);
|
||||
client.put_object()
|
||||
.bucket(bucket)
|
||||
.key(&key)
|
||||
.body(ByteStream::from_static(&[]))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({"error": e.to_string()}))))?;
|
||||
|
||||
// TODO: Implement actual folder creation
|
||||
Ok(Json(serde_json::json!({"success": true})))
|
||||
}
|
||||
|
||||
/// Copy a file
|
||||
pub async fn copy_file(
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Json(_req): Json<CopyFileRequest>,
|
||||
) -> Result<Json<serde_json::Value>, (StatusCode, Json<serde_json::Value>)> {
|
||||
tracing::debug!("Copying file");
|
||||
|
||||
// TODO: Implement actual file copying
|
||||
Ok(Json(serde_json::json!({"success": true})))
|
||||
}
|
||||
|
||||
/// Upload file to drive
|
||||
pub async fn upload_file_to_drive(
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Json(_req): Json<UploadRequest>,
|
||||
) -> Result<Json<serde_json::Value>, (StatusCode, Json<serde_json::Value>)> {
|
||||
tracing::debug!("Uploading to drive");
|
||||
|
||||
// TODO: Implement actual file upload
|
||||
Ok(Json(serde_json::json!({"success": true})))
|
||||
}
|
||||
|
||||
/// Download file
|
||||
/// Download file (stream)
|
||||
pub async fn download_file(
|
||||
State(_state): State<Arc<AppState>>,
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(file_id): Path<String>,
|
||||
) -> Result<Json<FileItem>, (StatusCode, Json<serde_json::Value>)> {
|
||||
tracing::debug!("Downloading file: {}", file_id);
|
||||
) -> Result<Response, (StatusCode, Json<serde_json::Value>)> {
|
||||
let client = state.drive.as_ref().ok_or((
|
||||
StatusCode::SERVICE_UNAVAILABLE,
|
||||
Json(serde_json::json!({"error": "Drive not configured"})),
|
||||
))?;
|
||||
let bucket = &state.bucket_name;
|
||||
|
||||
// TODO: Implement actual file download
|
||||
let file_item = FileItem {
|
||||
id: file_id.clone(),
|
||||
name: "Download".to_string(),
|
||||
file_type: "file".to_string(),
|
||||
size: 0,
|
||||
mime_type: "application/octet-stream".to_string(),
|
||||
created_at: Utc::now(),
|
||||
modified_at: Utc::now(),
|
||||
parent_id: None,
|
||||
url: None,
|
||||
thumbnail_url: None,
|
||||
is_favorite: false,
|
||||
tags: vec![],
|
||||
metadata: HashMap::new(),
|
||||
};
|
||||
let resp = client.get_object()
|
||||
.bucket(bucket)
|
||||
.key(&file_id)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| (StatusCode::NOT_FOUND, Json(serde_json::json!({"error": e.to_string()}))))?;
|
||||
|
||||
Ok(Json(file_item))
|
||||
let stream = Body::from_stream(resp.body);
|
||||
|
||||
Ok(Response::builder()
|
||||
.header(header::CONTENT_TYPE, "application/octet-stream")
|
||||
.header(header::CONTENT_DISPOSITION, format!("attachment; filename=\"{}\"", file_id.split('/').last().unwrap_or("file")))
|
||||
.body(stream)
|
||||
.unwrap())
|
||||
}
|
||||
|
||||
/// List folder contents
|
||||
pub async fn list_folder_contents(
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Json(_req): Json<SearchQuery>,
|
||||
) -> Result<Json<Vec<FileItem>>, (StatusCode, Json<serde_json::Value>)> {
|
||||
tracing::debug!("Listing folder contents");
|
||||
|
||||
// TODO: Implement actual folder listing
|
||||
let files = vec![];
|
||||
|
||||
Ok(Json(files))
|
||||
// Stubs for others (list_shared, etc.)
|
||||
pub async fn copy_file(State(_): State<Arc<AppState>>, Json(_): Json<CopyFileRequest>) -> impl IntoResponse {
|
||||
(StatusCode::NOT_IMPLEMENTED, Json(serde_json::json!({"error": "Not implemented"})))
|
||||
}
|
||||
|
||||
/// Search files
|
||||
pub async fn search_files(
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Json(req): Json<SearchQuery>,
|
||||
) -> Result<Json<Vec<FileItem>>, (StatusCode, Json<serde_json::Value>)> {
|
||||
let query = req.query.clone().unwrap_or_default();
|
||||
let parent_path = req.parent_path.clone();
|
||||
|
||||
tracing::debug!("Searching files: query={:?}, parent_path={:?}", query, parent_path);
|
||||
|
||||
// TODO: Implement actual file search
|
||||
let files = vec![];
|
||||
|
||||
Ok(Json(files))
|
||||
pub async fn upload_file_to_drive(State(_): State<Arc<AppState>>, Json(_): Json<UploadRequest>) -> impl IntoResponse {
|
||||
(StatusCode::NOT_IMPLEMENTED, Json(serde_json::json!({"error": "Not implemented"})))
|
||||
}
|
||||
|
||||
/// Get recent files
|
||||
pub async fn recent_files(
|
||||
State(_state): State<Arc<AppState>>,
|
||||
) -> Result<Json<Vec<FileItem>>, (StatusCode, Json<serde_json::Value>)> {
|
||||
tracing::debug!("Getting recent files");
|
||||
|
||||
// TODO: Implement actual recent files query
|
||||
let files = vec![];
|
||||
|
||||
Ok(Json(files))
|
||||
pub async fn list_folder_contents(State(_): State<Arc<AppState>>, Json(_): Json<SearchQuery>) -> impl IntoResponse {
|
||||
(StatusCode::OK, Json(Vec::<FileItem>::new()))
|
||||
}
|
||||
|
||||
/// List favorites
|
||||
pub async fn list_favorites(
|
||||
State(_state): State<Arc<AppState>>,
|
||||
) -> Result<Json<Vec<FileItem>>, (StatusCode, Json<serde_json::Value>)> {
|
||||
tracing::debug!("Listing favorites");
|
||||
|
||||
// TODO: Implement actual favorites query
|
||||
let files = vec![];
|
||||
|
||||
Ok(Json(files))
|
||||
pub async fn search_files(State(_): State<Arc<AppState>>, Json(_): Json<SearchQuery>) -> impl IntoResponse {
|
||||
(StatusCode::OK, Json(Vec::<FileItem>::new()))
|
||||
}
|
||||
|
||||
/// Share folder
|
||||
pub async fn share_folder(
|
||||
State(_state): State<Arc<AppState>>,
|
||||
Json(_req): Json<ShareRequest>,
|
||||
) -> Result<Json<serde_json::Value>, (StatusCode, Json<serde_json::Value>)> {
|
||||
tracing::debug!("Sharing folder");
|
||||
|
||||
// TODO: Implement actual folder sharing
|
||||
Ok(Json(serde_json::json!({"success": true})))
|
||||
pub async fn recent_files(State(_): State<Arc<AppState>>) -> impl IntoResponse {
|
||||
(StatusCode::OK, Json(Vec::<FileItem>::new()))
|
||||
}
|
||||
|
||||
/// List shared files/folders
|
||||
pub async fn list_shared(
|
||||
State(_state): State<Arc<AppState>>,
|
||||
) -> Result<Json<Vec<FileItem>>, (StatusCode, Json<serde_json::Value>)> {
|
||||
tracing::debug!("Listing shared resources");
|
||||
|
||||
// TODO: Implement actual shared query
|
||||
let items = vec![];
|
||||
|
||||
Ok(Json(items))
|
||||
pub async fn list_favorites(State(_): State<Arc<AppState>>) -> impl IntoResponse {
|
||||
(StatusCode::OK, Json(Vec::<FileItem>::new()))
|
||||
}
|
||||
pub async fn share_folder(State(_): State<Arc<AppState>>, Json(_): Json<ShareRequest>) -> impl IntoResponse {
|
||||
(StatusCode::OK, Json(serde_json::json!({"success": true})))
|
||||
}
|
||||
pub async fn list_shared(State(_): State<Arc<AppState>>) -> impl IntoResponse {
|
||||
(StatusCode::OK, Json(Vec::<FileItem>::new()))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue