diff --git a/crates/lib/mysql_queries/src/queries/media_files/list/list_media_files.rs b/crates/lib/mysql_queries/src/queries/media_files/list/list_media_files.rs index 6a698266ab..220460db51 100644 --- a/crates/lib/mysql_queries/src/queries/media_files/list/list_media_files.rs +++ b/crates/lib/mysql_queries/src/queries/media_files/list/list_media_files.rs @@ -1,3 +1,4 @@ +use std::collections::HashSet; use chrono::{DateTime, Utc}; use sqlx::{FromRow, MySql, MySqlPool, QueryBuilder, Row}; use sqlx::mysql::MySqlRow; @@ -84,7 +85,9 @@ pub struct MediaFileListItem { pub struct ListMediaFilesArgs<'a> { pub limit: usize, - pub maybe_filter_media_type: Option, + pub maybe_filter_media_types: Option<&'a HashSet>, + pub maybe_filter_media_classes: Option<&'a HashSet>, + pub maybe_filter_engine_categories: Option<&'a HashSet>, pub maybe_offset: Option, pub cursor_is_reversed: bool, pub sort_ascending: bool, @@ -95,7 +98,9 @@ pub struct ListMediaFilesArgs<'a> { pub async fn list_media_files(args: ListMediaFilesArgs<'_>) -> AnyhowResult { let mut query = query_builder( - args.maybe_filter_media_type, + args.maybe_filter_media_types, + args.maybe_filter_media_classes, + args.maybe_filter_engine_categories, args.limit, args.maybe_offset, args.cursor_is_reversed, @@ -166,7 +171,9 @@ pub async fn list_media_files(args: ListMediaFilesArgs<'_>) -> AnyhowResult( - maybe_filter_media_type: Option, + maybe_filter_media_types: Option<&HashSet>, + maybe_filter_media_classes: Option<&HashSet>, + maybe_filter_engine_categories: Option<&HashSet>, limit: usize, maybe_offset: Option, cursor_is_reversed: bool, @@ -248,18 +255,70 @@ LEFT OUTER JOIN prompts let mut first_predicate_added = false; - if let Some(media_type) = maybe_filter_media_type { - if !first_predicate_added { - query_builder.push(" WHERE "); - first_predicate_added = true; - } else { - query_builder.push(" AND "); + if let Some(media_types) = maybe_filter_media_types { + // NB: `WHERE IN` comma separated syntax will be wrong if list has zero length + // We'll skip the predicate if the list isn't empty. + if !media_types.is_empty() { + if !first_predicate_added { + query_builder.push(" WHERE "); + first_predicate_added = true; + } else { + query_builder.push(" AND "); + } + query_builder.push(" m.media_type IN ( "); + + let mut separated = query_builder.separated(", "); + + for media_type in media_types.iter() { + separated.push_bind(media_type.to_str()); + } + + separated.push_unseparated(") "); + } + } + + if let Some(media_classes) = maybe_filter_media_classes { + // NB: `WHERE IN` comma separated syntax will be wrong if list has zero length + // We'll skip the predicate if the list isn't empty. + if !media_classes.is_empty() { + if !first_predicate_added { + query_builder.push(" WHERE "); + first_predicate_added = true; + } else { + query_builder.push(" AND "); + } + query_builder.push(" m.media_class IN ( "); + + let mut separated = query_builder.separated(", "); + + for media_class in media_classes.iter() { + separated.push_bind(media_class.to_str()); + } + + separated.push_unseparated(") "); + } + } + + if let Some(engine_categories) = maybe_filter_engine_categories { + // NB: `WHERE IN` comma separated syntax will be wrong if list has zero length + // We'll skip the predicate if the list isn't empty. + if !engine_categories.is_empty() { + if !first_predicate_added { + query_builder.push(" WHERE "); + first_predicate_added = true; + } else { + query_builder.push(" AND "); + } + query_builder.push(" m.maybe_engine_category IN ( "); + + let mut separated = query_builder.separated(", "); + + for engine_category in engine_categories.iter() { + separated.push_bind(engine_category.to_str()); + } + + separated.push_unseparated(") "); } - // FIXME: Binding shouldn't require to_str(). - // Otherwise, it's calling the Display trait on the raw type which is resulting in an - // incorrect binding and runtime error. - query_builder.push(" m.media_type = "); - query_builder.push_bind(media_type.to_str()); } match view_as { diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/list_media_files_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/list_media_files_handler.rs index 43141d3ecd..d050d50f06 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/list_media_files_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/list_media_files_handler.rs @@ -27,6 +27,9 @@ use crate::http_server::common_responses::media_file_cover_image_details::{Media use crate::http_server::common_responses::media_file_origin_details::MediaFileOriginDetails; use crate::http_server::common_responses::pagination_cursors::PaginationCursors; use crate::http_server::common_responses::simple_entity_stats::SimpleEntityStats; +use crate::http_server::endpoints::media_files::list::helpers::get_scoped_engine_categories::get_scoped_engine_categories; +use crate::http_server::endpoints::media_files::list::helpers::get_scoped_media_classes::get_scoped_media_classes; +use crate::http_server::endpoints::media_files::list::helpers::get_scoped_media_types::get_scoped_media_types; use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; use crate::server_state::ServerState; use crate::util::allowed_explore_media_access::allowed_explore_media_access; @@ -39,7 +42,17 @@ pub struct ListMediaFilesQueryParams { pub cursor: Option, pub cursor_is_reversed: Option, - pub filter_media_type: Option, + /// NB: This can be one (or more comma-separated values) from `MediaFileType`. + /// ?filter_media_type=image or ?filter_media_type=image,video (etc.) + pub filter_media_type: Option, + + /// NB: This can be one (or more comma-separated values) from `MediaFileClass`. + /// ?filter_media_type=animation or ?filter_media_type=animation,character (etc.) + pub filter_media_classes: Option, + + /// NB: This can be one (or more comma-separated values) from `MediaFileEngineCategory`. + /// ?filter_engine_categories=scene or ?filter_engine_categories=animation,character,object (etc.) + pub filter_engine_categories: Option, } #[derive(Serialize, ToSchema)] @@ -213,9 +226,15 @@ pub async fn list_media_files_handler( ViewAs::AnotherUser }; + let mut maybe_filter_media_types = get_scoped_media_types(query.filter_media_type.as_deref()); + let mut maybe_filter_media_classes = get_scoped_media_classes(query.filter_media_classes.as_deref()); + let mut maybe_filter_engine_categories = get_scoped_engine_categories(query.filter_engine_categories.as_deref()); + let query_results = list_media_files(ListMediaFilesArgs { limit, - maybe_filter_media_type: query.filter_media_type, + maybe_filter_media_types: maybe_filter_media_types.as_ref(), + maybe_filter_media_classes: maybe_filter_media_classes.as_ref(), + maybe_filter_engine_categories: maybe_filter_engine_categories.as_ref(), maybe_offset: cursor, cursor_is_reversed, sort_ascending,