Compare commits

...

2 Commits

Author SHA1 Message Date
Johannes Heuel
db2bf1994e download pictures in the frontend and then create data-urls from blobs 2023-03-08 15:03:36 +01:00
Johannes Heuel
fbcea9e77b make read file and upload separate js functions 2023-03-05 11:54:24 +01:00
14 changed files with 529 additions and 184 deletions

220
Cargo.lock generated
View File

@@ -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"

View File

@@ -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"]

View File

@@ -1,2 +1,2 @@
pub mod photos_api;
pub mod photo_api;
pub mod user_api;

View File

@@ -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<String>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct DetailPhoto {
key: String,
width: u32,
height: u32,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct DetailPhotoWrapper {
photos: Vec<DetailPhoto>,
}
#[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<T>(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<MongoRepo>,
bucket: Data<Bucket>,
request: Json<PhotosWrapper>,
session: Session,
) -> Result<HttpResponse, actix_web::Error> {
let Some(user_id) = session.get::<String>("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<MongoRepo>,
request: Json<DetailPhotoWrapper>,
session: Session,
) -> Result<HttpResponse, actix_web::Error> {
let Some(user_id) = session.get::<String>("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<MongoRepo>,
bucket: Data<Bucket>,
session: Session,
) -> Result<impl Responder> {
let Some(user_id) = session.get::<String>("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<OutputPicture> = 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))
}

View File

@@ -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<String>,
}
#[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<T>(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<MongoRepo>,
bucket: Data<Bucket>,
request: Json<PhotosWrapper>,
session: Session,
) -> Result<HttpResponse, actix_web::Error> {
let Some(user_id) = session.get::<String>("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 }))
}

View File

@@ -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<impl Responder> {
let pics: Vec<OutputPicture> = (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)

View File

@@ -1 +1,2 @@
pub mod photo_model;
pub mod user_model;

View File

@@ -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<ObjectId>,
pub user_id: ObjectId,
pub key: String,
pub width: u32,
pub height: u32,
}

View File

@@ -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>,
user_col: Collection<User>,
photo_col: Collection<Photo>,
}
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<User> = db.collection("User");
MongoRepo { col }
let user_col: Collection<User> = db.collection("User");
let photo_col: Collection<Photo> = db.collection("Photo");
MongoRepo {
user_col,
photo_col,
}
}
pub async fn create_user(&self, new_user: User) -> Result<InsertOneResult, Error> {
@@ -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<User, Error> {
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<InsertOneResult, Error> {
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<Vec<Photo>, 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)
}
}

View File

@@ -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 = []

View File

@@ -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));
});
@@ -49,7 +49,7 @@ export function get_files_data_transfer_items(data_transfer_items) {
return getFilesDataTransferItems(data_transfer_items);
}
function read_file(file) {
export function read_file(file) {
return new Promise((resolve, reject) => {
let reader = new FileReader();
reader.onloadend = () => {
@@ -60,23 +60,21 @@ function read_file(file) {
});
}
export function upload_file(file, url) {
export function upload(content, content_type, url) {
return new Promise((resolve, reject) => {
read_file(file).then((content) => {
fetch(url, {
method: 'PUT',
headers: {
'Accept': 'application/json',
'Content-Type': 'multipart/form-data',
},
body: new Blob([content], { type: file.type }),
}).then((resp) => {
if (resp.status >= 200 && resp.status < 300) {
resolve(resp);
} else {
reject(resp);
}
});
}).catch(error => reject(error));
fetch(url, {
method: 'PUT',
headers: {
'Accept': 'application/json',
'Content-Type': 'multipart/form-data',
},
body: new Blob([content], { type: content_type }),
}).then((resp) => {
if (resp.status >= 200 && resp.status < 300) {
resolve(resp);
} else {
reject(resp);
}
});
});
}

View File

@@ -1,9 +1,11 @@
use std::io::Cursor;
use super::BasePage;
use crate::gallery::Grid;
use crate::hooks::use_user_context;
use gloo_net::http::Request;
use js_sys::{Array, Promise};
use js_sys::{Array, ArrayBuffer, Promise};
use serde::{Deserialize, Serialize};
use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue};
use web_sys::{
@@ -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<String>,
@@ -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<DetailPhoto>,
}
#[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<OutputPicture> = 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,78 @@ pub fn home() -> Html {
.unwrap();
console_log!("{}", serde_json::to_string(&photos).unwrap());
console_log!("", files.len());
let mut metadata: Vec<MetaData> = Vec::new();
let mut promises: Vec<Promise> = 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);
promises.push(upload_file(file.clone(), url.clone()));
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()
// );
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()));
}
}
for promise in promises {
match wasm_bindgen_futures::JsFuture::from(promise).await {
Ok(result) => console_log!(result),
_ => console_log!("errooooor"),
Err(e) => console_log!("errooooor", e),
};
}
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");
});
});
@@ -192,5 +270,6 @@ pub fn home() -> Html {
#[wasm_bindgen(module = "/src/components/home.js")]
extern "C" {
fn get_files_data_transfer_items(data_transfer_items: DataTransferItemList) -> js_sys::Promise;
fn upload_file(file: File, url: String) -> js_sys::Promise;
fn read_file(file: File) -> js_sys::Promise;
fn upload(content: ArrayBuffer, content_type: String, url: String) -> js_sys::Promise;
}

View File

@@ -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!{

View File

@@ -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::<HtmlElement>() {
element.set_attribute("src", &url);
}
});
|| ()
},
(),
);
}
html! {
<>
<img style={format!("margin: {}px; display: block;", margin)} src={thumb} width={width} height={height} />
<img ref={node} style={format!("margin: {}px; display: block;", margin)} width={width} height={height} />
</>
}
}