Add the count of tagged files to All tags endpoint #21

Merged
cameron merged 4 commits from feature/include-tag-counts into master 2024-01-18 03:54:39 +00:00
3 changed files with 50 additions and 16 deletions
Showing only changes of commit da21f064b9 - Show all commits

View File

@@ -459,7 +459,10 @@ mod tests {
#[test] #[test]
fn directory_traversal_test() { fn directory_traversal_test() {
let base = env::temp_dir(); 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, is_valid_full_path(&base, &PathBuf::from(".."), false));
assert_eq!( assert_eq!(
None, None,

View File

@@ -217,7 +217,8 @@ async fn stream_video(
debug!("Playlist: {}", playlist); debug!("Playlist: {}", playlist);
// Extract video playlist dir to dotenv // 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() HttpResponse::BadRequest().finish()
} else if let Ok(file) = NamedFile::open(playlist) { } else if let Ok(file) = NamedFile::open(playlist) {

View File

@@ -3,6 +3,7 @@ use actix_web::dev::{ServiceFactory, ServiceRequest};
use actix_web::{web, App, HttpResponse, Responder}; use actix_web::{web, App, HttpResponse, Responder};
use anyhow::Context; use anyhow::Context;
use chrono::Utc; use chrono::Utc;
use diesel::dsl::{count_star};
use diesel::prelude::*; use diesel::prelude::*;
use log::{debug, info}; use log::{debug, info};
use schema::{tagged_photo, tags}; use schema::{tagged_photo, tags};
@@ -36,7 +37,7 @@ async fn add_tag<D: TagDao>(
tag_dao tag_dao
.get_all_tags() .get_all_tags()
.and_then(|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()) Ok(tag.clone())
} else { } else {
tag_dao.create_tag(&tag_name) tag_dao.create_tag(&tag_name)
@@ -63,7 +64,12 @@ async fn get_all_tags<D: TagDao>(_: Claims, tag_dao: web::Data<Mutex<D>>) -> imp
let mut tag_dao = tag_dao.lock().expect("Unable to get TagDao"); let mut tag_dao = tag_dao.lock().expect("Unable to get TagDao");
tag_dao tag_dao
.get_all_tags() .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::<Vec<TagWithTagCount>>()))
.into_http_internal_err() .into_http_internal_err()
} }
@@ -111,10 +117,10 @@ async fn update_tags<D: TagDao>(
let new_tags = all_tags let new_tags = all_tags
.iter() .iter()
.filter(|&t| !existing_tags.contains(t) && request.tag_ids.contains(&t.id)) .filter(|(_, t)| !existing_tags.contains(t) && request.tag_ids.contains(&t.id))
.collect::<Vec<&Tag>>(); .collect::<Vec<&(i64, Tag)>>();
for new_tag in new_tags { for (_, new_tag) in new_tags {
info!( info!(
"Adding tag {:?} to file: {:?}", "Adding tag {:?} to file: {:?}",
new_tag.name, request.file_name new_tag.name, request.file_name
@@ -142,6 +148,13 @@ pub struct Tag {
pub created_time: i64, pub created_time: i64,
} }
#[derive(Serialize, Debug)]
pub struct TagWithTagCount {
pub tag_count: i64,
pub tag: Tag,
}
#[derive(Insertable, Clone, Debug)] #[derive(Insertable, Clone, Debug)]
#[diesel(table_name = tags)] #[diesel(table_name = tags)]
pub struct InsertTag { pub struct InsertTag {
@@ -172,7 +185,7 @@ pub struct AddTagsRequest {
} }
pub trait TagDao { pub trait TagDao {
fn get_all_tags(&mut self) -> anyhow::Result<Vec<Tag>>; fn get_all_tags(&mut self) -> anyhow::Result<Vec<(i64, Tag)>>;
fn get_tags_for_path(&mut self, path: &str) -> anyhow::Result<Vec<Tag>>; fn get_tags_for_path(&mut self, path: &str) -> anyhow::Result<Vec<Tag>>;
fn create_tag(&mut self, name: &str) -> anyhow::Result<Tag>; fn create_tag(&mut self, name: &str) -> anyhow::Result<Tag>;
fn remove_tag(&mut self, tag_name: &str, path: &str) -> anyhow::Result<Option<()>>; fn remove_tag(&mut self, tag_name: &str, path: &str) -> anyhow::Result<Option<()>>;
@@ -197,9 +210,26 @@ impl Default for SqliteTagDao {
} }
impl TagDao for SqliteTagDao { impl TagDao for SqliteTagDao {
fn get_all_tags(&mut self) -> anyhow::Result<Vec<Tag>> { fn get_all_tags(&mut self) -> anyhow::Result<Vec<(i64, Tag)>> {
// 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 tags::table
.inner_join(tagged_photo::table)
.group_by(tags::id)
.select((count_star(), id, name, created_time))
.get_results(&mut self.connection) .get_results(&mut self.connection)
.map::<Vec<(i64, Tag)>, _>(|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") .with_context(|| "Unable to get all tags")
} }