Pour tout problème contactez-nous par mail : support@froggit.fr | La FAQ :grey_question: | Rejoignez-nous sur le Chat :speech_balloon:

Skip to content
Snippets Groups Projects
Commit 232acae2 authored by Pierre Jarriges's avatar Pierre Jarriges
Browse files

basic upload files working

parent 16b70c57
No related branches found
No related tags found
1 merge request!9Upload files
...@@ -8,4 +8,5 @@ target ...@@ -8,4 +8,5 @@ target
public/**/*.js public/**/*.js
public/**/view/* public/**/view/*
public/standard/test_sitemap.xml public/standard/test_sitemap.xml
public/standard/dyn_sitemap.xml public/standard/dyn_sitemap.xml
\ No newline at end of file public/uploads
\ No newline at end of file
...@@ -40,7 +40,7 @@ class RootComponent { ...@@ -40,7 +40,7 @@ class RootComponent {
fetch_post_file(e.target).then(res => console.log(res)).catch(err => console.log(err)); fetch_post_file(e.target).then(res => console.log(res)).catch(err => console.log(err));
}, },
contents: [ contents: [
{ tag: "input", name: "file", type: "file" }, { tag: "input", name: "file", type: "file", multiple: true },
{ tag: "input", type: "submit" } { tag: "input", type: "submit" }
] ]
}, },
......
...@@ -106,15 +106,14 @@ function fetch_all_articles() { ...@@ -106,15 +106,14 @@ function fetch_all_articles() {
function fetch_post_file(form) { function fetch_post_file(form) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fetch("/post-file", { fetch("/post-files", {
method: "POST", method: "POST",
body: new FormData(form), body: new FormData(form),
}).then(async res => { }).then(async res => {
const res_text = await res.text();
if (res.status >= 400 && res.status < 600) { if (res.status >= 400 && res.status < 600) {
reject(res_text) reject((await res.text()))
} else { } else {
resolve(res_text); resolve((await res.json()));
} }
}) })
}) })
......
...@@ -87,7 +87,7 @@ async fn main() -> std::io::Result<()> { ...@@ -87,7 +87,7 @@ async fn main() -> std::io::Result<()> {
.service(get_articles_by_category) .service(get_articles_by_category)
.service(get_article) .service(get_article)
.service(get_all_articles) .service(get_all_articles)
.service(post_file) .service(post_files)
///////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////
// STANDARD FILES /////////////////////////////////////////////////////////////////////////////////////////// // STANDARD FILES ///////////////////////////////////////////////////////////////////////////////////////////
.service(resource("/favicon.ico").route(get().to(favicon))) .service(resource("/favicon.ico").route(get().to(favicon)))
......
use crate::{middleware::AuthenticatedAdminMiddleware, AppState}; use crate::{middleware::AuthenticatedAdminMiddleware, AppState};
use actix_multipart::Multipart; use actix_multipart::Multipart;
use actix_web::{ use actix_web::{post, web::Data, HttpRequest, HttpResponse, Responder};
post,
web::{block, Data},
HttpRequest, HttpResponse, Responder,
};
use futures::StreamExt; use futures::StreamExt;
use std::{ use std::{
fs::{remove_file, File}, fs::{create_dir_all, remove_file, File},
io::Write, io::Write,
path::Path,
sync::Arc,
}; };
#[post("/post-file")] // TODO separate file writing logic from Multipart implementation and implement the unit tests
pub async fn post_file( #[post("/post-files")]
pub async fn post_files(
app_state: Data<AppState>, app_state: Data<AppState>,
mut payload: Multipart, mut payload: Multipart,
middleware: Data<AuthenticatedAdminMiddleware<'_>>, middleware: Data<AuthenticatedAdminMiddleware<'_>>,
...@@ -24,6 +19,14 @@ pub async fn post_file( ...@@ -24,6 +19,14 @@ pub async fn post_file(
return HttpResponse::Unauthorized().finish(); return HttpResponse::Unauthorized().finish();
} }
let uploads_dir = app_state.env.public_dir.join("uploads");
if !uploads_dir.exists() {
create_dir_all(&uploads_dir).unwrap();
}
let mut uploaded_filepathes = Vec::new();
while let Some(item) = payload.next().await { while let Some(item) = payload.next().await {
match item { match item {
Ok(mut field) => { Ok(mut field) => {
...@@ -33,43 +36,44 @@ pub async fn post_file( ...@@ -33,43 +36,44 @@ pub async fn post_file(
.content_disposition() .content_disposition()
.expect("Missing Content Disposition header"); .expect("Missing Content Disposition header");
let filename = content_disposition.get_filename().expect("Missin filename"); let filename = content_disposition
.get_filename()
.expect("Missing filename");
let filepath = Arc::new(Path::new(format!("./tmp/{filename}"))); let filepath = uploads_dir.join(&filename);
// File::create is blocking operation, use threadpool let f = File::create(&filepath);
let mut f = block(|| File::create(*filepath.clone())).await;
if f.is_err() { if f.is_err() {
return HttpResponse::InternalServerError() return HttpResponse::InternalServerError()
.body(format!("Error creating file {:?}", f)); .body(format!("Error creating file {:?}", f));
} }
let f = f.unwrap(); let mut f = f.unwrap();
let mut error = None; let mut error = None;
// Field in turn is stream of *Bytes* object // Field in turn is stream of *Bytes* object
'chunks: while let Some(chunk) = field.next().await { 'chunks: while let Some(chunk) = field.next().await {
match chunk { match chunk {
Ok(chunk) => { Ok(chunk) => {
// filesystem operations are blocking, we have to use threadpool if f.write_all(&chunk).is_err() {
if block(move || f.write_all(&chunk).map(|_| f)).await.is_err() {
error = Some("Error writing chunk".to_string()); error = Some("Error writing chunk".to_string());
break 'chunks; break 'chunks;
} }
} }
Err(e) => { Err(e) => {
error = format!("Error writing file {} : {:?}", filename, e); error = Some(format!("Error writing file {} : {:?}", filename, e));
break 'chunks; break 'chunks;
} }
} }
} }
if let Some(err) = error { if let Some(err) = error {
block(|| remove_file(*filepath.clone())).await.unwrap(); remove_file(&filepath).unwrap();
return HttpResponse::InternalServerError().body(err); return HttpResponse::InternalServerError().body(err);
} }
uploaded_filepathes.push(String::from(filepath.to_string_lossy()));
} }
Err(e) => { Err(e) => {
return HttpResponse::InternalServerError().body(format!("FIELD ERR {:?}", e)) return HttpResponse::InternalServerError().body(format!("FIELD ERR {:?}", e))
...@@ -77,9 +81,11 @@ pub async fn post_file( ...@@ -77,9 +81,11 @@ pub async fn post_file(
} }
} }
HttpResponse::Ok().body("File was successfully uploaded") HttpResponse::Ok().json(uploaded_filepathes)
} }
// EXAMPLE FROM ACTIX REPO
// use futures::TryStreamExt; // use futures::TryStreamExt;
// use std::io::Write; // use std::io::Write;
...@@ -108,3 +114,51 @@ pub async fn post_file( ...@@ -108,3 +114,51 @@ pub async fn post_file(
// HttpResponse::Ok().body("sucess") // HttpResponse::Ok().body("sucess")
// } // }
/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*@@
*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*@@
* _______ ______ ______ _______ *@@
* |__ __@ | ____@ / ____@ |__ __@ *@@
* | @ | @__ \_ @_ | @ *@@
* | @ | __@ \ @_ | @ *@@
* | @ | @___ ____\ @ | @ *@@
* |__@ |______@ \______@ |__@ *@@
* *@@
*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*@@
*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*@*/
#[cfg(test)]
mod test_static_files {
use super::*;
use crate::middleware::get_auth_cookie;
use actix_web::{
http::{Method, StatusCode},
test, App,
};
#[tokio::test]
async fn post_files_unauthenticated_should_be_unauthorized() {
let app_state = AppState::for_test().await;
let mut app = test::init_service(
App::new()
.app_data(Data::new(app_state.clone()))
.app_data(Data::new(AuthenticatedAdminMiddleware::new(
"kuadrado-admin-auth",
)))
.service(post_files),
)
.await;
let req = test::TestRequest::with_uri("/post-files")
.method(Method::POST)
.cookie(get_auth_cookie(
"wrong-cookie",
app_state.encryption.random_ascii_lc_string(32),
))
.to_request();
let resp = test::call_service(&mut app, req).await;
assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment