Compare commits
2 Commits
f5af0c8711
...
db2bf1994e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db2bf1994e | ||
|
|
fbcea9e77b |
220
Cargo.lock
generated
220
Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
pub mod photos_api;
|
||||
pub mod photo_api;
|
||||
pub mod user_api;
|
||||
|
||||
149
backend/src/api/photo_api.rs
Normal file
149
backend/src/api/photo_api.rs
Normal 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))
|
||||
}
|
||||
@@ -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 }))
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
pub mod photo_model;
|
||||
pub mod user_model;
|
||||
|
||||
12
backend/src/models/photo_model.rs
Normal file
12
backend/src/models/photo_model.rs
Normal 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,
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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!{
|
||||
|
||||
@@ -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} />
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user