diff --git a/Cargo.lock b/Cargo.lock index 6347176..56015db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -220,7 +220,7 @@ dependencies = [ "serde_urlencoded", "smallvec", "socket2", - "time 0.3.10", + "time", "url", ] @@ -607,7 +607,7 @@ dependencies = [ "serde", "serde-xml-rs", "thiserror", - "time 0.3.10", + "time", "url", ] @@ -632,12 +632,13 @@ dependencies = [ "common", "dotenv", "env_logger", + "futures", "log", "mongodb", "rust-s3", "serde", "serde_json", - "uuid 1.3.0", + "uuid", "walkdir", ] @@ -733,8 +734,8 @@ dependencies = [ "serde", "serde_bytes", "serde_json", - "time 0.3.10", - "uuid 1.3.0", + "time", + "uuid", ] [[package]] @@ -755,6 +756,18 @@ version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" +[[package]] +name = "bytecount" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + [[package]] name = "bytes" version = "1.1.0" @@ -770,6 +783,37 @@ dependencies = [ "bytes", ] +[[package]] +name = "camino" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6031a462f977dd38968b6f23378356512feeace69cef817e1a4475108093cec3" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" +dependencies = [ + "camino", + "cargo-platform", + "semver 1.0.10", + "serde", + "serde_json", +] + [[package]] name = "cc" version = "1.0.73" @@ -804,11 +848,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" dependencies = [ "iana-time-zone", - "js-sys", "num-integer", "num-traits", - "time 0.1.45", - "wasm-bindgen", "winapi", ] @@ -878,7 +919,7 @@ dependencies = [ "rand", "sha2", "subtle", - "time 0.3.10", + "time", "version_check", ] @@ -1212,6 +1253,15 @@ dependencies = [ "libc", ] +[[package]] +name = "error-chain" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +dependencies = [ + "version_check", +] + [[package]] name = "event-listener" version = "2.5.3" @@ -1291,7 +1341,9 @@ dependencies = [ "dotenv_codegen", "gloo-net", "gloo-storage", + "image-meta", "js-sys", + "kamadak-exif", "lazy_static", "parking_lot", "pathfinding", @@ -1318,6 +1370,7 @@ checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -1342,9 +1395,9 @@ checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" [[package]] name = "futures-executor" -version = "0.3.21" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" dependencies = [ "futures-core", "futures-task", @@ -1431,7 +1484,7 @@ checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] @@ -1444,6 +1497,12 @@ dependencies = [ "polyval", ] +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "gloo" version = "0.2.1" @@ -1863,6 +1922,18 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "image-meta" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5831d8b072b2162a3b4f143081b6dea66175e0d84b6fd5adaa9dc615c31ceaa" +dependencies = [ + "byteorder", + "skeptic", + "strum", + "thiserror", +] + [[package]] name = "implicit-clone" version = "0.3.5" @@ -1967,6 +2038,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kamadak-exif" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4fc70d0ab7e5b6bafa30216a6b48705ea964cdfc29c050f2412295eba58077" +dependencies = [ + "mutate_once", +] + [[package]] name = "kv-log-macro" version = "1.0.7" @@ -2154,15 +2234,15 @@ checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "windows-sys 0.36.1", ] [[package]] name = "mongodb" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a1df476ac9541b0e4fdc8e2cc48884e66c92c933cd17a1fd75e68caf75752e" +checksum = "a37fe10c1485a0cd603468e284a1a8535b4ecf46808f5f7de3639a1e1252dbf8" dependencies = [ "async-std", "async-std-resolver", @@ -2172,14 +2252,15 @@ dependencies = [ "bson", "chrono", "derivative", + "derive_more", "futures-core", "futures-executor", + "futures-io", "futures-util", "hex", "hmac", "lazy_static", "md-5", - "os_info", "pbkdf2", "percent-encoding", "rand", @@ -2201,10 +2282,16 @@ dependencies = [ "trust-dns-proto", "trust-dns-resolver", "typed-builder", - "uuid 0.8.2", + "uuid", "webpki-roots", ] +[[package]] +name = "mutate_once" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b" + [[package]] name = "native-tls" version = "0.2.11" @@ -2328,16 +2415,6 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "os_info" -version = "3.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c424bc68d15e0778838ac013b5b3449544d8133633d8016319e7e05a820b8c0" -dependencies = [ - "log", - "winapi", -] - [[package]] name = "parking" version = "2.0.0" @@ -2390,9 +2467,9 @@ dependencies = [ [[package]] name = "pbkdf2" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271779f35b581956db91a3e55737327a03aa051e90b1c47aeb189508533adfd7" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ "digest", ] @@ -2554,6 +2631,17 @@ dependencies = [ "wasm-bindgen-futures", ] +[[package]] +name = "pulldown-cmark" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d9cc634bc78768157b5cbfe988ffcd1dcba95cd2b2f03a88316c08c6d00ed63" +dependencies = [ + "bitflags", + "memchr", + "unicase", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -2747,7 +2835,7 @@ dependencies = [ "serde_derive", "sha2", "thiserror", - "time 0.3.10", + "time", "tokio", "tokio-stream", "url", @@ -2815,11 +2903,11 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "0.3.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee86d63972a7c661d1536fefe8c3c8407321c3df668891286de28abcd087360" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64 0.13.0", + "base64 0.21.0", ] [[package]] @@ -2930,6 +3018,9 @@ name = "semver" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a41d061efea015927ac527063765e73601444cdc344ba855bc7bd44578b25e1c" +dependencies = [ + "serde", +] [[package]] name = "semver-parser" @@ -3101,6 +3192,21 @@ dependencies = [ "libc", ] +[[package]] +name = "skeptic" +version = "0.13.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" +dependencies = [ + "bytecount", + "cargo_metadata", + "error-chain", + "glob", + "pulldown-cmark", + "tempfile", + "walkdir", +] + [[package]] name = "slab" version = "0.4.6" @@ -3160,6 +3266,28 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "subtle" version = "2.4.1" @@ -3225,17 +3353,6 @@ dependencies = [ "syn", ] -[[package]] -name = "time" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - [[package]] name = "time" version = "0.3.10" @@ -3519,15 +3636,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "uuid" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" -dependencies = [ - "getrandom", -] - [[package]] name = "uuid" version = "1.3.0" @@ -3587,12 +3695,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index e91906f..607bfcf 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -22,8 +22,9 @@ serde_json = "1.0" common = { path = "../common" } uuid = { version = "1", features = ["v4", "serde"] } rust-s3 = "0.32.3" +futures = "0.3" [dependencies.mongodb] -version = "2.2.0" +version = "2.4.0" default-features = false features = ["async-std-runtime"] diff --git a/backend/src/api/mod.rs b/backend/src/api/mod.rs index 9f32237..9a45a7f 100644 --- a/backend/src/api/mod.rs +++ b/backend/src/api/mod.rs @@ -1,2 +1,2 @@ -pub mod photos_api; +pub mod photo_api; pub mod user_api; diff --git a/backend/src/api/photo_api.rs b/backend/src/api/photo_api.rs new file mode 100644 index 0000000..fb572fb --- /dev/null +++ b/backend/src/api/photo_api.rs @@ -0,0 +1,149 @@ +use actix_session::Session; +use actix_web::Result; +use actix_web::{ + get, post, + web::{Data, Json}, + HttpResponse, Responder, +}; +use actix_web_lab::__reexports::tokio; +use common::OutputPicture; +use mongodb::bson::oid::ObjectId; +use s3::bucket::Bucket; +use serde::{Deserialize, Serialize}; + +use crate::{models::photo_model::Photo, repository::mongodb_repo::MongoRepo}; + +#[derive(Debug, Deserialize, Serialize)] +pub struct PhotosWrapper { + photos: Vec, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct DetailPhoto { + key: String, + width: u32, + height: u32, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct DetailPhotoWrapper { + photos: Vec, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct PhotosUrlsWrapper { + photos: Vec<(String, String)>, +} + +// Return an opaque 500 while preserving the error's root cause for logging. +fn e500(e: T) -> actix_web::Error +where + T: std::fmt::Debug + std::fmt::Display + 'static, +{ + actix_web::error::ErrorInternalServerError(e) +} + +#[post("/api/photos/upload")] +pub async fn get_presigned_post_urls( + db: Data, + bucket: Data, + request: Json, + session: Session, +) -> Result { + let Some(user_id) = session.get::("user_id").map_err(e500)? else { + return Ok(HttpResponse::Unauthorized().body("Not authorized")); + }; + + let Ok(_) = db.get_user(&user_id).await else { + return Ok(HttpResponse::Unauthorized().body("Not authorized")); + }; + + let photos: Vec<(String, String)> = request + .photos + .iter() + .map(|x| { + ( + x.clone(), + bucket + .presign_put(format!("/{}/{}", user_id, x), 86400, None) + .unwrap(), + ) + }) + .collect(); + + Ok(HttpResponse::Ok().json(PhotosUrlsWrapper { photos })) +} + +#[post("/api/photos/upload/done")] +pub async fn upload_done( + db: Data, + request: Json, + session: Session, +) -> Result { + let Some(user_id) = session.get::("user_id").map_err(e500)? else { + return Ok(HttpResponse::Unauthorized().body("Not authorized")); + }; + + let Ok(_) = db.get_user(&user_id).await else { + return Ok(HttpResponse::Unauthorized().body("Not authorized")); + }; + + for p in &request.photos { + db.create_photo(Photo { + id: None, + user_id: ObjectId::parse_str(&user_id).unwrap(), + key: p.key.clone(), + width: p.width, + height: p.height, + }) + .await + .map_err(e500)?; + } + + Ok(HttpResponse::Ok().json(&request)) +} + +#[get("/api/photos/get")] +async fn get_user_photos( + db: Data, + bucket: Data, + session: Session, +) -> Result { + let Some(user_id) = session.get::("user_id").map_err(e500)? else { + return Ok(HttpResponse::Unauthorized().body("Not authorized")); + }; + + let Ok(user) = db.get_user(&user_id).await else { + return Ok(HttpResponse::Unauthorized().body("Not authorized")); + }; + let photos: Vec = db + .get_photos(&user_id) + .await + .unwrap() + .iter() + .map(|p| { + // let mut w: u32 = x.width.try_into().unwrap(); + // let mut h: u32 = x.height.try_into().unwrap(); + // if let Some(o) = &x.orientation { + // if o == "Rotate 270 CW" { + // swap(&mut w, &mut h); + // } + // } + let url = bucket + .presign_get(format!("/{}/{}", user_id, p.key.clone()), 86400, None) + .unwrap(); + + OutputPicture { + thumbnail: Some(url), + // thumbnail: Some(String::from("https://upload.wikimedia.org/wikipedia/commons/thumb/b/b4/Mila_Kunis_2018.jpg/220px-Mila_Kunis_2018.jpg")), + // thumbnail: x.thumbnail.clone(), + // width: w, + // height: h, + width: p.width, + height: p.height, + } + }) + .collect(); + + Ok(HttpResponse::Ok().json(photos)) +} diff --git a/backend/src/api/photos_api.rs b/backend/src/api/photos_api.rs deleted file mode 100644 index 7d46df7..0000000 --- a/backend/src/api/photos_api.rs +++ /dev/null @@ -1,57 +0,0 @@ -use actix_session::Session; -use actix_web::{ - post, - web::{Data, Json}, - HttpResponse, -}; -use s3::bucket::Bucket; -use serde::{Deserialize, Serialize}; - -use crate::repository::mongodb_repo::MongoRepo; - -#[derive(Debug, Deserialize, Serialize)] -pub struct PhotosWrapper { - photos: Vec, -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct PhotosUrlsWrapper { - photos: Vec<(String, String)>, -} - -// Return an opaque 500 while preserving the error's root cause for logging. -fn e500(e: T) -> actix_web::Error -where - T: std::fmt::Debug + std::fmt::Display + 'static, -{ - actix_web::error::ErrorInternalServerError(e) -} - -#[post("/api/photos/upload")] -pub async fn get_presigned_post_urls( - db: Data, - bucket: Data, - request: Json, - session: Session, -) -> Result { - let Some(user_id) = session.get::("user_id").map_err(e500)? else { - return Ok(HttpResponse::Unauthorized().body("Not authorized")); - }; - - let Ok(_) = db.get_user(&user_id).await else { - return Ok(HttpResponse::Unauthorized().body("Not authorized")); - }; - - let photos: Vec<(String, String)> = request - .photos - .iter() - .map(|x| { - ( - x.clone(), - bucket.presign_put(format!("/{x}"), 86400, None).unwrap(), - ) - }) - .collect(); - - Ok(HttpResponse::Ok().json(PhotosUrlsWrapper { photos })) -} diff --git a/backend/src/main.rs b/backend/src/main.rs index a95f135..d09e6a2 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -2,7 +2,8 @@ mod api; mod models; mod repository; -use api::photos_api::get_presigned_post_urls; +use api::photo_api::get_presigned_post_urls; +use api::photo_api::get_user_photos; use api::user_api::{create_user, delete_user, get_user, is_logged_in, login, update_user}; use common::OutputPicture; use repository::mongodb_repo::MongoRepo; @@ -31,30 +32,7 @@ use actix_web::{ use actix_web::{Responder, Result}; use actix_web_lab::web::spa; -#[get("/api/pictures/")] -async fn get_pictures() -> Result { - let pics: Vec = (1..15) - .map(|_| { - // let mut w: u32 = x.width.try_into().unwrap(); - // let mut h: u32 = x.height.try_into().unwrap(); - // if let Some(o) = &x.orientation { - // if o == "Rotate 270 CW" { - // swap(&mut w, &mut h); - // } - // } - OutputPicture { - thumbnail: Some(String::from("https://upload.wikimedia.org/wikipedia/commons/thumb/b/b4/Mila_Kunis_2018.jpg/220px-Mila_Kunis_2018.jpg")), - // thumbnail: x.thumbnail.clone(), - // width: w, - // height: h, - width: 100, - height: 100, - } - }) - .collect(); - - Ok(web::Json(pics)) -} +use crate::api::photo_api::upload_done; #[actix_web::main] async fn main() -> std::io::Result<()> { @@ -119,7 +97,8 @@ async fn main() -> std::io::Result<()> { .app_data(bucket.clone()) .wrap(middleware::Logger::default()) .service(get_presigned_post_urls) - .service(get_pictures) + .service(upload_done) + .service(get_user_photos) .service(is_logged_in) .service(login) .service(create_user) diff --git a/backend/src/models/mod.rs b/backend/src/models/mod.rs index 7db1805..1f539bb 100644 --- a/backend/src/models/mod.rs +++ b/backend/src/models/mod.rs @@ -1 +1,2 @@ +pub mod photo_model; pub mod user_model; diff --git a/backend/src/models/photo_model.rs b/backend/src/models/photo_model.rs new file mode 100644 index 0000000..d31f282 --- /dev/null +++ b/backend/src/models/photo_model.rs @@ -0,0 +1,12 @@ +use mongodb::bson::oid::ObjectId; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Photo { + #[serde(rename = "_id", skip_serializing_if = "Option::is_none")] + pub id: Option, + pub user_id: ObjectId, + pub key: String, + pub width: u32, + pub height: u32, +} diff --git a/backend/src/repository/mongodb_repo.rs b/backend/src/repository/mongodb_repo.rs index be9e538..2b14763 100644 --- a/backend/src/repository/mongodb_repo.rs +++ b/backend/src/repository/mongodb_repo.rs @@ -2,15 +2,19 @@ use std::env; extern crate dotenv; use dotenv::dotenv; +use crate::models::photo_model::Photo; use crate::models::user_model::User; +use futures::stream::TryStreamExt; use mongodb::{ - bson::{doc, extjson::de::Error, oid::ObjectId}, + bson::{doc, oid::ObjectId}, + error::Error, results::{DeleteResult, InsertOneResult, UpdateResult}, Client, Collection, }; pub struct MongoRepo { - col: Collection, + user_col: Collection, + photo_col: Collection, } impl MongoRepo { @@ -22,8 +26,12 @@ impl MongoRepo { }; let client = Client::with_uri_str(uri).await.unwrap(); let db = client.database("photos"); - let col: Collection = db.collection("User"); - MongoRepo { col } + let user_col: Collection = db.collection("User"); + let photo_col: Collection = db.collection("Photo"); + MongoRepo { + user_col, + photo_col, + } } pub async fn create_user(&self, new_user: User) -> Result { @@ -34,7 +42,7 @@ impl MongoRepo { password: new_user.password, }; let user = self - .col + .user_col .insert_one(new_doc, None) .await .ok() @@ -46,7 +54,7 @@ impl MongoRepo { let obj_id = ObjectId::parse_str(id).unwrap(); let filter = doc! {"_id": obj_id}; let user_detail = self - .col + .user_col .find_one(filter, None) .await .ok() @@ -57,7 +65,7 @@ impl MongoRepo { pub async fn get_user_from_email(&self, email: &String) -> Result { let filter = doc! {"email": email}; let user_detail = self - .col + .user_col .find_one(filter, None) .await .ok() @@ -78,7 +86,7 @@ impl MongoRepo { }, }; let updated_doc = self - .col + .user_col .update_one(filter, new_doc, None) .await .ok() @@ -90,11 +98,44 @@ impl MongoRepo { let obj_id = ObjectId::parse_str(id).unwrap(); let filter = doc! {"_id": obj_id}; let user_detail = self - .col + .user_col .delete_one(filter, None) .await .ok() .expect("Error deleting user"); Ok(user_detail) } + + pub async fn create_photo(&self, new_photo: Photo) -> Result { + let new_doc = Photo { + id: None, + user_id: new_photo.user_id, + key: new_photo.key, + width: new_photo.width, + height: new_photo.height, + }; + let photo = self + .photo_col + .insert_one(new_doc, None) + .await + .ok() + .expect("Error creating user"); + Ok(photo) + } + + pub async fn get_photos(&self, id: &String) -> Result, Error> { + let obj_id = ObjectId::parse_str(id).unwrap(); + let filter = doc! {"user_id": obj_id}; + let mut cursor = self + .photo_col + .find(filter, None) + .await + .ok() + .expect("Error getting photos cursor"); + let mut photos = Vec::new(); + while let Some(photo) = cursor.try_next().await? { + photos.push(photo); + } + Ok(photos) + } } diff --git a/frontend/Cargo.toml b/frontend/Cargo.toml index 9d40190..fa72713 100644 --- a/frontend/Cargo.toml +++ b/frontend/Cargo.toml @@ -30,6 +30,8 @@ lazy_static = "1.4" parking_lot = "0.12" dotenv = "0.15.0" dotenv_codegen = "0.15.0" +kamadak-exif = "0.5.5" +image-meta = "0.1.2" [features] default = [] diff --git a/frontend/src/components/home.js b/frontend/src/components/home.js index 25f84c6..fa153c9 100644 --- a/frontend/src/components/home.js +++ b/frontend/src/components/home.js @@ -5,7 +5,7 @@ function getFilesDataTransferItems(dataTransferItems) { // nothing to do } else if (item.isFile) { item.file(file => { - file.filepath = path + "/" + file.name; //save full path + file.filepath = path + file.name; //save full path folder.push(file); resolve(file); }); @@ -17,7 +17,7 @@ function getFilesDataTransferItems(dataTransferItems) { folder.push({ name: item.name, subfolder: subfolder }); for (let entry of entries) entriesPromises.push( - traverseFileTreePromise(entry, path + "/" + item.name, subfolder) + traverseFileTreePromise(entry, path + "/" + item.name + "/", subfolder) ); resolve(Promise.all(entriesPromises)); }); diff --git a/frontend/src/components/home.rs b/frontend/src/components/home.rs index 5583493..64a91b5 100644 --- a/frontend/src/components/home.rs +++ b/frontend/src/components/home.rs @@ -1,3 +1,5 @@ +use std::io::Cursor; + use super::BasePage; use crate::gallery::Grid; use crate::hooks::use_user_context; @@ -16,6 +18,10 @@ use yew_hooks::prelude::*; use common::OutputPicture; +pub struct MetaData { + width: u32, + height: u32, +} #[derive(Debug, Deserialize, Serialize)] pub struct PhotosWrapper { photos: Vec, @@ -25,6 +31,17 @@ pub struct PhotosUrlsWrapper { photos: Vec<(String, String)>, } +#[derive(Debug, Deserialize, Serialize)] +pub struct DetailPhoto { + key: String, + width: u32, + height: u32, +} +#[derive(Debug, Deserialize, Serialize)] +pub struct DetailPhotoWrapper { + photos: Vec, +} + #[function_component(Home)] pub fn home() -> Html { let user_ctx = use_user_context(); @@ -39,7 +56,7 @@ pub fn home() -> Html { use_effect_with_deps( move |_| { wasm_bindgen_futures::spawn_local(async move { - let url = "/api/pictures/"; + let url = "/api/photos/get"; let fetched_pictures: Vec = Request::get(url) .send() .await @@ -118,7 +135,9 @@ pub fn home() -> Html { console_log!("end", uiae.join("\n")); let photos: PhotosUrlsWrapper = Request::post("/api/photos/upload") - .json(&PhotosWrapper { photos: uiae }) + .json(&PhotosWrapper { + photos: uiae.clone(), + }) .unwrap() .send() .await @@ -128,19 +147,48 @@ pub fn home() -> Html { .unwrap(); console_log!("{}", serde_json::to_string(&photos).unwrap()); + let mut metadata: Vec = Vec::new(); let mut promises: Vec = Vec::new(); - for (file, (_, url)) in files.iter().zip(photos.photos) { + for (file, (key, url)) in files.iter().zip(photos.photos) { console_log!("uploading: ", &file.name(), &url); let promise = read_file(file.clone()); if let Ok(content) = wasm_bindgen_futures::JsFuture::from(promise).await { let buffer: ArrayBuffer = content.dyn_into().unwrap(); let typed_buffer: js_sys::Uint8Array = js_sys::Uint8Array::new(&buffer); let mut buf = vec![0; typed_buffer.length() as usize]; - typed_buffer.copy_to(&mut buf[..]); - console_log!( - serde_json::to_string(&buf.len()).unwrap(), - serde_json::to_string(&buf).unwrap() - ); + typed_buffer.copy_to(&mut buf); + // console_log!( + // serde_json::to_string(&buf.len()).unwrap(), + // serde_json::to_string(&buf).unwrap() + // ); + let mut md = MetaData { + width: 0, + height: 0, + }; + let meta = image_meta::load_from_buf(&buf).unwrap(); + console_log!(format!( + "dims: {}x{}", + meta.dimensions.width, meta.dimensions.height + )); + console_log!(format!("animation: {:?}", meta.is_animation())); + console_log!(format!("format: {:?}", meta.format)); + md.height = meta.dimensions.height; + md.width = meta.dimensions.width; + + let exifreader = exif::Reader::new(); + let mut cursor = Cursor::new(&buf); + if let Ok(exif) = exifreader.read_from_container(&mut cursor) { + for f in exif.fields() { + console_log!(format!( + "{} {} {}", + f.tag, + f.ifd_num, + f.display_value().with_unit(&exif) + )); + } + } + metadata.push(md); + promises.push(upload(buffer, file.type_(), url.clone())); } } @@ -152,6 +200,25 @@ pub fn home() -> Html { }; } + let photos: DetailPhotoWrapper = Request::post("/api/photos/upload/done") + .json(&DetailPhotoWrapper { + photos: uiae + .iter() + .zip(metadata) + .map(|(p, md)| DetailPhoto { + key: p.clone(), + width: md.width, + height: md.height, + }) + .collect(), + }) + .unwrap() + .send() + .await + .unwrap() + .json() + .await + .unwrap(); console_log!("all uploaded"); }); }); diff --git a/frontend/src/gallery/grid.rs b/frontend/src/gallery/grid.rs index 299bbec..a2f02e7 100644 --- a/frontend/src/gallery/grid.rs +++ b/frontend/src/gallery/grid.rs @@ -16,7 +16,7 @@ pub struct GridProps { #[function_component(Grid)] pub fn grid(props: &GridProps) -> Html { - let target_height = 100; + let target_height = 200; let container_width = if props.width == 0 { 0 } else { props.width - 4 }; let margin = 2; if container_width == 0 { @@ -54,6 +54,7 @@ pub fn grid(props: &GridProps) -> Html { "display: flex;", "flex-wrap: wrap;", "flex-direction: row;", + "min-height: 10vh", )}> { props.pictures.iter().zip(dimensions).map(|(p, d)| html!{ diff --git a/frontend/src/gallery/picture.rs b/frontend/src/gallery/picture.rs index 3e87aaa..05de7ee 100644 --- a/frontend/src/gallery/picture.rs +++ b/frontend/src/gallery/picture.rs @@ -1,4 +1,8 @@ use common::OutputPicture; +use wasm_bindgen::JsCast; +use wasm_bindgen_futures::JsFuture; +use web_sys::{window, Blob, HtmlElement, Request, RequestInit, Response, Url}; +use weblog::console_log; use yew::prelude::*; #[derive(Clone, Debug, Properties, PartialEq)] @@ -19,9 +23,42 @@ pub fn picture(props: &PictureProps) -> Html { let margin = props.margin.to_string(); let width = props.width.to_string(); let height = props.height.to_string(); + + let node = use_node_ref(); + + { + let node = node.clone(); + use_effect_with_deps( + move |_| { + wasm_bindgen_futures::spawn_local(async move { + let mut opts = RequestInit::new(); + opts.method("GET"); + let request = Request::new_with_str_and_init(&thumb, &opts).unwrap(); + let window = web_sys::window().unwrap(); + let resp = JsFuture::from(window.fetch_with_request(&request)) + .await + .unwrap(); + let resp: Response = resp.dyn_into().unwrap(); + let blob: Blob = JsFuture::from(resp.blob().unwrap()) + .await + .unwrap() + .dyn_into() + .unwrap(); + let url = Url::create_object_url_with_blob(&blob).unwrap(); + + if let Some(element) = node.cast::() { + element.set_attribute("src", &url); + } + }); + || () + }, + (), + ); + } + html! { <> - + } }