download pictures in the frontend and then create data-urls from blobs

This commit is contained in:
Johannes Heuel
2023-03-08 15:03:36 +01:00
parent fbcea9e77b
commit db2bf1994e
14 changed files with 500 additions and 165 deletions

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)
}
}