diff --git a/src/files.rs b/src/files.rs index 4c6b76c..b161776 100644 --- a/src/files.rs +++ b/src/files.rs @@ -459,7 +459,10 @@ mod tests { #[test] fn directory_traversal_test() { let base = env::temp_dir(); - assert_eq!(None, is_valid_full_path(&base, &PathBuf::from("../"), false)); + assert_eq!( + None, + is_valid_full_path(&base, &PathBuf::from("../"), false) + ); assert_eq!(None, is_valid_full_path(&base, &PathBuf::from(".."), false)); assert_eq!( None, diff --git a/src/main.rs b/src/main.rs index 95aa587..cf7066a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -217,7 +217,8 @@ async fn stream_video( debug!("Playlist: {}", playlist); // Extract video playlist dir to dotenv - if !playlist.starts_with("tmp") && is_valid_full_path(&app_state.base_path, playlist, false).is_some() + if !playlist.starts_with("tmp") + && is_valid_full_path(&app_state.base_path, playlist, false).is_some() { HttpResponse::BadRequest().finish() } else if let Ok(file) = NamedFile::open(playlist) { diff --git a/src/tags.rs b/src/tags.rs index da1c268..976c3a3 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -3,6 +3,7 @@ use actix_web::dev::{ServiceFactory, ServiceRequest}; use actix_web::{web, App, HttpResponse, Responder}; use anyhow::Context; use chrono::Utc; +use diesel::dsl::{count_star}; use diesel::prelude::*; use log::{debug, info}; use schema::{tagged_photo, tags}; @@ -11,8 +12,8 @@ use std::borrow::BorrowMut; use std::sync::Mutex; pub fn add_tag_services(app: App) -> App -where - T: ServiceFactory, + where + T: ServiceFactory, { app.service( web::resource("image/tags") @@ -20,8 +21,8 @@ where .route(web::get().to(get_tags::)) .route(web::delete().to(remove_tagged_photo::)), ) - .service(web::resource("image/tags/all").route(web::get().to(get_all_tags::))) - .service(web::resource("image/tags/batch").route(web::post().to(update_tags::))) + .service(web::resource("image/tags/all").route(web::get().to(get_all_tags::))) + .service(web::resource("image/tags/batch").route(web::post().to(update_tags::))) } async fn add_tag( @@ -36,7 +37,7 @@ async fn add_tag( tag_dao .get_all_tags() .and_then(|tags| { - if let Some(tag) = tags.iter().find(|t| t.name == tag_name) { + if let Some((_, tag)) = tags.iter().find(|t| t.1.name == tag_name) { Ok(tag.clone()) } else { tag_dao.create_tag(&tag_name) @@ -63,7 +64,12 @@ async fn get_all_tags(_: Claims, tag_dao: web::Data>) -> imp let mut tag_dao = tag_dao.lock().expect("Unable to get TagDao"); tag_dao .get_all_tags() - .map(|tags| HttpResponse::Ok().json(tags)) + .map(|tags| HttpResponse::Ok().json(tags.iter().map(|(tag_count, tag)| + TagWithTagCount { + tag: tag.clone(), + tag_count: *tag_count, + } + ).collect::>())) .into_http_internal_err() } @@ -111,10 +117,10 @@ async fn update_tags( let new_tags = all_tags .iter() - .filter(|&t| !existing_tags.contains(t) && request.tag_ids.contains(&t.id)) - .collect::>(); + .filter(|(_, t)| !existing_tags.contains(t) && request.tag_ids.contains(&t.id)) + .collect::>(); - for new_tag in new_tags { + for (_, new_tag) in new_tags { info!( "Adding tag {:?} to file: {:?}", new_tag.name, request.file_name @@ -142,6 +148,13 @@ pub struct Tag { pub created_time: i64, } + +#[derive(Serialize, Debug)] +pub struct TagWithTagCount { + pub tag_count: i64, + pub tag: Tag, +} + #[derive(Insertable, Clone, Debug)] #[diesel(table_name = tags)] pub struct InsertTag { @@ -172,7 +185,7 @@ pub struct AddTagsRequest { } pub trait TagDao { - fn get_all_tags(&mut self) -> anyhow::Result>; + fn get_all_tags(&mut self) -> anyhow::Result>; fn get_tags_for_path(&mut self, path: &str) -> anyhow::Result>; fn create_tag(&mut self, name: &str) -> anyhow::Result; fn remove_tag(&mut self, tag_name: &str, path: &str) -> anyhow::Result>; @@ -197,9 +210,26 @@ impl Default for SqliteTagDao { } impl TagDao for SqliteTagDao { - fn get_all_tags(&mut self) -> anyhow::Result> { + fn get_all_tags(&mut self) -> anyhow::Result> { + // select name, count(*) from tags join tagged_photo ON tags.id = tagged_photo.tag_id GROUP BY tags.name ORDER BY COUNT(*); + let (id, name, created_time) = tags::all_columns; tags::table + .inner_join(tagged_photo::table) + .group_by(tags::id) + .select((count_star(), id, name, created_time)) .get_results(&mut self.connection) + .map::, _>(|tags_with_count: Vec<(i64, i32, String, i64)>| { + tags_with_count.iter().map(|tup| { + ( + tup.0, + Tag { + id: tup.1, + name: tup.2.clone(), + created_time: tup.3, + }, + ) + }).collect() + }) .with_context(|| "Unable to get all tags") } @@ -255,9 +285,9 @@ impl TagDao for SqliteTagDao { .filter(tagged_photo::tag_id.eq(tag.id)) .filter(tagged_photo::photo_name.eq(path)), ) - .execute(&mut self.connection) - .with_context(|| format!("Unable to delete tag: '{}'", &tag.name)) - .map(|_| Some(())) + .execute(&mut self.connection) + .with_context(|| format!("Unable to delete tag: '{}'", &tag.name)) + .map(|_| Some(())) } else { info!("No tag found with name '{}'", tag_name); Ok(None)