feature/tagging #16
3
migrations/2021-09-02-000740_create_tags/down.sql
Normal file
3
migrations/2021-09-02-000740_create_tags/down.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
DROP TABLE tags;
|
||||
DROP TABLE tagged_photo;
|
||||
|
||||
11
migrations/2021-09-02-000740_create_tags/up.sql
Normal file
11
migrations/2021-09-02-000740_create_tags/up.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
CREATE TABLE tags (
|
||||
id INTEGER PRIMARY KEY NOT NULL,
|
||||
name TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE tagged_photo (
|
||||
id INTEGER PRIMARY KEY NOT NULL,
|
||||
photo_name TEXT NOT NULL,
|
||||
tag_id INTEGER NOT NULL,
|
||||
CONSTRAINT tagid FOREIGN KEY (tag_id) REFERENCES tags (id) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
@@ -133,6 +133,12 @@ impl From<fs::Metadata> for MetadataResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct AddTagRequest {
|
||||
pub file_name: String,
|
||||
pub tag_name: String,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Claims;
|
||||
|
||||
@@ -9,7 +9,7 @@ use std::{
|
||||
use crate::database::models::{Favorite, InsertFavorite, InsertUser, User};
|
||||
|
||||
pub mod models;
|
||||
mod schema;
|
||||
pub mod schema;
|
||||
|
||||
pub trait UserDao {
|
||||
fn create_user(&self, user: &str, password: &str) -> Option<User>;
|
||||
@@ -81,7 +81,7 @@ impl UserDao for SqliteUserDao {
|
||||
}
|
||||
}
|
||||
|
||||
fn connect() -> SqliteConnection {
|
||||
pub fn connect() -> SqliteConnection {
|
||||
let db_url = dotenv::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
||||
SqliteConnection::establish(&db_url).expect("Error connecting to DB")
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::database::schema::{favorites, users};
|
||||
use crate::database::schema::{favorites, tagged_photo, tags, users};
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Insertable)]
|
||||
@@ -29,3 +29,29 @@ pub struct Favorite {
|
||||
pub userid: i32,
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Queryable, Clone, Debug)]
|
||||
pub struct Tag {
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Insertable, Clone, Debug)]
|
||||
#[table_name = "tags"]
|
||||
pub struct InsertTag {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Insertable, Clone, Debug)]
|
||||
#[table_name = "tagged_photo"]
|
||||
pub struct InsertTaggedPhoto {
|
||||
pub tag_id: i32,
|
||||
pub photo_name: String,
|
||||
}
|
||||
|
||||
#[derive(Queryable, Clone, Debug)]
|
||||
pub struct TaggedPhoto {
|
||||
pub id: i32,
|
||||
pub photo_name: String,
|
||||
pub tag_id: i32,
|
||||
}
|
||||
|
||||
@@ -6,6 +6,21 @@ table! {
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
tagged_photo (id) {
|
||||
id -> Integer,
|
||||
photo_name -> Text,
|
||||
tag_id -> Integer,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
tags (id) {
|
||||
id -> Integer,
|
||||
name -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
users (id) {
|
||||
id -> Integer,
|
||||
@@ -14,4 +29,11 @@ table! {
|
||||
}
|
||||
}
|
||||
|
||||
allow_tables_to_appear_in_same_query!(favorites, users,);
|
||||
joinable!(tagged_photo -> tags (tag_id));
|
||||
|
||||
allow_tables_to_appear_in_same_query!(
|
||||
favorites,
|
||||
tagged_photo,
|
||||
tags,
|
||||
users,
|
||||
);
|
||||
|
||||
84
src/main.rs
84
src/main.rs
@@ -25,6 +25,7 @@ use actix_web::{
|
||||
web::{self, BufMut, BytesMut, HttpRequest, HttpResponse},
|
||||
App, HttpServer, Responder,
|
||||
};
|
||||
use diesel::prelude::*;
|
||||
use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
|
||||
use rayon::prelude::*;
|
||||
|
||||
@@ -34,11 +35,12 @@ use crate::auth::login;
|
||||
use crate::data::*;
|
||||
use crate::database::*;
|
||||
use crate::files::{is_image_or_video, is_valid_path};
|
||||
use crate::models::{InsertTag, InsertTaggedPhoto, Tag, TaggedPhoto};
|
||||
use crate::video::*;
|
||||
|
||||
mod auth;
|
||||
mod data;
|
||||
mod database;
|
||||
pub mod database;
|
||||
mod files;
|
||||
mod video;
|
||||
|
||||
@@ -292,6 +294,85 @@ 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();
|
||||
let tag_id = tags::table
|
||||
.filter(tags::name.eq(&tag))
|
||||
.get_result::<Tag>(connection)
|
||||
.optional()
|
||||
.map_or(-1, |t| {
|
||||
if let Some(t) = t {
|
||||
t.id
|
||||
} else {
|
||||
match diesel::insert_into(tags::table)
|
||||
.values(InsertTag { name: tag.clone() })
|
||||
.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_results::<i32>(connection)
|
||||
}) {
|
||||
Err(e) => {
|
||||
error!("Error inserting tag: '{}'. {:?}", tag, e);
|
||||
-1
|
||||
}
|
||||
Ok(id) => {
|
||||
debug!("Inserted tag: '{}' with id: {:?}", tag, id);
|
||||
*id.first().expect("We should have just inserted the row")
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if tag_id == -1 {
|
||||
HttpResponse::InternalServerError()
|
||||
} else {
|
||||
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(),
|
||||
})
|
||||
.execute(connection)
|
||||
.map(|_| {
|
||||
debug!("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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_thumbnails() {
|
||||
let thumbs = &dotenv::var("THUMBNAILS").expect("THUMBNAILS not defined");
|
||||
let thumbnail_directory: &Path = Path::new(thumbs);
|
||||
@@ -474,6 +555,7 @@ fn main() -> std::io::Result<()> {
|
||||
.service(put_add_favorite)
|
||||
.service(delete_favorite)
|
||||
.service(get_file_metadata)
|
||||
.service(add_tag)
|
||||
.app_data(app_data.clone())
|
||||
.data::<Box<dyn UserDao>>(Box::new(user_dao))
|
||||
.data::<Box<dyn FavoriteDao>>(Box::new(favorites_dao))
|
||||
|
||||
Reference in New Issue
Block a user