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;
|
use serde::Serialize;
|
||||||
|
|
||||||
#[derive(Insertable)]
|
#[derive(Insertable)]
|
||||||
@@ -29,33 +29,3 @@ pub struct Favorite {
|
|||||||
pub userid: i32,
|
pub userid: i32,
|
||||||
pub path: String,
|
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::web::Data;
|
||||||
use actix_web_prom::PrometheusMetricsBuilder;
|
use actix_web_prom::PrometheusMetricsBuilder;
|
||||||
use chrono::Utc;
|
|
||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use prometheus::{self, IntGauge};
|
use prometheus::{self, IntGauge};
|
||||||
@@ -24,7 +23,6 @@ use actix_web::{
|
|||||||
web::{self, BufMut, BytesMut},
|
web::{self, BufMut, BytesMut},
|
||||||
App, HttpRequest, HttpResponse, HttpServer, Responder,
|
App, HttpRequest, HttpResponse, HttpServer, Responder,
|
||||||
};
|
};
|
||||||
use diesel::prelude::*;
|
|
||||||
use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
|
use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
|
|
||||||
@@ -34,15 +32,16 @@ use crate::auth::login;
|
|||||||
use crate::data::*;
|
use crate::data::*;
|
||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::files::{is_image_or_video, is_valid_full_path};
|
use crate::files::{is_image_or_video, is_valid_full_path};
|
||||||
use crate::models::{InsertTag, InsertTaggedPhoto, Tag, TaggedPhoto};
|
|
||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
|
use crate::tags::*;
|
||||||
use crate::video::*;
|
use crate::video::*;
|
||||||
|
|
||||||
mod auth;
|
mod auth;
|
||||||
mod data;
|
mod data;
|
||||||
pub mod database;
|
mod database;
|
||||||
mod files;
|
mod files;
|
||||||
mod state;
|
mod state;
|
||||||
|
mod tags;
|
||||||
mod video;
|
mod video;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[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() {
|
fn create_thumbnails() {
|
||||||
let thumbs = &dotenv::var("THUMBNAILS").expect("THUMBNAILS not defined");
|
let thumbs = &dotenv::var("THUMBNAILS").expect("THUMBNAILS not defined");
|
||||||
let thumbnail_directory: &Path = Path::new(thumbs);
|
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