FavoritesDao for querying, adding and removing favorites #9
@@ -1,6 +1,10 @@
|
|||||||
use bcrypt::{hash, verify, DEFAULT_COST};
|
use bcrypt::{hash, verify, DEFAULT_COST};
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use diesel::sqlite::SqliteConnection;
|
use diesel::sqlite::SqliteConnection;
|
||||||
|
use std::{
|
||||||
|
ops::Deref,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::database::models::{Favorite, InsertFavorite, InsertUser, User};
|
use crate::database::models::{Favorite, InsertFavorite, InsertUser, User};
|
||||||
|
|
||||||
@@ -85,25 +89,87 @@ fn connect() -> SqliteConnection {
|
|||||||
SqliteConnection::establish(&db_url).expect("Error connecting to DB")
|
SqliteConnection::establish(&db_url).expect("Error connecting to DB")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_favorite(user_id: i32, favorite_path: String) {
|
#[derive(Debug)]
|
||||||
|
pub struct DbError {
|
||||||
|
pub kind: DbErrorKind,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DbError {
|
||||||
|
fn new(kind: DbErrorKind) -> Self {
|
||||||
|
DbError { kind }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exists() -> Self {
|
||||||
|
DbError::new(DbErrorKind::AlreadyExists)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum DbErrorKind {
|
||||||
|
AlreadyExists,
|
||||||
|
InsertError,
|
||||||
|
QueryError,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait FavoriteDao: Sync + Send {
|
||||||
|
fn add_favorite(&self, user_id: i32, favorite_path: &str) -> Result<usize, DbError>;
|
||||||
|
fn remove_favorite(&self, user_id: i32, favorite_path: String);
|
||||||
|
fn get_favorites(&self, user_id: i32) -> Result<Vec<Favorite>, DbError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SqliteFavoriteDao {
|
||||||
|
connection: Arc<Mutex<SqliteConnection>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SqliteFavoriteDao {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
SqliteFavoriteDao {
|
||||||
|
connection: Arc::new(Mutex::new(connect())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FavoriteDao for SqliteFavoriteDao {
|
||||||
|
fn add_favorite(&self, user_id: i32, favorite_path: &str) -> Result<usize, DbError> {
|
||||||
use schema::favorites::dsl::*;
|
use schema::favorites::dsl::*;
|
||||||
|
|
||||||
let connection = connect();
|
let connection = self.connection.lock().unwrap();
|
||||||
|
let connection = connection.deref();
|
||||||
|
|
||||||
|
if favorites
|
||||||
|
.filter(userid.eq(user_id).and(path.eq(&favorite_path)))
|
||||||
|
.first::<Favorite>(connection)
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
diesel::insert_into(favorites)
|
diesel::insert_into(favorites)
|
||||||
.values(InsertFavorite {
|
.values(InsertFavorite {
|
||||||
userid: &user_id,
|
userid: &user_id,
|
||||||
path: &favorite_path,
|
path: &favorite_path,
|
||||||
})
|
})
|
||||||
.execute(&connection)
|
.execute(connection)
|
||||||
|
.map_err(|_| DbError::new(DbErrorKind::InsertError))
|
||||||
|
} else {
|
||||||
|
Err(DbError::exists())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_favorite(&self, user_id: i32, favorite_path: String) {
|
||||||
|
use schema::favorites::dsl::*;
|
||||||
|
|
||||||
|
diesel::delete(favorites)
|
||||||
|
.filter(userid.eq(user_id).and(path.eq(favorite_path)))
|
||||||
|
.execute(self.connection.lock().unwrap().deref())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_favorites(user_id: i32) -> diesel::QueryResult<Vec<Favorite>> {
|
fn get_favorites(&self, user_id: i32) -> Result<Vec<Favorite>, DbError> {
|
||||||
use schema::favorites::dsl::*;
|
use schema::favorites::dsl::*;
|
||||||
|
|
||||||
favorites
|
favorites
|
||||||
.filter(userid.eq(user_id))
|
.filter(userid.eq(user_id))
|
||||||
.load::<Favorite>(&connect())
|
.load::<Favorite>(self.connection.lock().unwrap().deref())
|
||||||
|
.map_err(|_| DbError::new(DbErrorKind::QueryError))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
66
src/main.rs
66
src/main.rs
@@ -3,7 +3,7 @@ extern crate diesel;
|
|||||||
extern crate rayon;
|
extern crate rayon;
|
||||||
|
|
||||||
use crate::auth::login;
|
use crate::auth::login;
|
||||||
use database::{SqliteUserDao, UserDao};
|
use database::{DbError, DbErrorKind, FavoriteDao, SqliteFavoriteDao, SqliteUserDao, UserDao};
|
||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
@@ -15,7 +15,9 @@ use actix::{Actor, Addr};
|
|||||||
use actix_files::NamedFile;
|
use actix_files::NamedFile;
|
||||||
use actix_multipart as mp;
|
use actix_multipart as mp;
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
get, post,
|
delete,
|
||||||
|
error::BlockingError,
|
||||||
|
get, post, put,
|
||||||
web::{self, BufMut, BytesMut},
|
web::{self, BufMut, BytesMut},
|
||||||
App, HttpServer, Responder,
|
App, HttpServer, Responder,
|
||||||
};
|
};
|
||||||
@@ -31,7 +33,6 @@ use data::{AddFavoriteRequest, ThumbnailRequest};
|
|||||||
use log::{debug, error, info};
|
use log::{debug, error, info};
|
||||||
|
|
||||||
use crate::data::Claims;
|
use crate::data::Claims;
|
||||||
use crate::database::{add_favorite, get_favorites};
|
|
||||||
use crate::files::{is_image_or_video, is_valid_path, list_files};
|
use crate::files::{is_image_or_video, is_valid_path, list_files};
|
||||||
use crate::video::*;
|
use crate::video::*;
|
||||||
|
|
||||||
@@ -216,8 +217,12 @@ async fn get_video_part(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("image/favorites")]
|
#[get("image/favorites")]
|
||||||
async fn favorites(claims: Claims) -> impl Responder {
|
async fn favorites(
|
||||||
let favorites = web::block(move || get_favorites(claims.sub.parse::<i32>().unwrap()))
|
claims: Claims,
|
||||||
|
favorites_dao: web::Data<Box<dyn FavoriteDao>>,
|
||||||
|
) -> impl Responder {
|
||||||
|
let favorites =
|
||||||
|
web::block(move || favorites_dao.get_favorites(claims.sub.parse::<i32>().unwrap()))
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -230,17 +235,55 @@ async fn favorites(claims: Claims) -> impl Responder {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("image/favorites")]
|
#[put("image/favorites")]
|
||||||
async fn post_add_favorite(claims: Claims, body: web::Json<AddFavoriteRequest>) -> impl Responder {
|
async fn put_add_favorite(
|
||||||
|
claims: Claims,
|
||||||
|
body: web::Json<AddFavoriteRequest>,
|
||||||
|
favorites_dao: web::Data<Box<dyn FavoriteDao>>,
|
||||||
|
) -> impl Responder {
|
||||||
|
if let Ok(user_id) = claims.sub.parse::<i32>() {
|
||||||
|
let path = body.path.clone();
|
||||||
|
match web::block::<_, usize, DbError>(move || favorites_dao.add_favorite(user_id, &path))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Err(BlockingError::Error(e)) if e.kind == DbErrorKind::AlreadyExists => {
|
||||||
|
debug!("Favorite: {} exists for user: {}", &body.path, user_id);
|
||||||
|
HttpResponse::Ok()
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
info!("{:?} {}. for user: {}", e, body.path, user_id);
|
||||||
|
HttpResponse::BadRequest()
|
||||||
|
}
|
||||||
|
Ok(_) => {
|
||||||
|
debug!("Adding favorite \"{}\" for userid: {}", body.path, user_id);
|
||||||
|
HttpResponse::Created()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error!("Unable to parse sub as i32: {}", claims.sub);
|
||||||
|
HttpResponse::BadRequest()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[delete("image/favorites")]
|
||||||
|
async fn delete_favorite(
|
||||||
|
claims: Claims,
|
||||||
|
body: web::Query<AddFavoriteRequest>,
|
||||||
|
favorites_dao: web::Data<Box<dyn FavoriteDao>>,
|
||||||
|
) -> impl Responder {
|
||||||
if let Ok(user_id) = claims.sub.parse::<i32>() {
|
if let Ok(user_id) = claims.sub.parse::<i32>() {
|
||||||
let path = body.path.clone();
|
let path = body.path.clone();
|
||||||
web::block::<_, _, String>(move || {
|
web::block::<_, _, String>(move || {
|
||||||
add_favorite(user_id, path);
|
favorites_dao.remove_favorite(user_id, path);
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
debug!("Adding favorite \"{}\" for userid: {}", body.path, user_id);
|
|
||||||
|
debug!(
|
||||||
|
"Removing favorite \"{}\" for userid: {}",
|
||||||
|
body.path, user_id
|
||||||
|
);
|
||||||
HttpResponse::Ok()
|
HttpResponse::Ok()
|
||||||
} else {
|
} else {
|
||||||
error!("Unable to parse sub as i32: {}", claims.sub);
|
error!("Unable to parse sub as i32: {}", claims.sub);
|
||||||
@@ -367,6 +410,7 @@ fn main() -> std::io::Result<()> {
|
|||||||
|
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
let user_dao = SqliteUserDao::new();
|
let user_dao = SqliteUserDao::new();
|
||||||
|
let favorites_dao = SqliteFavoriteDao::new();
|
||||||
App::new()
|
App::new()
|
||||||
.wrap(middleware::Logger::default())
|
.wrap(middleware::Logger::default())
|
||||||
.service(web::resource("/login").route(web::post().to(login)))
|
.service(web::resource("/login").route(web::post().to(login)))
|
||||||
@@ -377,9 +421,11 @@ fn main() -> std::io::Result<()> {
|
|||||||
.service(stream_video)
|
.service(stream_video)
|
||||||
.service(get_video_part)
|
.service(get_video_part)
|
||||||
.service(favorites)
|
.service(favorites)
|
||||||
.service(post_add_favorite)
|
.service(put_add_favorite)
|
||||||
|
.service(delete_favorite)
|
||||||
.app_data(app_data.clone())
|
.app_data(app_data.clone())
|
||||||
.data::<Box<dyn UserDao>>(Box::new(user_dao))
|
.data::<Box<dyn UserDao>>(Box::new(user_dao))
|
||||||
|
.data::<Box<dyn FavoriteDao>>(Box::new(favorites_dao))
|
||||||
})
|
})
|
||||||
.bind(dotenv::var("BIND_URL").unwrap())?
|
.bind(dotenv::var("BIND_URL").unwrap())?
|
||||||
.bind("localhost:8088")?
|
.bind("localhost:8088")?
|
||||||
|
|||||||
Reference in New Issue
Block a user