From 16b70c5701b3e9f6a4f1265cce741867f3e568ea Mon Sep 17 00:00:00 2001 From: Pierre Jarriges <pierre.jarriges@tutanota.com> Date: Mon, 18 Jul 2022 16:38:35 +0200 Subject: [PATCH] wip upload ans create file --- Cargo.lock | 77 +++++++++++++----- Cargo.toml | 1 + admin-frontend/src/components/root.js | 14 ++++ admin-frontend/src/xhr.js | 18 +++++ src/main.rs | 1 + src/service.rs | 2 + src/service/static_files.rs | 110 ++++++++++++++++++++++++++ 7 files changed, 205 insertions(+), 18 deletions(-) create mode 100644 src/service/static_files.rs diff --git a/Cargo.lock b/Cargo.lock index cfa0e81..2892c70 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,7 +101,7 @@ dependencies = [ "http", "httparse", "indexmap", - "itoa", + "itoa 0.4.8", "language-tags", "lazy_static", "log", @@ -128,6 +128,24 @@ dependencies = [ "syn", ] +[[package]] +name = "actix-multipart" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "774bfeb11b54bf9c857a005b8ab893293da4eaff79261a66a9200dab7f5ab6e3" +dependencies = [ + "actix-service", + "actix-utils", + "actix-web", + "bytes 0.5.6", + "derive_more", + "futures-util", + "httparse", + "log", + "mime", + "twoway", +] + [[package]] name = "actix-router" version = "0.2.7" @@ -1111,13 +1129,13 @@ dependencies = [ [[package]] name = "http" -version = "0.2.4" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes 1.1.0", "fnv", - "itoa", + "itoa 1.0.2", ] [[package]] @@ -1163,7 +1181,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa", + "itoa 0.4.8", "pin-project 1.0.8", "socket2", "tokio", @@ -1257,6 +1275,12 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + [[package]] name = "js-sys" version = "0.3.55" @@ -1281,6 +1305,7 @@ name = "kuadrado_server" version = "2.0.2" dependencies = [ "actix-files", + "actix-multipart", "actix-web", "actix-web-middleware-redirect-https", "chrono", @@ -1313,9 +1338,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.102" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2a5ac8f984bfcf3a823267e5fde638acc3325f6496633a5da6bb6eb2171e103" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" [[package]] name = "linked-hash-map" @@ -1325,9 +1350,9 @@ checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" [[package]] name = "lock_api" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" dependencies = [ "scopeguard", ] @@ -1588,9 +1613,9 @@ checksum = "1a5b3dd1c072ee7963717671d1ca129f1048fda25edea6b752bfc71ac8854170" [[package]] name = "once_cell" -version = "1.8.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" [[package]] name = "opaque-debug" @@ -2107,7 +2132,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8" dependencies = [ "indexmap", - "itoa", + "itoa 0.4.8", "ryu", "serde", ] @@ -2119,7 +2144,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" dependencies = [ "form_urlencoded", - "itoa", + "itoa 0.4.8", "ryu", "serde", ] @@ -2537,9 +2562,9 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" [[package]] name = "tracing" -version = "0.1.28" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84f96e095c0c82419687c20ddf5cb3eadb61f4e1405923c9dc8e53a1adacbda8" +checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" dependencies = [ "cfg-if 1.0.0", "log", @@ -2549,11 +2574,11 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.20" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46125608c26121c81b0c6d693eab5a420e416da7e43c426d2e8f7df8da8a3acf" +checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" dependencies = [ - "lazy_static", + "once_cell", ] [[package]] @@ -2612,6 +2637,16 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +[[package]] +name = "twoway" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c57ffb460d7c24cd6eda43694110189030a3d1dfe418416d9468fd1c1d290b47" +dependencies = [ + "memchr", + "unchecked-index", +] + [[package]] name = "typed-builder" version = "0.4.1" @@ -2635,6 +2670,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +[[package]] +name = "unchecked-index" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c" + [[package]] name = "unicase" version = "2.6.0" diff --git a/Cargo.toml b/Cargo.toml index 739a029..714776b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,3 +25,4 @@ time = "0.2.7" regex = "1.5" tokio = { version = "0.2", features = ["full"] } sitemap = "0.4.1" +actix-multipart = "0.3" diff --git a/admin-frontend/src/components/root.js b/admin-frontend/src/components/root.js index e4cf916..449b476 100644 --- a/admin-frontend/src/components/root.js +++ b/admin-frontend/src/components/root.js @@ -1,4 +1,5 @@ +const { fetch_post_file } = require("../xhr"); const CreateArticleForm = require("./create-article-form"); const UpdateArticleForm = require("./update-article-form"); @@ -30,6 +31,19 @@ class RootComponent { tag: "main", contents: [ { tag: "h1", contents: "Kuadrado admin panel" }, + { + tag: "form", + style_rules: { border: "1px solid black" }, + enctype: "multipart/form-data", + onsubmit: function (e) { + e.preventDefault(); + fetch_post_file(e.target).then(res => console.log(res)).catch(err => console.log(err)); + }, + contents: [ + { tag: "input", name: "file", type: "file" }, + { tag: "input", type: "submit" } + ] + }, { tag: "nav", contents: [ diff --git a/admin-frontend/src/xhr.js b/admin-frontend/src/xhr.js index a650a6d..870dd8e 100644 --- a/admin-frontend/src/xhr.js +++ b/admin-frontend/src/xhr.js @@ -104,6 +104,23 @@ function fetch_all_articles() { }); } +function fetch_post_file(form) { + return new Promise((resolve, reject) => { + fetch("/post-file", { + method: "POST", + body: new FormData(form), + }).then(async res => { + const res_text = await res.text(); + if (res.status >= 400 && res.status < 600) { + reject(res_text) + } else { + resolve(res_text); + } + }) + }) + +} + module.exports = { fetch_article, @@ -112,4 +129,5 @@ module.exports = { fetch_update_article, fetch_delete_article, fetch_all_articles, + fetch_post_file, } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 7506cd5..cdeaa0f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -87,6 +87,7 @@ async fn main() -> std::io::Result<()> { .service(get_articles_by_category) .service(get_article) .service(get_all_articles) + .service(post_file) ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // STANDARD FILES /////////////////////////////////////////////////////////////////////////////////////////// .service(resource("/favicon.ico").route(get().to(favicon))) diff --git a/src/service.rs b/src/service.rs index 91376c0..6f1d53f 100644 --- a/src/service.rs +++ b/src/service.rs @@ -1,4 +1,6 @@ mod admin_auth; mod articles; +mod static_files; pub use admin_auth::*; pub use articles::*; +pub use static_files::*; diff --git a/src/service/static_files.rs b/src/service/static_files.rs new file mode 100644 index 0000000..1842be7 --- /dev/null +++ b/src/service/static_files.rs @@ -0,0 +1,110 @@ +use crate::{middleware::AuthenticatedAdminMiddleware, AppState}; +use actix_multipart::Multipart; +use actix_web::{ + post, + web::{block, Data}, + HttpRequest, HttpResponse, Responder, +}; +use futures::StreamExt; +use std::{ + fs::{remove_file, File}, + io::Write, + path::Path, + sync::Arc, +}; + +#[post("/post-file")] +pub async fn post_file( + app_state: Data<AppState>, + mut payload: Multipart, + middleware: Data<AuthenticatedAdminMiddleware<'_>>, + req: HttpRequest, +) -> impl Responder { + if middleware.exec(&app_state, &req, None).await.is_err() { + return HttpResponse::Unauthorized().finish(); + } + + while let Some(item) = payload.next().await { + match item { + Ok(mut field) => { + // Field in turn is stream of *Bytes* object + // A multipart/form-data stream has to contain `content_disposition` + let content_disposition = field + .content_disposition() + .expect("Missing Content Disposition header"); + + let filename = content_disposition.get_filename().expect("Missin filename"); + + let filepath = Arc::new(Path::new(format!("./tmp/{filename}"))); + + // File::create is blocking operation, use threadpool + let mut f = block(|| File::create(*filepath.clone())).await; + + if f.is_err() { + return HttpResponse::InternalServerError() + .body(format!("Error creating file {:?}", f)); + } + + let f = f.unwrap(); + + let mut error = None; + + // Field in turn is stream of *Bytes* object + 'chunks: while let Some(chunk) = field.next().await { + match chunk { + Ok(chunk) => { + // filesystem operations are blocking, we have to use threadpool + if block(move || f.write_all(&chunk).map(|_| f)).await.is_err() { + error = Some("Error writing chunk".to_string()); + break 'chunks; + } + } + Err(e) => { + error = format!("Error writing file {} : {:?}", filename, e); + break 'chunks; + } + } + } + + if let Some(err) = error { + block(|| remove_file(*filepath.clone())).await.unwrap(); + return HttpResponse::InternalServerError().body(err); + } + } + Err(e) => { + return HttpResponse::InternalServerError().body(format!("FIELD ERR {:?}", e)) + } + } + } + + HttpResponse::Ok().body("File was successfully uploaded") +} + +// use futures::TryStreamExt; +// use std::io::Write; + +// pub async fn save_file(mut payload: Multipart) -> impl Responder { +// println!("SAVE FILE"); +// // iterate over multipart stream +// while let Some(mut field) = payload.try_next().await.unwrap() { +// // A multipart/form-data stream has to contain `content_disposition` +// let content_disposition = field.content_disposition().unwrap(); +// let filename = content_disposition.get_filename().unwrap(); +// let filepath = format!("./tmp/{filename}"); + +// // File::create is blocking operation, use threadpool +// let mut f = actix_web::web::block(|| std::fs::File::create(filepath)) +// .await +// .unwrap(); + +// // Field in turn is stream of *Bytes* object +// while let Some(chunk) = field.try_next().await.unwrap() { +// // filesystem operations are blocking, we have to use threadpool +// f = actix_web::web::block(move || f.write_all(&chunk).map(|_| f)) +// .await +// .unwrap(); +// } +// } + +// HttpResponse::Ok().body("sucess") +// } -- GitLab