switch to mongodb, add drag&drop

This commit is contained in:
Johannes Heuel
2023-02-23 18:28:12 +01:00
parent 61d0bbe4d1
commit ac27cba766
38 changed files with 3273 additions and 730 deletions

View File

@@ -11,12 +11,18 @@ default-run = "backend"
actix-web = "4"
actix-web-lab = "^0"
actix-files = "0.6"
actix-cors = "0.6.4"
actix-session = { version = "0.7.2", features = ["cookie-session"] }
env_logger = "0.9.0"
log = "0.4"
diesel = { version = "1.4.8", features = ["postgres", "r2d2"] }
diesel_migrations = "1.4"
dotenv = "0.15.0"
walkdir = "2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
common = { path = "../common" }
uuid = { version = "1", features = ["v4", "serde"] }
[dependencies.mongodb]
version = "2.2.0"
default-features = false
features = ["async-std-runtime"]

View File

@@ -1,5 +0,0 @@
# For documentation on how to configure this file,
# see diesel.rs/guides/configuring-diesel-cli
[print_schema]
file = "src/schema.rs"

1
backend/src/api/mod.rs Normal file
View File

@@ -0,0 +1 @@
pub mod user_api;

186
backend/src/api/user_api.rs Normal file
View File

@@ -0,0 +1,186 @@
use crate::{models::user_model::User, repository::mongodb_repo::MongoRepo};
use actix_session::Session;
use actix_web::{
delete, get, post, put,
web::{Data, Json, Path},
HttpResponse,
};
use mongodb::bson::oid::ObjectId;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize)]
pub struct UserWrapper {
user: User,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct UserWithToken {
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
pub id: Option<ObjectId>,
pub username: String,
pub email: String,
pub password: String,
pub token: String,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct UserTokenWrapper {
user: UserWithToken,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Login {
email: String,
password: String,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct LoginWrapper {
user: Login,
}
// 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)
}
#[get("/api/user")]
pub async fn is_logged_in(
db: Data<MongoRepo>,
session: Session,
) -> Result<HttpResponse, actix_web::Error> {
if let Some(user_id) = session.get::<String>("user_id").map_err(e500)? {
if let Ok(user) = db.get_user(&user_id).await {
let token = user.username.clone();
log::info!("success");
return Ok(HttpResponse::Ok().json(UserTokenWrapper {
user: UserWithToken {
id: user.id,
username: user.username,
email: user.email,
password: user.password,
token,
},
}));
}
}
Ok(HttpResponse::Unauthorized().body("Not logged in"))
}
#[post("/api/user")]
pub async fn create_user(db: Data<MongoRepo>, request: Json<UserWrapper>) -> HttpResponse {
let data = User {
id: None,
username: request.user.username.to_owned(),
email: request.user.email.to_owned(),
password: request.user.password.to_owned(),
};
let user_detail = db.create_user(data).await;
match user_detail {
Ok(user) => HttpResponse::Ok().json(user),
Err(err) => HttpResponse::InternalServerError().body(err.to_string()),
}
}
#[post("/api/users/login")]
pub async fn login(
db: Data<MongoRepo>,
request: Json<LoginWrapper>,
session: Session,
) -> HttpResponse {
let user = db.get_user_from_email(&request.user.email).await;
let Ok(user) = user else {
return HttpResponse::Unauthorized().body("Login failed");
};
if user.password != request.user.password {
return HttpResponse::Unauthorized().body("Login failed");
}
let Some(user_id) = user.id else {
return HttpResponse::Unauthorized().body("Login failed");
};
session.renew();
if let Err(e) = session.insert("user_id", user_id.to_string()) {
return HttpResponse::InternalServerError().body(e.to_string());
}
let token = user.username.clone();
HttpResponse::Ok().json(UserTokenWrapper {
user: UserWithToken {
id: user.id,
username: user.username,
email: user.email,
password: user.password,
token,
},
})
}
#[get("/api/user/{id}")]
pub async fn get_user(db: Data<MongoRepo>, path: Path<String>) -> HttpResponse {
let id = path.into_inner();
if id.is_empty() {
return HttpResponse::BadRequest().body("invalid ID");
}
let user_detail = db.get_user(&id).await;
match user_detail {
Ok(user) => HttpResponse::Ok().json(user),
Err(err) => HttpResponse::InternalServerError().body(err.to_string()),
}
}
#[put("/api/user/{id}")]
pub async fn update_user(
db: Data<MongoRepo>,
path: Path<String>,
request: Json<UserWrapper>,
) -> HttpResponse {
let id = path.into_inner();
if id.is_empty() {
return HttpResponse::BadRequest().body("invalid ID");
};
let data = User {
id: Some(ObjectId::parse_str(&id).unwrap()),
username: request.user.username.to_owned(),
email: request.user.email.to_owned(),
password: request.user.password.to_owned(),
};
let update_result = db.update_user(&id, data).await;
match update_result {
Ok(update) => {
if update.matched_count == 1 {
let updated_user_info = db.get_user(&id).await;
match updated_user_info {
Ok(user) => HttpResponse::Ok().json(user),
Err(err) => HttpResponse::InternalServerError().body(err.to_string()),
}
} else {
HttpResponse::NotFound().body("No user found with specified ID")
}
}
Err(err) => HttpResponse::InternalServerError().body(err.to_string()),
}
}
#[delete("/api/user/{id}")]
pub async fn delete_user(db: Data<MongoRepo>, path: Path<String>) -> HttpResponse {
let id = path.into_inner();
if id.is_empty() {
return HttpResponse::BadRequest().body("invalid ID");
};
let result = db.delete_user(&id).await;
match result {
Ok(res) => {
if res.deleted_count == 1 {
HttpResponse::Ok().json("User successfully deleted!")
} else {
HttpResponse::NotFound().json("User with specified ID not found!")
}
}
Err(err) => HttpResponse::InternalServerError().body(err.to_string()),
}
}

View File

@@ -1,236 +1,236 @@
extern crate diesel;
use backend::create_picture;
use backend::establish_connection;
use backend::models::NewPicture;
// extern crate diesel;
// use backend::create_picture;
// use backend::establish_connection;
// use backend::models::NewPicture;
// use backend::*;
use std::ffi::OsStr;
use std::path::Path;
use std::time::UNIX_EPOCH;
use std::{fs, process::Command};
use walkdir::{DirEntry, WalkDir};
// // use backend::*;
// use std::ffi::OsStr;
// use std::path::Path;
// use std::time::UNIX_EPOCH;
// use std::{fs, process::Command};
// use walkdir::{DirEntry, WalkDir};
use serde::{Deserialize, Serialize};
// use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct PhotoExif {
#[serde(default, alias = "FocalLength")]
focal_length: Option<String>,
#[serde(default, alias = "ShutterSpeed")]
shutter_speed: Option<String>,
#[serde(alias = "ImageWidth")]
width: i32,
#[serde(alias = "ImageHeight")]
height: i32,
#[serde(default, alias = "Make")]
make: Option<String>,
#[serde(default, alias = "Model")]
model: Option<String>,
#[serde(default, alias = "LensID")]
lens: Option<String>,
#[serde(default, alias = "Orientation")]
orientation: Option<String>,
#[serde(default, alias = "FNumber")]
fnumber: Option<f64>,
#[serde(default, alias = "ExposureProgram")]
exposure_program: Option<String>,
#[serde(default, alias = "CreateDate")]
created_at: Option<i32>,
#[serde(default, alias = "ISO")]
iso: Option<i32>,
#[serde(default = "MaybeString::default", alias = "ExposureCompensation")]
exposure_compensation: MaybeString,
}
// #[derive(Serialize, Deserialize)]
// struct PhotoExif {
// #[serde(default, alias = "FocalLength")]
// focal_length: Option<String>,
// #[serde(default, alias = "ShutterSpeed")]
// shutter_speed: Option<String>,
// #[serde(alias = "ImageWidth")]
// width: i32,
// #[serde(alias = "ImageHeight")]
// height: i32,
// #[serde(default, alias = "Make")]
// make: Option<String>,
// #[serde(default, alias = "Model")]
// model: Option<String>,
// #[serde(default, alias = "LensID")]
// lens: Option<String>,
// #[serde(default, alias = "Orientation")]
// orientation: Option<String>,
// #[serde(default, alias = "FNumber")]
// fnumber: Option<f64>,
// #[serde(default, alias = "ExposureProgram")]
// exposure_program: Option<String>,
// #[serde(default, alias = "CreateDate")]
// created_at: Option<i32>,
// #[serde(default, alias = "ISO")]
// iso: Option<i32>,
// #[serde(default = "MaybeString::default", alias = "ExposureCompensation")]
// exposure_compensation: MaybeString,
// }
#[derive(Serialize, Deserialize)]
#[serde(untagged)]
enum MaybeString {
Number(i32),
Str(String),
None,
}
// #[derive(Serialize, Deserialize)]
// #[serde(untagged)]
// enum MaybeString {
// Number(i32),
// Str(String),
// None,
// }
impl MaybeString {
fn default() -> MaybeString {
MaybeString::None
}
fn to_opt_string(&self) -> Option<String> {
if let MaybeString::Str(exp_comp) = &self {
Some(exp_comp.clone())
} else {
None
}
}
}
// impl MaybeString {
// fn default() -> MaybeString {
// MaybeString::None
// }
// fn to_opt_string(&self) -> Option<String> {
// if let MaybeString::Str(exp_comp) = &self {
// Some(exp_comp.clone())
// } else {
// None
// }
// }
// }
fn is_hidden(entry: &DirEntry) -> bool {
entry
.file_name()
.to_str()
.map(|s| s.starts_with('.'))
.unwrap_or(false)
}
// fn is_hidden(entry: &DirEntry) -> bool {
// entry
// .file_name()
// .to_str()
// .map(|s| s.starts_with('.'))
// .unwrap_or(false)
// }
fn is_image(entry: &DirEntry) -> bool {
let allowed_extensions = ["cr2", "cr3", "jpg", "jpeg"];
// fn is_image(entry: &DirEntry) -> bool {
// let allowed_extensions = ["cr2", "cr3", "jpg", "jpeg"];
let extension = if let Some(ext) = entry.path().extension() {
ext
} else {
OsStr::new("")
};
// let extension = if let Some(ext) = entry.path().extension() {
// ext
// } else {
// OsStr::new("")
// };
if allowed_extensions
.iter()
.all(|&v| v != extension.to_ascii_lowercase())
{
return false;
}
true
}
// if allowed_extensions
// .iter()
// .all(|&v| v != extension.to_ascii_lowercase())
// {
// return false;
// }
// true
// }
static PICTURE_PATH: &str = "./pictures";
static LIBRARY_PATH: &str = "./examples";
// static PICTURE_PATH: &str = "./pictures";
// static LIBRARY_PATH: &str = "./examples";
fn main() {
let connection = establish_connection();
// let connection = establish_connection();
WalkDir::new(LIBRARY_PATH)
.into_iter()
.filter_map(Result::ok)
.filter(|e| !e.file_type().is_dir())
.filter(|e| !is_hidden(e))
.filter(is_image)
.into_iter()
.for_each(|path| {
let thumbnail = if let Ok(t) = extract_preview(path.path()) {
t
} else {
println!("Could not create thumbnail");
return;
};
let thumbnail = std::path::PathBuf::from(thumbnail.strip_prefix(PICTURE_PATH).unwrap());
// WalkDir::new(LIBRARY_PATH)
// .into_iter()
// .filter_map(Result::ok)
// .filter(|e| !e.file_type().is_dir())
// .filter(|e| !is_hidden(e))
// .filter(is_image)
// .into_iter()
// .for_each(|path| {
// let thumbnail = if let Ok(t) = extract_preview(path.path()) {
// t
// } else {
// println!("Could not create thumbnail");
// return;
// };
// let thumbnail = std::path::PathBuf::from(thumbnail.strip_prefix(PICTURE_PATH).unwrap());
let output = Command::new("exiftool")
.arg("-j")
.arg("-d")
.arg("%s")
.arg(path.path())
.output()
.expect("failed to execute exiftool");
let pel: Vec<PhotoExif> = serde_json::from_slice(&output.stdout).unwrap();
let pe = &pel[0];
// let output = Command::new("exiftool")
// .arg("-j")
// .arg("-d")
// .arg("%s")
// .arg(path.path())
// .output()
// .expect("failed to execute exiftool");
// let pel: Vec<PhotoExif> = serde_json::from_slice(&output.stdout).unwrap();
// let pe = &pel[0];
println!("pe = {}", serde_json::to_string_pretty(pe).unwrap());
// println!("pe = {}", serde_json::to_string_pretty(pe).unwrap());
let created_at: Option<i32> = if let Some(c) = pe.created_at {
Some(c)
} else {
let metadata = fs::metadata(&path.path()).unwrap();
if let Ok(time) = metadata.created() {
Some(
time.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
.try_into()
.unwrap(),
)
} else {
println!("Not supported on this platform or filesystem");
None
}
};
// let created_at: Option<i32> = if let Some(c) = pe.created_at {
// Some(c)
// } else {
// let metadata = fs::metadata(&path.path()).unwrap();
// if let Ok(time) = metadata.created() {
// Some(
// time.duration_since(UNIX_EPOCH)
// .unwrap()
// .as_secs()
// .try_into()
// .unwrap(),
// )
// } else {
// println!("Not supported on this platform or filesystem");
// None
// }
// };
let filepath = path.path().to_string_lossy().into_owned();
// let filepath = path.path().to_string_lossy().into_owned();
let new_picture = NewPicture {
filepath: filepath.clone(),
created_at,
focal_length: pe.focal_length.clone(),
shutter_speed: pe.shutter_speed.clone(),
width: pe.width,
height: pe.height,
make: pe.make.clone(),
model: pe.model.clone(),
lens: pe.lens.clone(),
orientation: pe.orientation.clone(),
fnumber: pe.fnumber,
iso: pe.iso,
exposure_program: pe.exposure_program.clone(),
exposure_compensation: pe.exposure_compensation.to_opt_string(),
thumbnail: Some(thumbnail.into_os_string().into_string().unwrap()),
};
// let new_picture = NewPicture {
// filepath: filepath.clone(),
// created_at,
// focal_length: pe.focal_length.clone(),
// shutter_speed: pe.shutter_speed.clone(),
// width: pe.width,
// height: pe.height,
// make: pe.make.clone(),
// model: pe.model.clone(),
// lens: pe.lens.clone(),
// orientation: pe.orientation.clone(),
// fnumber: pe.fnumber,
// iso: pe.iso,
// exposure_program: pe.exposure_program.clone(),
// exposure_compensation: pe.exposure_compensation.to_opt_string(),
// thumbnail: Some(thumbnail.into_os_string().into_string().unwrap()),
// };
let pic = create_picture(&connection, new_picture);
println!("Created picture with filepath={} and id={}", filepath, pic);
});
}
fn extract_preview(path: &Path) -> Result<std::path::PathBuf, Box<dyn std::error::Error>> {
let file_name = if let Some(p) = path.file_name() {
p
} else {
OsStr::new("")
};
let parent = if let Some(p) = path.parent() {
p
} else {
Path::new(LIBRARY_PATH)
};
let relative_parent = parent
.strip_prefix(LIBRARY_PATH)
.expect("Could not remove prefix");
let thumb_path = Path::new(PICTURE_PATH).join(relative_parent);
if !thumb_path.exists() {
fs::create_dir_all(&thumb_path).unwrap_or_else(|e| {
panic!("Could not create directory {}: {}", thumb_path.display(), e)
});
}
let mut thumbnail = thumb_path.join(file_name);
thumbnail.set_extension("jpg");
let extension = path.extension().unwrap();
let jpegs = ["jpg", "jpeg"];
if jpegs.iter().any(|&x| x == extension.to_ascii_lowercase()) {
match fs::copy(path, &thumbnail) {
Ok(_it) => return Ok(thumbnail),
Err(err) => return Err(err.into()),
};
}
let _output_thumb = Command::new("exiftool")
.arg("-if")
.arg("$jpgfromraw")
.arg("-b")
.arg("-jpgfromraw")
.arg("-w")
.arg(thumb_path.join("%f.jpg"))
.arg("-execute")
.arg("-if")
.arg("$previewimage")
.arg("-b")
.arg("-previewimage")
.arg("-w")
.arg(thumb_path.join("%f.jpg"))
.arg("-execute")
.arg("-tagsfromfile")
.arg("@")
.arg("-srcfile")
.arg(thumb_path.join("%f.jpg"))
.arg("-overwrite_original")
.arg("-common_args")
.arg("--ext")
.arg("jpg")
.arg(path)
.output()
.expect("failed to execute exiftool to extract thumbnail");
// println!("{:?}", _output_thumb);
if thumbnail.exists() {
Ok(thumbnail)
} else {
Err("Could not create thumbnail".into())
}
// let pic = create_picture(&connection, new_picture);
// println!("Created picture with filepath={} and id={}", filepath, pic);
// });
// }
// fn extract_preview(path: &Path) -> Result<std::path::PathBuf, Box<dyn std::error::Error>> {
// let file_name = if let Some(p) = path.file_name() {
// p
// } else {
// OsStr::new("")
// };
// let parent = if let Some(p) = path.parent() {
// p
// } else {
// Path::new(LIBRARY_PATH)
// };
// let relative_parent = parent
// .strip_prefix(LIBRARY_PATH)
// .expect("Could not remove prefix");
// let thumb_path = Path::new(PICTURE_PATH).join(relative_parent);
// if !thumb_path.exists() {
// fs::create_dir_all(&thumb_path).unwrap_or_else(|e| {
// panic!("Could not create directory {}: {}", thumb_path.display(), e)
// });
// }
// let mut thumbnail = thumb_path.join(file_name);
// thumbnail.set_extension("jpg");
// let extension = path.extension().unwrap();
// let jpegs = ["jpg", "jpeg"];
// if jpegs.iter().any(|&x| x == extension.to_ascii_lowercase()) {
// match fs::copy(path, &thumbnail) {
// Ok(_it) => return Ok(thumbnail),
// Err(err) => return Err(err.into()),
// };
// }
// let _output_thumb = Command::new("exiftool")
// .arg("-if")
// .arg("$jpgfromraw")
// .arg("-b")
// .arg("-jpgfromraw")
// .arg("-w")
// .arg(thumb_path.join("%f.jpg"))
// .arg("-execute")
// .arg("-if")
// .arg("$previewimage")
// .arg("-b")
// .arg("-previewimage")
// .arg("-w")
// .arg(thumb_path.join("%f.jpg"))
// .arg("-execute")
// .arg("-tagsfromfile")
// .arg("@")
// .arg("-srcfile")
// .arg(thumb_path.join("%f.jpg"))
// .arg("-overwrite_original")
// .arg("-common_args")
// .arg("--ext")
// .arg("jpg")
// .arg(path)
// .output()
// .expect("failed to execute exiftool to extract thumbnail");
// // println!("{:?}", _output_thumb);
// if thumbnail.exists() {
// Ok(thumbnail)
// } else {
// Err("Could not create thumbnail".into())
// }
}

View File

@@ -1,21 +1,21 @@
extern crate diesel;
// extern crate diesel;
use self::models::*;
use backend::*;
use diesel::prelude::*;
// use self::models::*;
// use backend::*;
// use diesel::prelude::*;
fn main() {
use self::schema::pictures::dsl::*;
// use self::schema::pictures::dsl::*;
let connection = establish_connection();
let results = pictures
.limit(5)
.load::<Picture>(&connection)
.expect("Error loading pictures");
// let connection = establish_connection();
// let results = pictures
// .limit(5)
// .load::<Picture>(&connection)
// .expect("Error loading pictures");
println!("Displaying {} pictures", results.len());
for picture in results {
println!("filepath: {}", picture.filepath);
println!("\tid: {}", picture.id);
}
// println!("Displaying {} pictures", results.len());
// for picture in results {
// println!("filepath: {}", picture.filepath);
// println!("\tid: {}", picture.id);
// }
}

View File

@@ -1,29 +1,29 @@
#[macro_use]
extern crate diesel;
extern crate dotenv;
// #[macro_use]
// extern crate diesel;
// extern crate dotenv;
pub mod models;
pub mod schema;
// pub mod models;
// pub mod schema;
use self::models::NewPicture;
use diesel::pg::PgConnection;
use diesel::prelude::*;
use dotenv::dotenv;
use std::env;
// use self::models::NewPicture;
// use diesel::pg::PgConnection;
// use diesel::prelude::*;
// use dotenv::dotenv;
// use std::env;
pub fn establish_connection() -> PgConnection {
dotenv().ok();
// pub fn establish_connection() -> PgConnection {
// dotenv().ok();
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
PgConnection::establish(&database_url)
.unwrap_or_else(|_| panic!("Error connecting to {}", database_url))
}
// let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
// PgConnection::establish(&database_url)
// .unwrap_or_else(|_| panic!("Error connecting to {}", database_url))
// }
pub fn create_picture(conn: &PgConnection, new_picture: NewPicture) -> usize {
use schema::pictures;
// pub fn create_picture(conn: &PgConnection, new_picture: NewPicture) -> usize {
// use schema::pictures;
diesel::insert_into(pictures::table)
.values(&new_picture)
.execute(conn)
.expect("Error saving new picture")
}
// diesel::insert_into(pictures::table)
// .values(&new_picture)
// .execute(conn)
// .expect("Error saving new picture")
// }

View File

@@ -1,59 +1,49 @@
#[macro_use]
extern crate diesel;
mod api;
mod models;
mod repository;
#[macro_use]
extern crate diesel_migrations;
use api::user_api::{create_user, delete_user, get_user, is_logged_in, login, update_user};
use common::OutputPicture;
use repository::mongodb_repo::MongoRepo;
use std::mem::swap;
use actix_files as fs;
use actix_cors::Cors;
use actix_session::{
config::CookieContentSecurity, storage::CookieSessionStore, SessionMiddleware,
};
use actix_web::{
cookie,
// get, middleware, post, web, App, Error, HttpRequest, HttpResponse, HttpServer,
// get,
// middleware,
get,
http,
middleware,
web,
web::Data,
App,
HttpServer,
};
use actix_web::{Responder, Result};
use actix_web_lab::web::spa;
use backend::establish_connection;
use diesel::prelude::*;
use diesel::r2d2::{self, ConnectionManager};
// use uuid::Uuid;
mod actions;
mod models;
mod schema;
use common::OutputPicture;
type DbPool = r2d2::Pool<ConnectionManager<PgConnection>>;
#[get("/api/pictures/")]
async fn get_pictures(pool: web::Data<DbPool>) -> Result<impl Responder> {
let conn = pool.get().unwrap();
let pics = if let Ok(p) = actions::list_pictures(&conn) {
p
} else {
vec![]
};
let pics: Vec<OutputPicture> = pics
.iter()
.map(|x| {
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);
}
}
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: x.thumbnail.clone(),
width: w,
height: h,
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();
@@ -61,40 +51,50 @@ async fn get_pictures(pool: web::Data<DbPool>) -> Result<impl Responder> {
Ok(web::Json(pics))
}
embed_migrations!("migrations");
#[actix_web::main]
async fn main() -> std::io::Result<()> {
dotenv::dotenv().ok();
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
let connection = establish_connection();
// // This will run the necessary migrations.
// embedded_migrations::run(&connection).expect("Could not migrate database.");
// By default the output is thrown out. If you want to redirect it to stdout, you
// should call embedded_migrations::run_with_output.
embedded_migrations::run_with_output(&connection, &mut std::io::stdout())
.expect("Could not migrate database.");
// set up database connection pool
let conn_spec = std::env::var("DATABASE_URL").expect("DATABASE_URL");
let manager = ConnectionManager::<PgConnection>::new(conn_spec);
let pool = r2d2::Pool::builder()
.build(manager)
.expect("Failed to create pool.");
let secret_key = dotenv::var("SECRET").expect("SECRET not found");
let host = "0.0.0.0";
let port = 8081;
log::info!("starting HTTP server at http://{}:{}", host, port);
let db = MongoRepo::init().await;
let db_data = Data::new(db);
HttpServer::new(move || {
let cors = Cors::default()
.allowed_origin("http://localhost:8080")
.allowed_origin_fn(|origin, _req_head| origin.as_bytes().starts_with(b"localhost"))
.allowed_methods(vec!["GET", "POST"])
.allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT])
.allowed_header(http::header::CONTENT_TYPE)
.max_age(3600);
App::new()
.app_data(web::Data::new(pool.clone()))
// .app_data(web::Data::new(pool.clone()))
.wrap(cors)
.wrap(
SessionMiddleware::builder(
CookieSessionStore::default(),
cookie::Key::from(&secret_key.clone().into_bytes()),
)
.cookie_content_security(CookieContentSecurity::Private)
.build(),
)
.app_data(db_data.clone())
.wrap(middleware::Logger::default())
.service(get_pictures)
.service(fs::Files::new("/api/pictures/", "./pictures/"))
// .service(fs::Files::new("/api/pictures/", "./pictures/"))
.service(is_logged_in)
.service(login)
.service(create_user)
.service(get_user)
.service(update_user)
.service(delete_user)
.service(
spa()
.index_file("./dist/index.html")

View File

@@ -1,43 +0,0 @@
use serde::Serialize;
use super::schema::pictures;
#[derive(Queryable, Serialize)]
pub struct Picture {
pub id: i32,
pub filepath: String,
pub created_at: Option<i32>,
pub focal_length: Option<String>,
pub shutter_speed: Option<String>,
pub width: i32,
pub height: i32,
pub make: Option<String>,
pub model: Option<String>,
pub lens: Option<String>,
pub orientation: Option<String>,
pub fnumber: Option<f64>,
pub iso: Option<i32>,
pub exposure_program: Option<String>,
pub exposure_compensation: Option<String>,
pub thumbnail: Option<String>,
}
#[derive(Insertable)]
#[table_name = "pictures"]
pub struct NewPicture {
pub filepath: String,
pub created_at: Option<i32>,
pub focal_length: Option<String>,
pub shutter_speed: Option<String>,
pub width: i32,
pub height: i32,
pub make: Option<String>,
pub model: Option<String>,
pub lens: Option<String>,
pub orientation: Option<String>,
pub fnumber: Option<f64>,
pub iso: Option<i32>,
pub exposure_program: Option<String>,
pub exposure_compensation: Option<String>,
pub thumbnail: Option<String>,
}

View File

@@ -0,0 +1 @@
pub mod user_model;

View File

@@ -0,0 +1,11 @@
use mongodb::bson::oid::ObjectId;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct User {
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
pub id: Option<ObjectId>,
pub username: String,
pub email: String,
pub password: String,
}

View File

@@ -0,0 +1 @@
pub mod mongodb_repo;

View File

@@ -0,0 +1,100 @@
use std::env;
extern crate dotenv;
use dotenv::dotenv;
use crate::models::user_model::User;
use mongodb::{
bson::{doc, extjson::de::Error, oid::ObjectId},
results::{DeleteResult, InsertOneResult, UpdateResult},
Client, Collection,
};
pub struct MongoRepo {
col: Collection<User>,
}
impl MongoRepo {
pub async fn init() -> Self {
dotenv().ok();
let uri = match env::var("MONGOURI") {
Ok(v) => v.to_string(),
Err(_) => format!("Error loading env variable"),
};
let client = Client::with_uri_str(uri).await.unwrap();
let db = client.database("photos");
let col: Collection<User> = db.collection("User");
MongoRepo { col }
}
pub async fn create_user(&self, new_user: User) -> Result<InsertOneResult, Error> {
let new_doc = User {
id: None,
username: new_user.username,
email: new_user.email,
password: new_user.password,
};
let user = self
.col
.insert_one(new_doc, None)
.await
.ok()
.expect("Error creating user");
Ok(user)
}
pub async fn get_user(&self, id: &String) -> Result<User, Error> {
let obj_id = ObjectId::parse_str(id).unwrap();
let filter = doc! {"_id": obj_id};
let user_detail = self
.col
.find_one(filter, None)
.await
.ok()
.expect("Error getting user's detail");
Ok(user_detail.unwrap())
}
pub async fn get_user_from_email(&self, email: &String) -> Result<User, Error> {
let filter = doc! {"email": email};
let user_detail = self
.col
.find_one(filter, None)
.await
.ok()
.expect("Error getting user's detail");
Ok(user_detail.unwrap())
}
pub async fn update_user(&self, id: &String, new_user: User) -> Result<UpdateResult, Error> {
let obj_id = ObjectId::parse_str(id).unwrap();
let filter = doc! {"_id": obj_id};
let new_doc = doc! {
"$set":
{
"id": new_user.id,
"username": new_user.username,
"email": new_user.email,
"password": new_user.password,
},
};
let updated_doc = self
.col
.update_one(filter, new_doc, None)
.await
.ok()
.expect("Error updating user");
Ok(updated_doc)
}
pub async fn delete_user(&self, id: &String) -> Result<DeleteResult, Error> {
let obj_id = ObjectId::parse_str(id).unwrap();
let filter = doc! {"_id": obj_id};
let user_detail = self
.col
.delete_one(filter, None)
.await
.ok()
.expect("Error deleting user");
Ok(user_detail)
}
}

View File

@@ -1,20 +1,20 @@
table! {
pictures (id) {
id -> Int4,
filepath -> Varchar,
created_at -> Nullable<Int4>,
focal_length -> Nullable<Varchar>,
shutter_speed -> Nullable<Varchar>,
width -> Int4,
height -> Int4,
make -> Nullable<Varchar>,
model -> Nullable<Varchar>,
lens -> Nullable<Varchar>,
orientation -> Nullable<Varchar>,
fnumber -> Nullable<Float8>,
iso -> Nullable<Int4>,
exposure_program -> Nullable<Varchar>,
exposure_compensation -> Nullable<Varchar>,
thumbnail -> Nullable<Varchar>,
}
}
// table! {
// pictures (id) {
// id -> Int4,
// filepath -> Varchar,
// created_at -> Nullable<Int4>,
// focal_length -> Nullable<Varchar>,
// shutter_speed -> Nullable<Varchar>,
// width -> Int4,
// height -> Int4,
// make -> Nullable<Varchar>,
// model -> Nullable<Varchar>,
// lens -> Nullable<Varchar>,
// orientation -> Nullable<Varchar>,
// fnumber -> Nullable<Float8>,
// iso -> Nullable<Int4>,
// exposure_program -> Nullable<Varchar>,
// exposure_compensation -> Nullable<Varchar>,
// thumbnail -> Nullable<Varchar>,
// }
// }