diff --git a/app_file_handlers/.gitignore b/app_file_handlers/.gitignore new file mode 100644 index 0000000..c3a29c2 --- /dev/null +++ b/app_file_handlers/.gitignore @@ -0,0 +1,10 @@ +/target +target +Cargo.lock +.cache +.temp +.env +*.log +.DS_Store +logs +tmp diff --git a/app_file_handlers/Cargo.toml b/app_file_handlers/Cargo.toml new file mode 100644 index 0000000..394988d --- /dev/null +++ b/app_file_handlers/Cargo.toml @@ -0,0 +1,57 @@ +[package] +name = "app_file_handlers" +version = "0.1.0" +authors = ["JesusPerez "] +edition = "2018" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.40" +base64 = "0.13.0" +bytes = "1.0.1" +casbin = "2.0.7" +chrono = "0.4" +dotenv = "0.15.0" +envmnt = "0.9.0" +error-chain = "0.12.4" +glob = "0.3.0" +json = "0.12.4" +once_cell = "1.7.2" +parking_lot = "0.11.1" +rand = "0.8.3" +regex = "1.4.3" +serde = { version = "1.0", features = ["derive"] } +serde_derive = "1.0.125" +serde_json = "1.0.64" +serde_yaml = "0.8.17" +slab = "0.4.3" +tempfile = "3.2.0" +tera = "1.8.0" +thiserror = "1.0.24" +toml = "0.5.8" +yaml-rust = "0.4" +tokio = { version = "1.5.0", features = ["full"] } +futures = { version = "0.3.14", default-features = false } +uuid = { version = "0.8", features = ["serde", "v4"] } +url = "2.2.1" +warp = { version = "0.3", features = ["default","websocket","tls","compression"] } +app_tools = { version = "0.1.0", path = "../../utils/app_tools" } +app_env = { version = "0.1.0", path = "../../defs/app_env" } +app_auth = { version = "0.1.0", path = "../../defs/app_auth" } +reqtasks = { version = "0.1.0", path = "../reqtasks" } + +[dev-dependencies] +pretty_env_logger = "0.4" +tracing-subscriber = "0.2.15" +tracing-log = "0.1" +serde_derive = "1.0.125" +handlebars = "3.0.0" +tokio = { version = "1.5.0", features = ["macros", "rt-multi-thread"] } +tokio-stream = { version = "0.1.5", features = ["net"] } +listenfd = "0.3" +envmnt = "0.9.0" + +[build-dependencies] +envmnt = "0.9.0" diff --git a/app_file_handlers/README.md b/app_file_handlers/README.md new file mode 100644 index 0000000..9e8207b --- /dev/null +++ b/app_file_handlers/README.md @@ -0,0 +1,5 @@ +# App file handlers library + +Files Upload + +Handlers for request for auth management using [warp](https://github.com/seanmonstar/warp) diff --git a/app_file_handlers/TODO.md b/app_file_handlers/TODO.md new file mode 100644 index 0000000..f77225a --- /dev/null +++ b/app_file_handlers/TODO.md @@ -0,0 +1,4 @@ +# App file handlers library + +- [ ] Review & test + diff --git a/app_file_handlers/src/lib.rs b/app_file_handlers/src/lib.rs new file mode 100644 index 0000000..e04b0b7 --- /dev/null +++ b/app_file_handlers/src/lib.rs @@ -0,0 +1,294 @@ +// use app_auth::{AppAuthDBs, AuthError, UserCtx, WebResult, LoginRequest }; +use app_auth::{AppAuthDBs, UserCtx, WebResult }; +use uuid::Uuid; +// use http::Uri; +use std::collections::HashMap; + +use std::path::PathBuf; + +use reqtasks::ReqTasks; + +use bytes::BufMut; +use futures::TryStreamExt; + +use warp::{ + http::{method::Method, HeaderMap, HeaderValue}, // , StatusCode}, + //http::{Uri,method::Method, HeaderMap, HeaderValue, StatusCode}, +// multipart::{FormData, Part}, + Reply, Rejection // , reject, +}; +use warp::multipart::{FormData, Part}; + +#[allow(clippy::missing_errors_doc)] +pub async fn member_handler(user_ctx: UserCtx) -> WebResult { + Ok(format!("Member with id {}", user_ctx.user_id)) +} + +#[allow(clippy::missing_errors_doc)] +pub async fn admin_handler(user_ctx: UserCtx) -> WebResult { + Ok(format!("Admin with id {}", user_ctx.user_id)) +} + + +// #[allow(clippy::missing_errors_doc)] +// pub async fn ui_handler ( +// header: HeaderMap, +// method: Method, +// db: AppAuthDBs, +// ) -> Result { +// let req = ReqTasks::new(db.app, db.auth, header, method, "/ui", "ui", "file",); +// // let allow_origin = req.config().allow_origin; +// // Ok(warp::redirect(Uri::from_static("https://google.com"))) +// // let location = http::uri::Uri::from_str(&format!("/user/{}", user_id)).unwrap(); +// let location = "https://localhost:8000/ui/index.html".parse::().unwrap(); +// Ok(warp::redirect(location)) +// // Ok(warp::http::Response::builder() +// // //.header("Access-Control-Allow-Origin",&allow_origin) +// // .body("".to_string()) +// // .into_response()) +//} + +#[allow(clippy::missing_errors_doc)] +pub async fn upload_handler ( + header: HeaderMap, + method: Method, + db: AppAuthDBs +) -> Result { + let req = ReqTasks::new(db.app, db.auth, header, method, "/upload", "upload", "file",); + // let allow_origin = req.config().allow_origin; + let lang = req.lang(); + let mut ctx = req.ctx(); + let req_module = ""; + let app_module: &str; + if ! req_module.is_empty() && req_module != req.config().default_module.as_str() { + app_module = req_module; + } else { + app_module = ""; + } + let mut data_hash: HashMap = HashMap::new(); + data_hash.insert("lang".to_string(), lang.to_owned()); + // let mut res = String::from(""); + // let res = if let Some(name) = query.get("name") { + // submitted form + match req + .render_page( + &mut ctx, + req.config().templates_path.as_str(), + "upload/index.html", + "index.html", + format!("upload/{}.toml", lang.to_owned()) + .to_owned() + .as_str(), + &mut data_hash, + app_module, + ) + .await { + Ok(page) => + Ok(warp::http::Response::builder() + .body(page) + .into_response()), + /* + Ok(warp::reply::with_header( + warp::http::Response::new(page), + // "Access-Control-Allow-Origin", + // &allow_origin)), + */ + Err(_err) => + Ok(warp::http::Response::builder() + .body("".to_string()) + .into_response()), + /* + Ok(warp::reply::with_header( + warp::http::Response::new(err.to_string()), + "Access-Control-Allow-Origin", + &allow_origin)), + */ + } +} +pub fn get_file_info(p: &warp::multipart::Part) -> (String, String) { + let content_type = p.content_type().unwrap_or(""); + let full_file_name = p.filename().unwrap_or(""); + //let mut file_name = ""; + if ! full_file_name.is_empty() { + let file_name = match std::path::Path::new(&full_file_name.to_owned()).file_stem() { + Some(stem_file_name) => PathBuf::from(&stem_file_name), + None => PathBuf::from("") + }; + (content_type.to_owned(), format!("{}",&file_name.display())) + } else { + (content_type.to_owned(), full_file_name.to_owned()) + } +} + +pub async fn uploadin_handler( + form: FormData, + header: HeaderMap, + method: Method, + db: AppAuthDBs, +// parts: Result)>, warp::Rejection>, +) -> Result { + let req = ReqTasks::new(db.app, db.auth, header, method, "/uploadin", "uploadin", "file",); +// ) -> WebResult { + // let name = body.name; + // println!("{}", &name); + // dbg!(&req.auth_store.users.read().await); + // let allow_origin = req.config().allow_origin; + match req.user_authentication().await { + Ok(auth) => { + let mut files_path = format!("{}/{}", req.config().upload_path,&auth.user_id); + if ! std::path::Path::new(&files_path).exists() { + std::fs::create_dir(&files_path).unwrap_or_else(|e| { println!("Error create dir {}: {}", &files_path,e); files_path = String::from("");}); + if files_path.is_empty() { + eprintln!("form create directory"); + return Err(warp::reject::reject()); + } + } + // let part: Result)>, warp::Rejection> = form + // .and_then(|part| { + // let name = part.name().to_string(); + // let value = part.stream().try_fold(Vec::new(), |mut vec, data| { + // vec.put(data); + // async move { Ok(vec) } + // }); + // value.map_ok(move |vec| (name, vec)) + // }) + // .try_collect() + // .await + // .map_err(|e| { + // panic!("multipart error: {:?}", e); + // }); + // // part + let parts: Vec = form.try_collect().await.map_err(|e| { + eprintln!("form error: {}", e); + warp::reject::reject() + })?; + for p in parts { + // if p.name() == "file" { + let (content_type, src_file_name) = get_file_info(&p); +// let content_type = &p.content_type(); + let file_ending; + match content_type.as_str() { +// Some(file_type) => match file_type.to_owned() { + "application/pdf" => { + file_ending = "pdf"; + }, + "image/png" => { + file_ending = "png"; + }, + "image/jpg" => { + file_ending = "jpg"; + }, + "image/jpeg" => { + file_ending = "jpeg"; + }, + "image/svg+xml" => { + file_ending = "svg"; + }, + "application/doc" => { + file_ending = "doc"; + }, + "application/txt" => { + file_ending = "txt"; + }, + // v => { + // eprintln!("invalid file type found: {}", v); + // return Err(warp::reject::reject()); + // } + //}, + _ => { + eprintln!("file type {} could not be determined", &content_type); + return Err(warp::reject::reject()); + }, + } + let value = p + .stream() + .try_fold(Vec::new(), |mut vec, data| { + vec.put(data); + async move { Ok(vec) } + }) + .await + .map_err(|e| { + eprintln!("reading file error: {}", e); + warp::reject::reject() + })?; + let file_name = format!("{}/{}_{}.{}", &files_path,src_file_name,Uuid::new_v4().to_string(), file_ending); + tokio::fs::write(&file_name, value).await.map_err(|e| { + eprint!("error writing file: {}", e); + warp::reject::reject() + })?; + println!("created file: {}", file_name); + //} + } + Ok(warp::http::Response::builder() + .body("".to_string()) + .into_response()) + // Err(reject::custom(AuthError::UserNotFoundError)) + }, + Err(e) => { + let result = format!("Error: no credentials found"); + println!("{}",e); + Ok(warp::http::Response::builder() + .body(result.to_string()) + .into_response()) + }, + } +} +// https://blog.logrocket.com/file-upload-and-download-in-rust/ +// https://github.com/zupzup/warp-upload-download-example + +// https://github.com/cetra3/mpart-async/blob/master/examples/warp.rs + +/* +pub async fn uploadfile(form: FormData) -> Result { + let parts: Vec = form.try_collect().await.map_err(|e| { + eprintln!("form error: {}", e); + warp::reject::reject() + })?; + + for p in parts { + if p.name() == "file" { + let content_type = p.content_type(); + let file_ending; + match content_type { + Some(file_type) => match file_type { + "application/pdf" => { + file_ending = "pdf"; + } + "image/png" => { + file_ending = "png"; + } + v => { + eprintln!("invalid file type found: {}", v); + return Err(warp::reject::reject()); + } + }, + None => { + eprintln!("file type could not be determined"); + return Err(warp::reject::reject()); + } + } + + let value = p + .stream() + .try_fold(Vec::new(), |mut vec, data| { + vec.put(data); + async move { Ok(vec) } + }) + .await + .map_err(|e| { + eprintln!("reading file error: {}", e); + warp::reject::reject() + })?; + + let file_name = format!("./files/{}.{}", Uuid::new_v4().to_string(), file_ending); + tokio::fs::write(&file_name, value).await.map_err(|e| { + eprint!("error writing file: {}", e); + warp::reject::reject() + })?; + println!("created file: {}", file_name); + } + } + + Ok("success") +} +*/ \ No newline at end of file