Create Tag tables and Add Tag endpoint

This commit is contained in:
Cameron Cordes
2021-09-03 19:34:38 -04:00
parent 2d6db6d059
commit 8939ffbaf5
7 changed files with 155 additions and 5 deletions

View File

@@ -0,0 +1,3 @@
DROP TABLE tags;
DROP TABLE tagged_photo;

View 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
);

View File

@@ -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)] #[cfg(test)]
mod tests { mod tests {
use super::Claims; use super::Claims;

View File

@@ -9,7 +9,7 @@ use std::{
use crate::database::models::{Favorite, InsertFavorite, InsertUser, User}; use crate::database::models::{Favorite, InsertFavorite, InsertUser, User};
pub mod models; pub mod models;
mod schema; pub mod schema;
pub trait UserDao { pub trait UserDao {
fn create_user(&self, user: &str, password: &str) -> Option<User>; 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"); let db_url = dotenv::var("DATABASE_URL").expect("DATABASE_URL must be set");
SqliteConnection::establish(&db_url).expect("Error connecting to DB") SqliteConnection::establish(&db_url).expect("Error connecting to DB")
} }

View File

@@ -1,4 +1,4 @@
use crate::database::schema::{favorites, users}; use crate::database::schema::{favorites, tagged_photo, tags, users};
use serde::Serialize; use serde::Serialize;
#[derive(Insertable)] #[derive(Insertable)]
@@ -29,3 +29,29 @@ 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,
}
#[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,
}

View File

@@ -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! { table! {
users (id) { users (id) {
id -> Integer, 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,
);

View File

@@ -25,6 +25,7 @@ use actix_web::{
web::{self, BufMut, BytesMut, HttpRequest, HttpResponse}, web::{self, BufMut, BytesMut, HttpRequest, HttpResponse},
App, HttpServer, Responder, App, HttpServer, Responder,
}; };
use diesel::prelude::*;
use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
use rayon::prelude::*; use rayon::prelude::*;
@@ -34,11 +35,12 @@ 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_path}; use crate::files::{is_image_or_video, is_valid_path};
use crate::models::{InsertTag, InsertTaggedPhoto, Tag, TaggedPhoto};
use crate::video::*; use crate::video::*;
mod auth; mod auth;
mod data; mod data;
mod database; pub mod database;
mod files; mod files;
mod video; 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() { 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);
@@ -474,6 +555,7 @@ fn main() -> std::io::Result<()> {
.service(put_add_favorite) .service(put_add_favorite)
.service(delete_favorite) .service(delete_favorite)
.service(get_file_metadata) .service(get_file_metadata)
.service(add_tag)
.app_data(app_data.clone()) .app_data(app_data.clone())
.data::<Box<dyn UserDao>>(Box::new(user_dao)) .data::<Box<dyn UserDao>>(Box::new(user_dao))
.data::<Box<dyn FavoriteDao>>(Box::new(favorites_dao)) .data::<Box<dyn FavoriteDao>>(Box::new(favorites_dao))