FavoritesDao for querying, adding and removing favorites #9

Merged
cameron merged 1 commits from feature/favorites-api into master 2021-03-27 21:25:26 +00:00
2 changed files with 143 additions and 31 deletions
Showing only changes of commit 2e97086751 - Show all commits

View File

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

View File

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