feature/tagging #16
@@ -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,
|
||||
}
|
||||
|
||||
146
src/main.rs
146
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<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
181
src/tags.rs
Normal 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,
|
||||
}
|
||||
Reference in New Issue
Block a user