diff --git a/crates/adapters/sqlite/src/library.rs b/crates/adapters/sqlite/src/library.rs index 7b4e5c9..71080f0 100644 --- a/crates/adapters/sqlite/src/library.rs +++ b/crates/adapters/sqlite/src/library.rs @@ -273,6 +273,19 @@ impl LibraryQuery for SqliteLibraryRepository { .collect(); conditions.push(format!("({})", genre_conditions.join(" OR "))); } + if !filter.tags().is_empty() { + let tag_conditions: Vec = filter + .tags() + .iter() + .map(|t| { + format!( + "EXISTS (SELECT 1 FROM json_each(library_items.tags) WHERE LOWER(value) = LOWER('{}'))", + t.replace('\'', "''") + ) + }) + .collect(); + conditions.push(format!("({})", tag_conditions.join(" OR "))); + } if let Some(sn) = filter.season_number() { conditions.push(format!("season_number = {}", sn)); } diff --git a/crates/domain/src/services/schedule/mod.rs b/crates/domain/src/services/schedule/mod.rs index 17f40df..27049d9 100644 --- a/crates/domain/src/services/schedule/mod.rs +++ b/crates/domain/src/services/schedule/mod.rs @@ -702,7 +702,7 @@ fn media_filter_to_library_search(filter: &crate::value_objects::MediaFilter) -> lsf = lsf.with_search_term(term.clone()); } if !filter.tags.is_empty() { - // tags map to the same concept in the library + lsf = lsf.with_tags(filter.tags.clone()); } lsf } diff --git a/crates/domain/src/value_objects/search.rs b/crates/domain/src/value_objects/search.rs index f517264..5a7dbff 100644 --- a/crates/domain/src/value_objects/search.rs +++ b/crates/domain/src/value_objects/search.rs @@ -13,6 +13,7 @@ pub struct LibrarySearchFilter { min_duration_secs: Option, max_duration_secs: Option, search_term: Option, + tags: Vec, season_number: Option, role: Option, offset: u32, @@ -60,6 +61,10 @@ impl LibrarySearchFilter { self.search_term = Some(term.into()); self } + pub fn with_tags(mut self, tags: Vec) -> Self { + self.tags = tags; + self + } pub fn with_season_number(mut self, n: u32) -> Self { self.season_number = Some(n); self @@ -104,6 +109,9 @@ impl LibrarySearchFilter { pub fn search_term(&self) -> Option<&str> { self.search_term.as_deref() } + pub fn tags(&self) -> &[String] { + &self.tags + } pub fn season_number(&self) -> Option { self.season_number } @@ -130,6 +138,7 @@ impl Default for LibrarySearchFilter { min_duration_secs: None, max_duration_secs: None, search_term: None, + tags: vec![], season_number: None, role: None, offset: 0,