diff --git a/crates/service/web/storyteller_web/src/api_doc.rs b/crates/service/web/storyteller_web/src/api_doc.rs index ab9d55beca..dd52adcf7f 100644 --- a/crates/service/web/storyteller_web/src/api_doc.rs +++ b/crates/service/web/storyteller_web/src/api_doc.rs @@ -80,6 +80,7 @@ use crate::http_server::endpoints::media_files::list::list_media_files_by_batch_ use crate::http_server::endpoints::media_files::list::list_media_files_for_user_handler::*; use crate::http_server::endpoints::media_files::list::list_media_files_handler::*; use crate::http_server::endpoints::media_files::list::list_pinned_media_files_handler::*; +use crate::http_server::endpoints::media_files::search::search_media_files_handler::*; use crate::http_server::endpoints::media_files::upload::upload_audio_media_file_handler::*; use crate::http_server::endpoints::media_files::upload::upload_engine_asset::upload_engine_asset_media_file_handler::*; use crate::http_server::endpoints::media_files::upload::upload_error::MediaFileUploadError; @@ -157,6 +158,7 @@ use crate::http_server::web_utils::response_success_helpers::*; crate::http_server::endpoints::media_files::list::list_media_files_for_user_handler::list_media_files_for_user_handler, crate::http_server::endpoints::media_files::list::list_media_files_handler::list_media_files_handler, crate::http_server::endpoints::media_files::list::list_pinned_media_files_handler::list_pinned_media_files_handler, + crate::http_server::endpoints::media_files::search::search_media_files_handler::search_media_files_handler, crate::http_server::endpoints::media_files::upload::upload_audio_media_file_handler::upload_audio_media_file_handler, crate::http_server::endpoints::media_files::upload::upload_engine_asset::upload_engine_asset_media_file_handler::upload_engine_asset_media_file_handler, crate::http_server::endpoints::media_files::upload::upload_generic::upload_media_file_handler::upload_media_file_handler, @@ -293,6 +295,10 @@ use crate::http_server::web_utils::response_success_helpers::*; CreateFeaturedItemSuccessResponse, CreateSceneError, CreateSceneSuccessResponse, + SearchMediaFilesError, + SearchMediaFilesSuccessResponse, + SearchMediaFilesQueryParams, + SearchMediaFileListItem, CreateUserBookmarkError, CreateUserBookmarkRequest, CreateUserBookmarkSuccessResponse, diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/list_beta_keys_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/list_beta_keys_handler.rs index 36cbf4f0e7..2dccb73730 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/list_beta_keys_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/beta_keys/list_beta_keys_handler.rs @@ -35,9 +35,6 @@ use crate::http_server::common_responses::pagination_page::PaginationPage; use crate::http_server::common_responses::simple_entity_stats::SimpleEntityStats; use crate::http_server::endpoints::beta_keys::create_beta_keys_handler::CreateBetaKeysError; use crate::http_server::endpoints::events::list_events::ListEventsError; -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::require_moderator::{require_moderator, RequireModeratorError}; use crate::http_server::web_utils::require_user_session::{require_user_session, RequireUserSessionError}; use crate::server_state::ServerState; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/helpers/get_scoped_engine_categories.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/helpers/get_scoped_engine_categories.rs similarity index 100% rename from crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/helpers/get_scoped_engine_categories.rs rename to crates/service/web/storyteller_web/src/http_server/endpoints/media_files/helpers/get_scoped_engine_categories.rs diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/helpers/get_scoped_media_classes.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/helpers/get_scoped_media_classes.rs similarity index 100% rename from crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/helpers/get_scoped_media_classes.rs rename to crates/service/web/storyteller_web/src/http_server/endpoints/media_files/helpers/get_scoped_media_classes.rs diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/helpers/get_scoped_media_types.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/helpers/get_scoped_media_types.rs similarity index 100% rename from crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/helpers/get_scoped_media_types.rs rename to crates/service/web/storyteller_web/src/http_server/endpoints/media_files/helpers/get_scoped_media_types.rs diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/helpers/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/helpers/mod.rs similarity index 100% rename from crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/helpers/mod.rs rename to crates/service/web/storyteller_web/src/http_server/endpoints/media_files/helpers/mod.rs diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/list_featured_media_files_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/list_featured_media_files_handler.rs index 85c4f703a7..3449023dbd 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/list_featured_media_files_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/list_featured_media_files_handler.rs @@ -28,9 +28,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::endpoints::media_files::helpers::get_scoped_engine_categories::get_scoped_engine_categories; +use crate::http_server::endpoints::media_files::helpers::get_scoped_media_classes::get_scoped_media_classes; +use crate::http_server::endpoints::media_files::helpers::get_scoped_media_types::get_scoped_media_types; use crate::server_state::ServerState; use crate::util::allowed_explore_media_access::allowed_explore_media_access; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/list_media_files_for_user_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/list_media_files_for_user_handler.rs index 72e625c6d9..a86e47ed4e 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/list_media_files_for_user_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/list_media_files_for_user_handler.rs @@ -27,9 +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_page::PaginationPage; 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::endpoints::media_files::helpers::get_scoped_engine_categories::get_scoped_engine_categories; +use crate::http_server::endpoints::media_files::helpers::get_scoped_media_classes::get_scoped_media_classes; +use crate::http_server::endpoints::media_files::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_studio_access::allowed_studio_access; 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 a3a1b1e6f2..3554ad477c 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,9 +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::endpoints::media_files::helpers::get_scoped_engine_categories::get_scoped_engine_categories; +use crate::http_server::endpoints::media_files::helpers::get_scoped_media_classes::get_scoped_media_classes; +use crate::http_server::endpoints::media_files::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; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/mod.rs index c458806f96..2f9b7d5193 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/mod.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/mod.rs @@ -1,4 +1,3 @@ -pub mod helpers; pub mod list_featured_media_files_handler; pub mod list_media_files_by_batch_token_handler; pub mod list_media_files_for_user_handler; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/mod.rs index dc13211510..5498b75096 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/mod.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/mod.rs @@ -1,6 +1,8 @@ pub mod delete; pub mod edit; pub mod get; +pub mod helpers; pub mod list; +pub mod search; pub mod upload; -pub mod upsert_upload; \ No newline at end of file +pub mod upsert_upload; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/search/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/search/mod.rs new file mode 100644 index 0000000000..d54316ca9d --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/search/mod.rs @@ -0,0 +1 @@ +pub mod search_media_files_handler; \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/search/search_media_files_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/search/search_media_files_handler.rs new file mode 100644 index 0000000000..abf9ddd68e --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/search/search_media_files_handler.rs @@ -0,0 +1,279 @@ +use std::sync::Arc; + +use actix_web::{HttpMessage, HttpRequest, HttpResponse, web}; +use actix_web::error::ResponseError; +use actix_web::http::StatusCode; +use actix_web::web::Query; +use chrono::{DateTime, Utc}; +use log::warn; +use utoipa::{IntoParams, ToSchema}; + +use buckets::public::media_files::bucket_file_path::MediaFileBucketPath; +use elasticsearch_schema::searches::search_media_files::{search_media_files, SearchArgs}; +use enums::by_table::media_files::media_file_animation_type::MediaFileAnimationType; +use enums::by_table::media_files::media_file_class::MediaFileClass; +use enums::by_table::media_files::media_file_engine_category::MediaFileEngineCategory; +use enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory; +use enums::by_table::media_files::media_file_origin_model_type::MediaFileOriginModelType; +use enums::by_table::media_files::media_file_origin_product_category::MediaFileOriginProductCategory; +use enums::by_table::media_files::media_file_type::MediaFileType; +use enums::common::view_as::ViewAs; +use enums::common::visibility::Visibility; +use enums::no_table::style_transfer::style_transfer_name::StyleTransferName; +use tokens::tokens::media_files::MediaFileToken; +use users_component::common_responses::user_details_lite::UserDetailsLight; + +use crate::http_server::common_responses::media_file_cover_image_details::{MediaFileCoverImageDetails, MediaFileDefaultCover}; +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::helpers::get_scoped_engine_categories::get_scoped_engine_categories; +use crate::http_server::endpoints::media_files::helpers::get_scoped_media_classes::get_scoped_media_classes; +use crate::http_server::endpoints::media_files::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; +use crate::util::allowed_studio_access::allowed_studio_access; + +#[derive(Deserialize, ToSchema, IntoParams)] +pub struct SearchMediaFilesQueryParams { + /// REQUIRED: The search term. + /// + /// This supports typeahead, but the request should still be debounced. + pub search_term: String, + + /// NB: This can be one (or more comma-separated values) from `MediaFileClass`, + /// which are the broad category of media files: image, video, etc. + /// + /// Usage: + /// - `?filter_media_classes=audio` + /// - `?filter_media_classes=image,video` + /// - etc. + pub filter_media_classes: Option, + + /// NB: This can be one (or more comma-separated values) from `MediaFileType`, + /// which are mimetype-like / format-like categories of media files: glb, gltf, + /// scene_json, etc. + /// + /// Usage: + /// - `?filter_media_type=scene_json` + /// - `?filter_media_type=glb,gltf` + /// - etc. + pub filter_media_type: Option, + + /// NB: This can be one (or more comma-separated values) from `MediaFileEngineCategory`. + /// + /// Usage: + /// - `?filter_engine_categories=scene` + /// - `?filter_engine_categories=animation,character,object` + /// - etc. + pub filter_engine_categories: Option, +} + +#[derive(Serialize, ToSchema)] +pub struct SearchMediaFilesSuccessResponse { + pub success: bool, + pub results: Vec, +} + +#[derive(Serialize, ToSchema)] +pub struct SearchMediaFileListItem { + pub token: MediaFileToken, + + /// The coarse-grained class of media file: image, video, etc. + pub media_class: MediaFileClass, + + /// Type of media will dictate which fields are populated and what + /// the frontend should display (eg. video player vs audio player). + /// This is closer in meaning to a "mime type". + pub media_type: MediaFileType, + + /// If this is an engine/3D asset, this is the broad category (scene, + /// animation, etc.) of that object. + /// This can also be used for filtering in list/batch endpoints. + pub maybe_engine_category: Option, + + /// If this is an engine/3D asset for an animation or a rig that can + /// be animated with either (or both) skeletal or blend shape animations, + /// this describes the animation regime used or supported. + pub maybe_animation_type: Option, + + // #[deprecated(note="Use MediaFileOriginDetails instead")] + // pub origin_category: MediaFileOriginCategory, + + // #[deprecated(note="Use MediaFileOriginDetails instead")] + // pub origin_product_category: MediaFileOriginProductCategory, + + // #[deprecated(note="Use MediaFileOriginDetails instead")] + // pub maybe_origin_model_type: Option, + + // #[deprecated(note="Use MediaFileOriginDetails instead")] + // pub maybe_origin_model_token: Option, + + // /// Details where the media file came from. + // pub origin: MediaFileOriginDetails, + + /// URL to the media file. + pub public_bucket_path: String, + + /// Information about the cover image. Many media files do not require a cover image, + /// e.g. image files, video files with thumbnails, audio files, etc. + /// 3D files require them. + pub cover_image: MediaFileCoverImageDetails, + + pub maybe_creator: Option, + + // /// Statistics about the media file + // pub stats: SimpleEntityStats, + + pub creator_set_visibility: Visibility, + + /// The name or title of the media file (optional) + pub maybe_title: Option, + + // /// Text transcripts for TTS, etc. + // pub maybe_text_transcript: Option, + + // /// For Comfy / Video Style Transfer jobs, this might include + // /// the name of the selected style. + // pub maybe_style_name: Option, + + // /// Duration for audio and video files, if available. + // /// Measured in milliseconds. + // pub maybe_duration_millis: Option, + + pub created_at: DateTime, + pub updated_at: DateTime, +} + +#[derive(Debug, ToSchema)] +pub enum SearchMediaFilesError { + ServerError, + NotAuthorized, +} + +impl ResponseError for SearchMediaFilesError { + fn status_code(&self) -> StatusCode { + match *self { + SearchMediaFilesError::ServerError => StatusCode::INTERNAL_SERVER_ERROR, + SearchMediaFilesError::NotAuthorized => StatusCode::UNAUTHORIZED, + } + } + + fn error_response(&self) -> HttpResponse { + let error_reason = match self { + SearchMediaFilesError::ServerError => "server error".to_string(), + SearchMediaFilesError::NotAuthorized => "not authorized".to_string(), + }; + + to_simple_json_error(&error_reason, self.status_code()) + } +} + +impl std::fmt::Display for SearchMediaFilesError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +/// Search for media files based on various criteria. +#[utoipa::path( + get, + tag = "Media Files", + path = "/v1/media_files/search", + params(SearchMediaFilesQueryParams), + responses( + (status = 200, description = "List Featured Media Files", body = SearchMediaFilesSuccessResponse), + (status = 500, description = "Server error", body = SearchMediaFilesError), + ), +)] +pub async fn search_media_files_handler( + http_request: HttpRequest, + query: Query, + server_state: web::Data> +) -> Result +{ + 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 results = search_media_files(SearchArgs { + search_term: &query.search_term, + maybe_media_classes: maybe_filter_media_classes, + maybe_media_types: maybe_filter_media_types, + maybe_engine_categories: maybe_filter_engine_categories, + client: &server_state.elasticsearch, + }).await; + + let results = match results { + Ok(results) => results, + Err(err) => { + warn!("Searching error: {:?}", err); + return Err(SearchMediaFilesError::ServerError); + } + }; + + let results = results.into_iter() + .map(|result| SearchMediaFileListItem { + token: result.token.clone(), + media_class: result.media_class, + media_type: result.media_type, + maybe_engine_category: result.maybe_engine_category, + maybe_animation_type: result.maybe_animation_type, + // origin: MediaFileOriginDetails::from_db_fields_str( + // result.origin_category, + // result.origin_product_category, + // result.maybe_origin_model_type, + // result.maybe_origin_model_token.as_deref(), + // result.maybe_origin_model_title.as_deref()), + // origin_category: result.origin_category, + // origin_product_category: result.origin_product_category, + // maybe_origin_model_type: result.maybe_origin_model_type, + // maybe_origin_model_token: result.maybe_origin_model_token, + public_bucket_path: MediaFileBucketPath::from_object_hash( + &result.public_bucket_directory_hash, + result.maybe_public_bucket_prefix.as_deref(), + result.maybe_public_bucket_extension.as_deref()) + .get_full_object_path_str() + .to_string(), + cover_image: MediaFileCoverImageDetails::from_optional_db_fields( + &result.token, + result.maybe_cover_image_public_bucket_hash.as_deref(), + result.maybe_cover_image_public_bucket_prefix.as_deref(), + result.maybe_cover_image_public_bucket_extension.as_deref(), + ), + maybe_creator: UserDetailsLight::from_optional_db_fields_owned( + result.maybe_creator_user_token, + result.maybe_creator_username, + result.maybe_creator_display_name, + result.maybe_creator_gravatar_hash, + ), + // stats: SimpleEntityStats { + // positive_rating_count: result.maybe_ratings_positive_count.unwrap_or(0), + // bookmark_count: result.maybe_bookmark_count.unwrap_or(0), + // }, + creator_set_visibility: result.creator_set_visibility, + maybe_title: result.maybe_title, + // maybe_text_transcript: result.maybe_text_transcript, + // maybe_style_name: result.maybe_prompt_args + // .as_ref() + // .and_then(|args| args.style_name.as_ref()) + // .and_then(|style| style.to_style_name()), + // maybe_duration_millis: result.maybe_duration_millis, + created_at: result.created_at, + updated_at: result.updated_at, + }) + .collect::>(); + + let response = SearchMediaFilesSuccessResponse { + success: true, + results, + }; + + let body = serde_json::to_string(&response) + .map_err(|e| SearchMediaFilesError::ServerError)?; + + Ok(HttpResponse::Ok() + .content_type("application/json") + .body(body)) +} diff --git a/crates/service/web/storyteller_web/src/routes/media_files_routes.rs b/crates/service/web/storyteller_web/src/routes/media_files_routes.rs index 531c8a129b..3431b7c995 100644 --- a/crates/service/web/storyteller_web/src/routes/media_files_routes.rs +++ b/crates/service/web/storyteller_web/src/routes/media_files_routes.rs @@ -15,6 +15,7 @@ use crate::http_server::endpoints::media_files::list::list_media_files_by_batch_ use crate::http_server::endpoints::media_files::list::list_media_files_for_user_handler::list_media_files_for_user_handler; use crate::http_server::endpoints::media_files::list::list_media_files_handler::list_media_files_handler; use crate::http_server::endpoints::media_files::list::list_pinned_media_files_handler::list_pinned_media_files_handler; +use crate::http_server::endpoints::media_files::search::search_media_files_handler::search_media_files_handler; use crate::http_server::endpoints::media_files::upload::upload_audio_media_file_handler::upload_audio_media_file_handler; use crate::http_server::endpoints::media_files::upload::upload_engine_asset::upload_engine_asset_media_file_handler::upload_engine_asset_media_file_handler; use crate::http_server::endpoints::media_files::upload::upload_generic::upload_media_file_handler::upload_media_file_handler; @@ -85,6 +86,10 @@ pub fn add_media_file_routes (app: App) -> App .route(web::get().to(list_media_files_for_user_handler)) .route(web::head().to(|| HttpResponse::Ok())) ) + .service(web::resource("/search") + .route(web::get().to(search_media_files_handler)) + .route(web::head().to(|| HttpResponse::Ok())) + ) .service(web::resource("/upload") .route(web::post().to(upload_media_file_handler)) .route(web::head().to(|| HttpResponse::Ok()))