diff --git a/.vscode/launch.json b/.vscode/launch.json index cd66e91..79f4876 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,47 +4,19 @@ { "type": "lldb", "request": "launch", - "name": "Debug GB API Server", + "name": "Debug GB Server", "cargo": { "args": [ "build", - "--bin=gb-server" + "--bin=gbserver" ], "filter": { - "name": "gb-server", + "name": "gbserver", "kind": "bin" } }, "args": [], "cwd": "${workspaceFolder}", - "env": { - "RUST_LOG": "info", - "DATABASE_URL": "postgres://gbuser:gbpassword@localhost:5432/generalbots", - "REDIS_URL": "redis://localhost:6379" - } - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in executable 'gb-server'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=gb-server" - ], - "filter": { - "name": "gb-server", - "kind": "bin" - } - }, - "args": [ - "--test-threads=1" - ], - "cwd": "${workspaceFolder}", "env": { - "RUST_LOG": "info" - } }, { "type": "lldb", @@ -55,7 +27,7 @@ "test", "--no-run", "--lib", - "--package=gb-server" + "--package=gbserver" ], "filter": { "name": "integration", @@ -63,17 +35,10 @@ } }, "args": [], - "cwd": "${workspaceFolder}", "env": { + "cwd": "${workspaceFolder}", + "env": { "RUST_LOG": "info" - } + } }, ], - "compounds": [ - { - "name": "API Server + Debug", - "configurations": [ - "Debug GB API Server" - ] - } - ] } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 39fd2e7..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,193 +0,0 @@ -use actix_multipart::Multipart; -use actix_web::{post, web, HttpResponse}; -use minio::s3::builders::ObjectContent; -use minio::s3::Client; -use std::io::Write; -use tempfile::NamedTempFile; -use minio::s3::types::ToStream; -use tokio_stream::StreamExt; - -use minio::s3::client::{Client as MinioClient, ClientBuilder as MinioClientBuilder}; -use minio::s3::creds::StaticProvider; -use minio::s3::http::BaseUrl; -use std::str::FromStr; -use std::env; - -/// Define AppConfig and its nested MinioConfig struct if not already defined elsewhere. -#[derive(Clone)] -pub struct AppConfig { - pub minio: MinioConfig, - pub server: ServerConfig, -} - -/// Define ServerConfig as a tuple struct for (host, port) -#[derive(Clone)] -pub struct ServerConfig { - pub host: String, - pub port: u16, -} - -impl AppConfig { - pub fn from_env() -> Self { - let minio = MinioConfig { - endpoint: env::var("MINIO_ENDPOINT").expect("MINIO_ENDPOINT not set"), - access_key: env::var("MINIO_ACCESS_KEY").expect("MINIO_ACCESS_KEY not set"), - secret_key: env::var("MINIO_SECRET_KEY").expect("MINIO_SECRET_KEY not set"), - use_ssl: env::var("MINIO_USE_SSL") - .unwrap_or_else(|_| "false".to_string()) - .parse() - .unwrap_or(false), - bucket: env::var("MINIO_BUCKET").expect("MINIO_BUCKET not set"), - }; - AppConfig { - minio, - server: ServerConfig { - host: env::var("SERVER_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()), - port: env::var("SERVER_PORT").ok() - .and_then(|p| p.parse().ok()) - .unwrap_or(8080), - }, - } - } -} -#[derive(Clone)] -pub struct MinioConfig { - pub endpoint: String, - pub access_key: String, - pub secret_key: String, - pub use_ssl: bool, - pub bucket: String, -} - - -// App state shared across all handlers -pub struct AppState { - pub minio_client: Option, - pub config: Option, - -} - -pub async fn init_minio(config: &AppConfig) -> Result { - let scheme = if config.minio.use_ssl { "https" } else { "http" }; - let base_url = format!("{}://{}", scheme, config.minio.endpoint); - let base_url = BaseUrl::from_str(&base_url)?; - let credentials = StaticProvider::new( - &config.minio.access_key, - &config.minio.secret_key, - None, - ); - - let minio_client = MinioClientBuilder::new(base_url) - .provider(Some(credentials)) - .build()?; - - Ok(minio_client) -} - -#[post("/files/upload/{folder_path}")] -pub async fn upload_file( - folder_path: web::Path, - mut payload: Multipart, - state: web::Data, -) -> Result { - let folder_path = folder_path.into_inner(); - - // 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(); - file_name = content_disposition - .get_filename() - .map(|name| name.to_string()); - - // 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().unwrap(); - 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| { - 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 - ))) -} - - - -#[post("/files/list/{folder_path}")] -pub async fn list_file( - folder_path: web::Path, - state: web::Data, -) -> Result { - let folder_path = folder_path.into_inner(); - - let client: Client = state.minio_client.clone().unwrap(); - let bucket_name = "file-upload-rust-bucket"; - - // Create the stream using the to_stream() method - let mut objects_stream = client - .list_objects(bucket_name) - .prefix(Some(folder_path)) - .to_stream() - .await; - - let mut file_list = Vec::new(); - - // Use StreamExt::next() to iterate through the stream - while let Some(items) = objects_stream.next().await { - match items { - Ok(result) => { - for item in result.contents { - file_list.push(item.name); - } - }, - Err(e) => { - return Err(actix_web::error::ErrorInternalServerError( - format!("Failed to list files in MinIO: {}", e) - )); - } - } - } - - Ok(HttpResponse::Ok().json(file_list)) -} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index af73773..e9ef551 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,160 @@ use actix_web::{middleware, web, App, HttpServer}; -use gbserver::{init_minio, upload_file, AppConfig, AppState}; use tracing_subscriber::fmt::format::FmtSpan; use dotenv::dotenv; +use actix_multipart::Multipart; +use actix_web::{post, HttpResponse}; +use minio::s3::builders::ObjectContent; +use minio::s3::Client; +use std::io::Write; +use tempfile::NamedTempFile; +use minio::s3::types::ToStream; +use tokio_stream::StreamExt; + +use minio::s3::client::{Client as MinioClient, ClientBuilder as MinioClientBuilder}; +use minio::s3::creds::StaticProvider; +use minio::s3::http::BaseUrl; +use std::str::FromStr; + +use services::config::*; +mod services; + +// App state shared across all handlers +pub struct AppState { + pub minio_client: Option, + pub config: Option, + +} + +pub async fn init_minio(config: &AppConfig) -> Result { + let scheme = if config.minio.use_ssl { "https" } else { "http" }; + let base_url = format!("{}://{}", scheme, config.minio.endpoint); + let base_url = BaseUrl::from_str(&base_url)?; + let credentials = StaticProvider::new( + &config.minio.access_key, + &config.minio.secret_key, + None, + ); + + let minio_client = MinioClientBuilder::new(base_url) + .provider(Some(credentials)) + .build()?; + + Ok(minio_client) +} + +#[post("/files/upload/{folder_path}")] +pub async fn upload_file( + folder_path: web::Path, + mut payload: Multipart, + state: web::Data, +) -> Result { + let folder_path = folder_path.into_inner(); + + // 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(); + file_name = content_disposition + .get_filename() + .map(|name| name.to_string()); + + // 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().unwrap(); + 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| { + 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 + ))) +} + + + +#[post("/files/list/{folder_path}")] +pub async fn list_file( + folder_path: web::Path, + state: web::Data, +) -> Result { + let folder_path = folder_path.into_inner(); + + let client: Client = state.minio_client.clone().unwrap(); + let bucket_name = "file-upload-rust-bucket"; + + // Create the stream using the to_stream() method + let mut objects_stream = client + .list_objects(bucket_name) + .prefix(Some(folder_path)) + .to_stream() + .await; + + let mut file_list = Vec::new(); + + // Use StreamExt::next() to iterate through the stream + while let Some(items) = objects_stream.next().await { + match items { + Ok(result) => { + for item in result.contents { + file_list.push(item.name); + } + }, + Err(e) => { + return Err(actix_web::error::ErrorInternalServerError( + format!("Failed to list files in MinIO: {}", e) + )); + } + } + } + + Ok(HttpResponse::Ok().json(file_list)) +} #[actix_web::main] async fn main() -> std::io::Result<()> { + dotenv().ok(); // Initialize tracing diff --git a/src/models.rs b/src/models.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/services.rs b/src/services.rs new file mode 100644 index 0000000..ef68c36 --- /dev/null +++ b/src/services.rs @@ -0,0 +1 @@ +pub mod config; diff --git a/src/services/config.rs b/src/services/config.rs new file mode 100644 index 0000000..117c87f --- /dev/null +++ b/src/services/config.rs @@ -0,0 +1,46 @@ +use std::env; + +#[derive(Clone)] +pub struct AppConfig { + pub minio: MinioConfig, + pub server: ServerConfig, +} + +#[derive(Clone)] +pub struct MinioConfig { + pub endpoint: String, + pub access_key: String, + pub secret_key: String, + pub use_ssl: bool, + pub bucket: String, +} + +#[derive(Clone)] +pub struct ServerConfig { + pub host: String, + pub port: u16, +} + +impl AppConfig { + pub fn from_env() -> Self { + let minio = MinioConfig { + endpoint: env::var("MINIO_ENDPOINT").expect("MINIO_ENDPOINT not set"), + access_key: env::var("MINIO_ACCESS_KEY").expect("MINIO_ACCESS_KEY not set"), + secret_key: env::var("MINIO_SECRET_KEY").expect("MINIO_SECRET_KEY not set"), + use_ssl: env::var("MINIO_USE_SSL") + .unwrap_or_else(|_| "false".to_string()) + .parse() + .unwrap_or(false), + bucket: env::var("MINIO_BUCKET").expect("MINIO_BUCKET not set"), + }; + AppConfig { + minio, + server: ServerConfig { + host: env::var("SERVER_HOST").unwrap_or_else(|_| "127.0.0.1".to_string()), + port: env::var("SERVER_PORT").ok() + .and_then(|p| p.parse().ok()) + .unwrap_or(8080), + }, + } + } +}