Create Tag tables and Add Tag endpoint
This commit is contained in:
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Claims;
|
use super::Claims;
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
);
|
||||||
|
|||||||
84
src/main.rs
84
src/main.rs
@@ -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))
|
||||||
|
|||||||
Reference in New Issue
Block a user