feature/tagging #16

Merged
cameron merged 22 commits from feature/tagging into master 2023-04-10 12:55:28 +00:00
3 changed files with 185 additions and 174 deletions
Showing only changes of commit 40c79d13db - Show all commits

View File

@@ -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,
}

View File

@@ -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<AddTagRequest>) -> 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::<Tag>(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::<i32>(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::<TaggedPhoto>(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<ThumbnailRequest>) -> 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::<Tag>(&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<AddTagRequest>) -> impl Responder {
use schema::tags;
match tags::table
.filter(tags::name.eq(&request.tag_name))
.get_result::<Tag>(&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);

181
src/tags.rs Normal file
View File

@@ -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<AddTagRequest>) -> 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::<Tag>(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::<i32>(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::<TaggedPhoto>(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<ThumbnailRequest>) -> 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::<Tag>(&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<AddTagRequest>) -> impl Responder {
use schema::tags;
match tags::table
.filter(tags::name.eq(&request.tag_name))
.get_result::<Tag>(&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,
}