diff --git a/src/database/models.rs b/src/database/models.rs index 13ccc4d..dca23ba 100644 --- a/src/database/models.rs +++ b/src/database/models.rs @@ -1,4 +1,4 @@ -use crate::database::schema::{favorites, tagged_photo, tags, users}; +use crate::database::schema::{favorites, users}; use serde::Serialize; #[derive(Insertable)] @@ -29,33 +29,3 @@ pub struct Favorite { pub userid: i32, pub path: String, } - -#[derive(Serialize, Queryable, Clone, Debug)] -pub struct Tag { - pub id: i32, - pub name: String, - pub created_time: i64, -} - -#[derive(Insertable, Clone, Debug)] -#[table_name = "tags"] -pub struct InsertTag { - pub name: String, - pub created_time: i64, -} - -#[derive(Insertable, Clone, Debug)] -#[table_name = "tagged_photo"] -pub struct InsertTaggedPhoto { - pub tag_id: i32, - pub photo_name: String, - pub created_time: i64, -} - -#[derive(Queryable, Clone, Debug)] -pub struct TaggedPhoto { - pub id: i32, - pub photo_name: String, - pub tag_id: i32, - pub created_time: i64, -} diff --git a/src/main.rs b/src/main.rs index 1bade46..b26d7ae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,6 @@ extern crate rayon; use actix_web::web::Data; use actix_web_prom::PrometheusMetricsBuilder; -use chrono::Utc; use futures::stream::StreamExt; use lazy_static::lazy_static; use prometheus::{self, IntGauge}; @@ -24,7 +23,6 @@ use actix_web::{ web::{self, BufMut, BytesMut}, App, HttpRequest, HttpResponse, HttpServer, Responder, }; -use diesel::prelude::*; use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; use rayon::prelude::*; @@ -34,15 +32,16 @@ use crate::auth::login; use crate::data::*; use crate::database::*; use crate::files::{is_image_or_video, is_valid_full_path}; -use crate::models::{InsertTag, InsertTaggedPhoto, Tag, TaggedPhoto}; use crate::state::AppState; +use crate::tags::*; use crate::video::*; mod auth; mod data; -pub mod database; +mod database; mod files; mod state; +mod tags; mod video; #[cfg(test)] @@ -320,145 +319,6 @@ async fn delete_favorite( } } -#[post("image/tags")] -async fn add_tag(_: Claims, body: web::Json) -> impl Responder { - let tag = body.tag_name.clone(); - - use database::schema::tags; - - let connection = &connect(); - match tags::table - .filter(tags::name.eq(&tag)) - .get_result::(connection) - .optional() - .and_then(|t| { - if let Some(t) = t { - Ok(t.id) - } else { - match diesel::insert_into(tags::table) - .values(InsertTag { - name: tag.clone(), - created_time: Utc::now().timestamp(), - }) - .execute(connection) - .and_then(|_| { - no_arg_sql_function!( - last_insert_rowid, - diesel::sql_types::Integer, - "Represents the SQL last_insert_row() function" - ); - diesel::select(last_insert_rowid).get_result::(connection) - }) { - Err(e) => { - error!("Error inserting tag: '{}'. {:?}", tag, e); - Err(e) - } - Ok(id) => { - info!("Inserted tag: '{}' with id: {:?}", tag, id); - Ok(id) - } - } - } - }) - .map(|tag_id| { - use database::schema::tagged_photo; - - let file_name = body.file_name.clone(); - - match tagged_photo::table - .filter(tagged_photo::photo_name.eq(&file_name)) - .filter(tagged_photo::tag_id.eq(tag_id)) - .get_result::(connection) - .optional() - { - Ok(Some(_)) => HttpResponse::NoContent(), - Ok(None) => diesel::insert_into(tagged_photo::table) - .values(InsertTaggedPhoto { - tag_id, - photo_name: file_name.clone(), - created_time: Utc::now().timestamp(), - }) - .execute(connection) - .map(|_| { - info!("Inserted tagged photo: {} -> '{}'", tag_id, file_name); - - HttpResponse::Created() - }) - .unwrap_or_else(|e| { - error!( - "Error inserting tagged photo: '{}' -> '{}'. {:?}", - tag_id, body.file_name, e - ); - - HttpResponse::InternalServerError() - }), - Err(e) => { - error!("Error querying tagged photo: {:?}", e); - HttpResponse::InternalServerError() - } - } - }) { - Ok(resp) => resp, - Err(e) => { - error!("{:?}", e); - HttpResponse::InternalServerError() - } - } -} - -#[get("image/tags")] -async fn get_tags(_: Claims, request: web::Query) -> impl Responder { - use schema::tagged_photo; - use schema::tags; - - match tags::table - .left_join(tagged_photo::table) - .filter(tagged_photo::photo_name.eq(&request.path)) - .select((tags::id, tags::name, tags::created_time)) - .get_results::(&connect()) - { - Ok(tags) => HttpResponse::Ok().json(tags), - Err(e) => { - error!("Error getting tags for image: '{}'. {:?}", request.path, e); - - HttpResponse::InternalServerError().finish() - } - } -} - -#[delete("image/tags")] -async fn remove_tagged_photo(_: Claims, request: web::Json) -> impl Responder { - use schema::tags; - match tags::table - .filter(tags::name.eq(&request.tag_name)) - .get_result::(&connect()) - .optional() - .and_then(|tag| { - if let Some(tag) = tag { - use schema::tagged_photo; - diesel::delete( - tagged_photo::table - .filter(tagged_photo::tag_id.eq(tag.id)) - .filter(tagged_photo::photo_name.eq(&request.file_name)), - ) - .execute(&connect()) - .map(|_| HttpResponse::Ok()) - } else { - info!("No tag found with name '{}'", &request.tag_name); - Ok(HttpResponse::NotFound()) - } - }) { - Ok(status) => status, - Err(err) => { - error!( - "Error removing tag '{}' from file: {}. {:?}", - &request.tag_name, &request.file_name, err - ); - HttpResponse::InternalServerError() - } - } -} - fn create_thumbnails() { let thumbs = &dotenv::var("THUMBNAILS").expect("THUMBNAILS not defined"); let thumbnail_directory: &Path = Path::new(thumbs); diff --git a/src/tags.rs b/src/tags.rs new file mode 100644 index 0000000..bbd1fe1 --- /dev/null +++ b/src/tags.rs @@ -0,0 +1,181 @@ +use crate::{ + connect, + data::AddTagRequest, + database, + database::schema::{tagged_photo, tags}, + schema, Claims, ThumbnailRequest, +}; +use actix_web::{delete, get, post, web, HttpResponse, Responder}; +use chrono::Utc; +use diesel::prelude::*; +use log::{error, info}; +use serde::Serialize; + +#[post("image/tags")] +pub async fn add_tag(_: Claims, body: web::Json) -> impl Responder { + let tag = body.tag_name.clone(); + + use database::schema::tags; + + let connection = &connect(); + match tags::table + .filter(tags::name.eq(&tag)) + .get_result::(connection) + .optional() + .and_then(|t| { + if let Some(t) = t { + Ok(t.id) + } else { + match diesel::insert_into(tags::table) + .values(InsertTag { + name: tag.clone(), + created_time: Utc::now().timestamp(), + }) + .execute(connection) + .and_then(|_| { + no_arg_sql_function!( + last_insert_rowid, + diesel::sql_types::Integer, + "Represents the SQL last_insert_row() function" + ); + diesel::select(last_insert_rowid).get_result::(connection) + }) { + Err(e) => { + error!("Error inserting tag: '{}'. {:?}", tag, e); + Err(e) + } + Ok(id) => { + info!("Inserted tag: '{}' with id: {:?}", tag, id); + Ok(id) + } + } + } + }) + .map(|tag_id| { + use database::schema::tagged_photo; + + let file_name = body.file_name.clone(); + + match tagged_photo::table + .filter(tagged_photo::photo_name.eq(&file_name)) + .filter(tagged_photo::tag_id.eq(tag_id)) + .get_result::(connection) + .optional() + { + Ok(Some(_)) => HttpResponse::NoContent(), + Ok(None) => diesel::insert_into(tagged_photo::table) + .values(InsertTaggedPhoto { + tag_id, + photo_name: file_name.clone(), + created_time: Utc::now().timestamp(), + }) + .execute(connection) + .map(|_| { + info!("Inserted tagged photo: {} -> '{}'", tag_id, file_name); + + HttpResponse::Created() + }) + .unwrap_or_else(|e| { + error!( + "Error inserting tagged photo: '{}' -> '{}'. {:?}", + tag_id, body.file_name, e + ); + + HttpResponse::InternalServerError() + }), + Err(e) => { + error!("Error querying tagged photo: {:?}", e); + HttpResponse::InternalServerError() + } + } + }) { + Ok(resp) => resp, + Err(e) => { + error!("{:?}", e); + HttpResponse::InternalServerError() + } + } +} + +#[get("image/tags")] +pub async fn get_tags(_: Claims, request: web::Query) -> impl Responder { + use schema::tagged_photo; + use schema::tags; + + match tags::table + .left_join(tagged_photo::table) + .filter(tagged_photo::photo_name.eq(&request.path)) + .select((tags::id, tags::name, tags::created_time)) + .get_results::(&connect()) + { + Ok(tags) => HttpResponse::Ok().json(tags), + Err(e) => { + error!("Error getting tags for image: '{}'. {:?}", request.path, e); + + HttpResponse::InternalServerError().finish() + } + } +} + +#[delete("image/tags")] +pub async fn remove_tagged_photo(_: Claims, request: web::Json) -> impl Responder { + use schema::tags; + match tags::table + .filter(tags::name.eq(&request.tag_name)) + .get_result::(&connect()) + .optional() + .and_then(|tag| { + if let Some(tag) = tag { + use schema::tagged_photo; + diesel::delete( + tagged_photo::table + .filter(tagged_photo::tag_id.eq(tag.id)) + .filter(tagged_photo::photo_name.eq(&request.file_name)), + ) + .execute(&connect()) + .map(|_| HttpResponse::Ok()) + } else { + info!("No tag found with name '{}'", &request.tag_name); + Ok(HttpResponse::NotFound()) + } + }) { + Ok(status) => status, + Err(err) => { + error!( + "Error removing tag '{}' from file: {}. {:?}", + &request.tag_name, &request.file_name, err + ); + HttpResponse::InternalServerError() + } + } +} + +#[derive(Serialize, Queryable, Clone, Debug)] +pub struct Tag { + pub id: i32, + pub name: String, + pub created_time: i64, +} + +#[derive(Insertable, Clone, Debug)] +#[table_name = "tags"] +pub struct InsertTag { + pub name: String, + pub created_time: i64, +} + +#[derive(Insertable, Clone, Debug)] +#[table_name = "tagged_photo"] +pub struct InsertTaggedPhoto { + pub tag_id: i32, + pub photo_name: String, + pub created_time: i64, +} + +#[derive(Queryable, Clone, Debug)] +pub struct TaggedPhoto { + pub id: i32, + pub photo_name: String, + pub tag_id: i32, + pub created_time: i64, +}