diff --git a/Cargo.lock b/Cargo.lock index 4fa6e12..7d54e18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -83,6 +83,44 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "actix-multipart" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee489e3c01eae4d1c35b03c4493f71cb40d93f66b14558feb1b1a807671cc4e" +dependencies = [ + "actix-multipart-derive", + "actix-utils", + "actix-web", + "bytes", + "derive_more", + "futures-core", + "futures-util", + "httparse", + "local-waker", + "log", + "memchr", + "mime", + "serde", + "serde_json", + "serde_plain", + "tempfile", + "tokio", +] + +[[package]] +name = "actix-multipart-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ec592f234db8a253cf80531246a4407c8a70530423eea80688a6c5a44a110e7" +dependencies = [ + "darling", + "parse-size", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "actix-router" version = "0.5.1" @@ -963,6 +1001,41 @@ dependencies = [ "syn 2.0.15", ] +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -1057,6 +1130,27 @@ dependencies = [ "serde", ] +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "event-listener" version = "2.5.3" @@ -1106,6 +1200,21 @@ dependencies = [ "regex", ] +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.28" @@ -1122,6 +1231,17 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-intrusive" version = "0.4.2" @@ -1139,6 +1259,17 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + [[package]] name = "futures-sink" version = "0.3.28" @@ -1157,8 +1288,10 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ + "futures-channel", "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -1243,6 +1376,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "hex" version = "0.4.3" @@ -1364,6 +1503,12 @@ dependencies = [ "cxx-build", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.3.0" @@ -1393,6 +1538,17 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "io-lifetimes" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "ipnet" version = "2.7.2" @@ -1419,6 +1575,7 @@ name = "jl-backend" version = "0.1.0" dependencies = [ "actix-cors", + "actix-multipart", "actix-web", "actix-web-utils", "aws-config", @@ -1429,6 +1586,7 @@ dependencies = [ "dotenvy", "dotenvy_macro", "err", + "futures", "jl-types", "rand", "reqwest", @@ -1499,6 +1657,12 @@ dependencies = [ "cc", ] +[[package]] +name = "linux-raw-sys" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eb31c1778188ae1e64398743890d0877fef36d11521ac60406b42016e8c2cf" + [[package]] name = "local-channel" version = "0.1.3" @@ -1620,7 +1784,7 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] @@ -1672,7 +1836,7 @@ dependencies = [ "cfg-if", "instant", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", "winapi", ] @@ -1685,11 +1849,17 @@ checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", "windows-sys 0.45.0", ] +[[package]] +name = "parse-size" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "944553dd59c802559559161f9816429058b869003836120e262e8caec061b7ae" + [[package]] name = "parse-zoneinfo" version = "0.3.0" @@ -1850,6 +2020,15 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_users" version = "0.4.3" @@ -1857,7 +2036,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom", - "redox_syscall", + "redox_syscall 0.2.16", "thiserror", ] @@ -1941,6 +2120,20 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.37.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b864d3c18a5785a05953adeed93e2dca37ed30f18e69bba9f30079d51f363f" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] + [[package]] name = "rustls" version = "0.20.8" @@ -2071,6 +2264,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_plain" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6018081315db179d0ce57b1fe4b62a12a0028c9cf9bbef868c9cf477b3c34ae" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2270,6 +2472,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "subtle" version = "2.4.1" @@ -2298,6 +2506,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.45.0", +] + [[package]] name = "termcolor" version = "1.2.0" @@ -2807,6 +3028,15 @@ dependencies = [ "windows-targets 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] + [[package]] name = "windows-targets" version = "0.42.2" diff --git a/Cargo.toml b/Cargo.toml index c66e051..7e96ccc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" dotenvy = "0.15.6" dotenvy_macro = "0.15.1" tokio = {version = "1.20.1", features = ["full"]} +futures = "0.3.28" reqwest = { version = "0.11.11", features = ["rustls-tls", "json", "blocking"], default-features = false } chrono = "0.4.23" chrono-tz = "0.8" @@ -18,8 +19,10 @@ sqlx = { version = "0.6.0", features = [ "runtime-tokio-rustls", "postgres", "ch dotenv = { version = "0.15.0" } actix-web = {version = "4.1.0"} actix-cors = "0.6.2" +actix-multipart = "0.6.0" uuid = { version = "1.3.0", features = ["v4", "fast-rng", "macro-diagnostics"] } + actix-web-utils = { git = "https://git.franklinblanco.dev/franklinblanco/actix-web-utils.git" } err = { git = "https://git.franklinblanco.dev/franklinblanco/err.git" } diff --git a/src/routes/admin.rs b/src/routes/admin.rs index 783de95..8010768 100644 --- a/src/routes/admin.rs +++ b/src/routes/admin.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use actix_multipart::Multipart; use actix_web::{ delete, get, post, put, web::{self, Path}, @@ -10,17 +11,20 @@ use jl_types::{ agent::Agent, contact::Contact, count::Count, location::Location, project::Project, unit::Unit, }, - dto::payloads::{ - agent::{NewAgentPayload, UpdateAgentPayload}, - location::NewLocationPayload, - project::{NewProjectPayload, UpdateProjectPayload}, - unit::{NewUnitPayload, UpdateUnitPayload}, - }, + dto::{ + payloads::{ + agent::{NewAgentPayload, UpdateAgentPayload}, + location::NewLocationPayload, + project::{NewProjectPayload, UpdateProjectPayload}, + unit::{NewUnitPayload, UpdateUnitPayload}, + }, + item::Item, + } }; use sqlx::PgPool; use uuid::Uuid; -use crate::{services, utils::s3::Item}; +use crate::{services}; #[post("/agent")] pub async fn create_new_agent_profile( @@ -125,7 +129,7 @@ pub async fn get_all_contacts(db_conn: web::Data>) -> TypedHttpRespo services::admin::get_all_contacts(&db_conn).await } -#[post("images/{item}/{id}/{file_name}")] -pub async fn upload_image(aws_client: web::Data>, path_vars: Path<(Item, Uuid, String)>) -> TypedHttpResponse { - services::admin::upload_image(&aws_client, path_vars.0.clone(), path_vars.1, path_vars.2.clone()).await +#[post("images/{item}/{id}")] +pub async fn upload_image(aws_client: web::Data>, path_vars: Path<(Item, Uuid)>, multipart: Multipart) -> TypedHttpResponse { + services::admin::upload_image(&aws_client, path_vars.0.clone(), path_vars.1, multipart).await } \ No newline at end of file diff --git a/src/services/admin.rs b/src/services/admin.rs index 8279c16..6bca177 100644 --- a/src/services/admin.rs +++ b/src/services/admin.rs @@ -1,3 +1,4 @@ +use actix_multipart::Multipart; use actix_web_utils::extensions::typed_response::TypedHttpResponse; use err::MessageResource; use jl_types::{ @@ -5,18 +6,23 @@ use jl_types::{ agent::Agent, contact::Contact, count::Count, location::Location, project::Project, unit::Unit, }, - dto::payloads::{ - agent::{NewAgentPayload, UpdateAgentPayload}, - location::NewLocationPayload, - project::{NewProjectPayload, UpdateProjectPayload}, - unit::{NewUnitPayload, UpdateUnitPayload}, - }, + dto::{ + payloads::{ + agent::{NewAgentPayload, UpdateAgentPayload}, + location::NewLocationPayload, + project::{NewProjectPayload, UpdateProjectPayload}, + unit::{NewUnitPayload, UpdateUnitPayload}, + }, + item::Item, + } }; +use futures::StreamExt as _; + use sqlx::PgPool; use uuid::Uuid; -use crate::{dao, handle_db_read_op, handle_db_write_op, handle_tx, success, unwrap_or_not_found, utils::s3::{Item, self}}; +use crate::{dao, handle_db_read_op, handle_db_write_op, handle_tx, success, unwrap_or_not_found, utils::s3::{self}}; // // Insert Methods @@ -197,8 +203,30 @@ pub async fn get_contact_count(conn: &PgPool) -> TypedHttpResponse { success!(count) } -pub async fn upload_image(client: &aws_sdk_s3::Client, item: Item, id: Uuid, file_name: String) -> TypedHttpResponse { - match s3::upload_image(client, id, item, file_name).await { +pub async fn upload_image(client: &aws_sdk_s3::Client, item: Item, id: Uuid, mut multipart: Multipart) -> TypedHttpResponse { + let mut bytes: Vec = Vec::new(); + match multipart.next().await { + Some(item) => { + let mut field = match item { + Ok(field) => field, + Err(error) => return TypedHttpResponse::return_standard_error(400, MessageResource::new_from_string(error.to_string())), + }; + + // Field in turn is stream of *Bytes* object + while let Some(chunk) = field.next().await { + bytes.append(&mut chunk.unwrap().to_vec()); + } + }, + None => { + return TypedHttpResponse::return_empty_response(400); + } + } + + if bytes.is_empty() { + return TypedHttpResponse::return_empty_response(400); + } + + match s3::upload_image(client, id, item, Uuid::new_v4().to_string(), bytes).await { Ok(url) => success!(url), Err(error) => return TypedHttpResponse::return_standard_error(400, MessageResource::new_from_string(error.to_string())), }; diff --git a/src/utils/s3.rs b/src/utils/s3.rs index ac2f7c6..e22cad5 100644 --- a/src/utils/s3.rs +++ b/src/utils/s3.rs @@ -1,31 +1,12 @@ -use std::fmt::Display; - +use jl_types::dto::item::Item; use aws_sdk_s3::{ error::SdkError, operation::put_object::{PutObjectError}, primitives::ByteStream, Client, }; -use serde::{Deserialize, Serialize}; use uuid::Uuid; -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, Default)] -pub enum Item { - #[default] - Project, - Unit, - Agent, -} -impl Display for Item { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Item::Project => write!(f, "Project"), - Item::Unit => write!(f, "Unit"), - Item::Agent => write!(f, "Agent"), - } - } -} - pub async fn init_aws_client() -> Client { dotenv::dotenv().expect("Failed loading env"); let config = aws_config::load_from_env().await; @@ -37,13 +18,14 @@ pub async fn upload_image( id: Uuid, item: Item, file_name: String, + multipart: Vec, ) -> Result> { let path = format!("jl-images/{item}/{id}/{file_name}"); match client .put_object() .bucket("jorge-ledesma-bucket") .key(path.clone()) - .body(ByteStream::from_static("Hey there".as_bytes())) + .body(ByteStream::from(multipart)) .acl(aws_sdk_s3::types::ObjectCannedAcl::PublicRead) .send() .await {