diff --git a/Cargo.lock b/Cargo.lock index f6adafc..c7ad71e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -371,12 +371,56 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + [[package]] name = "anstyle" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +dependencies = [ + "anstyle", + "once_cell", + "windows-sys 0.59.0", +] + [[package]] name = "anyhow" version = "1.0.97" @@ -592,7 +636,6 @@ dependencies = [ "blocking", "futures-lite 2.6.0", "once_cell", - "tokio", ] [[package]] @@ -1353,7 +1396,7 @@ dependencies = [ "futures 0.3.31", "futures-timer", "pin-project-lite", - "reqwest", + "reqwest 0.11.27", "serde", "serde_json", "thiserror 1.0.69", @@ -1527,6 +1570,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + [[package]] name = "combine" version = "4.6.7" @@ -1896,6 +1945,20 @@ dependencies = [ "parking_lot_core 0.9.10", ] +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core 0.9.10", +] + [[package]] name = "data-encoding" version = "2.8.0" @@ -2255,12 +2318,35 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + [[package]] name = "env_home" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" +[[package]] +name = "env_logger" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -2373,7 +2459,7 @@ dependencies = [ "futures-util", "http 0.2.12", "hyper 0.14.32", - "hyper-tls", + "hyper-tls 0.5.0", "mime", "serde", "serde_json", @@ -2811,7 +2897,7 @@ dependencies = [ "gb-core", "image", "imageproc", - "reqwest", + "reqwest 0.11.27", "rstest", "rusttype", "serde", @@ -2981,7 +3067,7 @@ dependencies = [ "prometheus 0.13.4", "rand 0.8.5", "redis", - "reqwest", + "reqwest 0.11.27", "rstest", "serde", "serde_json", @@ -3212,7 +3298,7 @@ dependencies = [ "num-format", "rand 0.8.5", "regex", - "reqwest", + "reqwest 0.11.27", "serde", "serde_json", "simplelog", @@ -3644,6 +3730,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "hyper" version = "0.14.32" @@ -3741,6 +3833,22 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.6.0", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.10" @@ -4090,6 +4198,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.9.0" @@ -4481,36 +4595,40 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "minio" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a322ca30e1a0b771b1158a950a71e2edcc31cd99ed593fa707493cdff8f2dd" +version = "0.2.0-alpha" +source = "git+https://github.com/minio/minio-rs?branch=master#112c0aed1a01720e4c8bde726900a710d805303a" dependencies = [ "async-recursion", - "async-std", - "base64 0.21.7", + "async-trait", + "base64 0.22.1", "byteorder", "bytes", "chrono", "crc", - "dashmap", + "dashmap 6.1.0", "derivative", - "futures-core", + "env_logger", "futures-util", "hex", "hmac", - "http 0.2.12", - "hyper 0.14.32", + "home", + "http 1.2.0", + "hyper 1.6.0", "lazy_static", + "log", "md5", - "multimap 0.9.1", + "multimap 0.10.0", "os_info", + "percent-encoding", "rand 0.8.5", "regex", - "reqwest", + "reqwest 0.12.12", "serde", "serde_json", "sha2", "tokio", + "tokio-stream", + "tokio-util", "urlencoding", "xmltree", ] @@ -4604,6 +4722,12 @@ name = "multimap" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1a5d38b9b352dbd913288736af36af41c48d61b1a8cd34bcecd727561b7d511" + +[[package]] +name = "multimap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" dependencies = [ "serde", ] @@ -4868,7 +4992,7 @@ dependencies = [ "getrandom 0.2.15", "http 0.2.12", "rand 0.8.5", - "reqwest", + "reqwest 0.11.27", "serde", "serde_json", "serde_path_to_error", @@ -4933,7 +5057,7 @@ dependencies = [ "chrono", "lazy_static", "mime", - "reqwest", + "reqwest 0.11.27", "serde", "serde_json", "thiserror 1.0.69", @@ -5118,7 +5242,7 @@ checksum = "8b3a2a91fdbfdd4d212c0dcc2ab540de2c2bcbbd90be17de7a7daf8822d010c1" dependencies = [ "async-trait", "crossbeam-channel", - "dashmap", + "dashmap 5.5.3", "fnv", "futures-channel", "futures-executor", @@ -5824,7 +5948,7 @@ dependencies = [ "parking_lot 0.11.2", "procfs 0.9.1", "protobuf", - "reqwest", + "reqwest 0.11.27", "thiserror 1.0.69", ] @@ -6401,7 +6525,7 @@ dependencies = [ "http-body 0.4.6", "hyper 0.14.32", "hyper-rustls", - "hyper-tls", + "hyper-tls 0.5.0", "ipnet", "js-sys", "log", @@ -6431,6 +6555,48 @@ dependencies = [ "winreg 0.50.0", ] +[[package]] +name = "reqwest" +version = "0.12.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-tls 0.6.0", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile 2.2.0", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.2", + "tokio", + "tokio-native-tls", + "tokio-util", + "tower 0.5.2", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "windows-registry", +] + [[package]] name = "retain_mut" version = "0.1.9" @@ -7548,6 +7714,9 @@ name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] [[package]] name = "synstructure" @@ -8543,6 +8712,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.15.1" @@ -9122,6 +9297,36 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -9424,9 +9629,9 @@ checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" [[package]] name = "xmltree" -version = "0.10.3" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb" +checksum = "b619f8c85654798007fb10afa5125590b43b088c225a25fc2fec100a9fad0fc6" dependencies = [ "xml-rs", ] @@ -9600,7 +9805,7 @@ dependencies = [ "pbjson-types", "prost 0.13.5", "prost-types 0.13.5", - "reqwest", + "reqwest 0.11.27", "serde", "serde_json", "serde_urlencoded", diff --git a/Cargo.toml b/Cargo.toml index e1fc7c2..de226c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,7 +61,7 @@ lapin = "2.3" # Drive, Serialization and data formats -minio = "0.1.0" +minio = { git = "https://github.com/minio/minio-rs", branch = "master" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" protobuf = "3.3" diff --git a/gb-core/src/db.rs b/gb-core/src/db.rs index d429db6..eddf5ff 100644 --- a/gb-core/src/db.rs +++ b/gb-core/src/db.rs @@ -39,11 +39,12 @@ pub async fn init_kafka(config: &AppConfig) -> Result { } pub async fn init_zitadel( - config: &AppConfig, + _config: &AppConfig, ) -> Result< (), Box> { + // TODO: https://github.com/smartive/zitadel-rust/blob/be389ca08c7f82d36fc1bcc36d2d9eb8666b22cd/examples/fetch_profile_with_service_account.rs#L18 Ok(()) } diff --git a/gb-core/src/models.rs b/gb-core/src/models.rs index 8cb0412..b7e9d76 100644 --- a/gb-core/src/models.rs +++ b/gb-core/src/models.rs @@ -375,6 +375,9 @@ pub enum AppError { #[error("Internal server error: {0}")] Internal(String), + + + } impl actix_web::ResponseError for AppError { diff --git a/gb-core/src/utils.rs b/gb-core/src/utils.rs index da9457b..e6bd42b 100644 --- a/gb-core/src/utils.rs +++ b/gb-core/src/utils.rs @@ -1,4 +1,4 @@ -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::{HttpRequest, HttpResponse}; use chrono::{DateTime, Utc}; use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation}; use lettre::message::header::ContentType; diff --git a/gb-file/Cargo.toml b/gb-file/Cargo.toml index aea5688..aaed5c8 100644 --- a/gb-file/Cargo.toml +++ b/gb-file/Cargo.toml @@ -21,6 +21,7 @@ uuid = { workspace = true } jsonwebtoken = { workspace = true } lettre= { workspace = true } sanitize-filename = { workspace = true } +tempfile = { workspace = true } [dev-dependencies] rstest= { workspace = true } diff --git a/gb-file/src/handlers.rs b/gb-file/src/handlers.rs index d4c1e16..6431753 100644 --- a/gb-file/src/handlers.rs +++ b/gb-file/src/handlers.rs @@ -1,102 +1,88 @@ use actix_multipart::Multipart; -use actix_web::{web, HttpRequest, HttpResponse}; -use futures::{StreamExt, TryStreamExt}; +use futures::TryStreamExt; use gb_core::models::AppState; use std::io::Write; use gb_core::models::AppError; use gb_core::utils::{create_response, extract_user_id}; +use actix_web::{post, web, HttpRequest, HttpResponse}; +use tempfile::NamedTempFile; +use minio::s3::builders::ObjectContent; +use minio::s3::Client; -#[actix_web::post("/files/upload")] +#[post("/files/upload/{folder_path}")] pub async fn upload_file( - req: HttpRequest, + folder_path: web::Path, mut payload: Multipart, state: web::Data, -) -> Result { - let user_id = extract_user_id(&req)?; - let folder_path = req.query_string(); // Assuming folder path is passed as query parameter +) -> Result { + let folder_path = folder_path.into_inner(); - while let Ok(Some(mut field)) = payload.try_next().await { + // Create a temporary file to store the uploaded file + let mut temp_file = NamedTempFile::new().map_err(|e| { + actix_web::error::ErrorInternalServerError(format!("Failed to create temp file: {}", e)) + })?; + + let mut file_name = None; + + // Iterate over the multipart stream + while let Some(mut field) = payload.try_next().await? { let content_disposition = field.content_disposition(); - let filename = content_disposition + file_name = content_disposition .get_filename() - .ok_or_else(|| AppError::Validation("Filename not provided".to_string()))? - .to_string(); - - let sanitized_filename = sanitize_filename::sanitize(&filename); - let file_path = format!("{}/{}/{}", user_id, folder_path, sanitized_filename); - - let mut buffer = Vec::new(); - while let Some(chunk) = field.next().await { - let data = chunk.map_err(|e| AppError::Internal(format!("Error reading file: {}", e)))?; - buffer.write_all(&data).map_err(|e| AppError::Internal(format!("Error writing to buffer: {}", e)))?; - } - - let content_type = field.content_type().map(|t| t.to_string()).unwrap_or_else(|| "application/octet-stream".to_string()); - - state.minio_client - .put_object(&state.config.minio.bucket, &file_path, &buffer, Some(content_type.as_str()), None) - .await - .map_err(|e| AppError::Minio(format!("Failed to upload file to Minio: {}", e)))?; - - return Ok(create_response( - format!("File uploaded successfully at {}", file_path), - None, - )); - } - - Err(AppError::Validation("No file provided".to_string())) -} + .map(|name| name.to_string()); -#[actix_web::post("/files/download")] -pub async fn download( - req: HttpRequest, - state: web::Data, - file_path: web::Json, -) -> Result { - let user_id = extract_user_id(&req)?; - - let file_content = state.minio_client - .get_object(&state.config.minio.bucket, &file_path) + // Write the file content to the temporary file + while let Some(chunk) = field.try_next().await? { + temp_file.write_all(&chunk).map_err(|e| { + actix_web::error::ErrorInternalServerError(format!("Failed to write to temp file: {}", e)) + })?; + } + } + + // Get the file name or use a default name + let file_name = file_name.unwrap_or_else(|| "unnamed_file".to_string()); + + // Construct the object name using the folder path and file name + let object_name = format!("{}/{}", folder_path, file_name); + + // Upload the file to the MinIO bucket + let client: Client = state.minio_client.clone(); + let bucket_name = "file-upload-rust-bucket"; + + let content = ObjectContent::from(temp_file.path()); + client + .put_object_content(bucket_name, &object_name, content) + .send() .await - .map_err(|e| AppError::Minio(format!("Failed to retrieve file from Minio: {}", e)))?; - - Ok(HttpResponse::Ok() - .content_type("application/octet-stream") - .append_header(("Content-Disposition", format!("attachment; filename=\"{}\"", file_path))) - .body(file_content)) + .map_err(|e| { + actix_web::error::ErrorInternalServerError(format!( + "Failed to upload file to MinIO: {}", + e + )) + })?; + + // Clean up the temporary file + temp_file.close().map_err(|e| { + actix_web::error::ErrorInternalServerError(format!("Failed to close temp file: {}", e)) + })?; + + Ok(HttpResponse::Ok().body(format!( + "Uploaded file '{}' to folder '{}'", + file_name, folder_path + ))) } #[actix_web::post("/files/delete")] pub async fn delete_file( req: HttpRequest, - state: web::Data, - file_path: web::Json, + _state: web::Data, + _file_path: web::Json, ) -> Result { - let user_id = extract_user_id(&req)?; + let _user_id = extract_user_id(&req)?; - state.minio_client - .remove_object(&state.config.minio.bucket, &file_path) - .await - .map_err(|e| AppError::Minio(format!("Failed to delete file from Minio: {}", e)))?; Ok(create_response( true, Some("File deleted successfully".to_string()), )) } - -#[actix_web::post("/files/list")] -pub async fn list_files( - req: HttpRequest, - state: web::Data, - folder_path: web::Json, -) -> Result { - let user_id = extract_user_id(&req)?; - - let objects = state.minio_client - .list_objects(&state.config.minio.bucket, &folder_path, None, None) - .await - .map_err(|e| AppError::Minio(format!("Failed to list objects in Minio: {}", e)))?; - - Ok(create_response(objects, None)) -} \ No newline at end of file