From 7b8439e2a640aa05b346ea172c82816d7a052faa Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Sun, 5 May 2024 02:44:49 -0400 Subject: [PATCH] fork featured media files -> pinned media files --- .../web/storyteller_web/src/api_doc.rs | 5 + .../list/list_pinned_media_files_handler.rs | 244 ++++++++++++++++++ .../endpoints/media_files/list/mod.rs | 1 + .../src/routes/media_files_routes.rs | 5 + 4 files changed, 255 insertions(+) create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/list_pinned_media_files_handler.rs diff --git a/crates/service/web/storyteller_web/src/api_doc.rs b/crates/service/web/storyteller_web/src/api_doc.rs index 7fd074695b..dbbb7e2d2f 100644 --- a/crates/service/web/storyteller_web/src/api_doc.rs +++ b/crates/service/web/storyteller_web/src/api_doc.rs @@ -59,6 +59,7 @@ use crate::http_server::endpoints::media_files::list::list_featured_media_files_ use crate::http_server::endpoints::media_files::list::list_media_files_by_batch_token_handler::*; 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::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; @@ -117,6 +118,7 @@ use crate::http_server::web_utils::response_success_helpers::*; crate::http_server::endpoints::media_files::list::list_media_files_by_batch_token_handler::list_media_files_by_batch_token_handler, 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::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, @@ -305,6 +307,8 @@ use crate::http_server::web_utils::response_success_helpers::*; ListMediaFilesForUserSuccessResponse, ListMediaFilesQueryParams, ListMediaFilesSuccessResponse, + ListPinnedMediaFilesError, + ListPinnedMediaFilesSuccessResponse, ListSessionJobsError, ListSessionJobsItem, ListSessionJobsQueryParams, @@ -337,6 +341,7 @@ use crate::http_server::web_utils::response_success_helpers::*; MediaFileUploadError, ModelWeightForList, ModelWeightSearchResult, + PinnedMediaFile, ProfileError, RatingRow, RenameMediaFileError, diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/list_pinned_media_files_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/list_pinned_media_files_handler.rs new file mode 100644 index 0000000000..6a0794290b --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list/list_pinned_media_files_handler.rs @@ -0,0 +1,244 @@ +use std::sync::Arc; + +use actix_web::{HttpRequest, HttpResponse, web}; +use actix_web::error::ResponseError; +use actix_web::http::StatusCode; +use chrono::{DateTime, Utc}; +use log::{debug, error, warn}; +use r2d2_redis::redis::Commands; +use utoipa::ToSchema; + +use buckets::public::media_files::bucket_file_path::MediaFileBucketPath; +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::no_table::style_transfer::style_transfer_name::StyleTransferName; +use mysql_queries::queries::media_files::get::batch_get_media_files_by_tokens::batch_get_media_files_by_tokens; +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::simple_entity_stats::SimpleEntityStats; +use crate::server_state::ServerState; + +#[derive(Serialize, ToSchema)] +pub struct ListPinnedMediaFilesSuccessResponse { + pub success: bool, + pub results: Vec, +} + +#[derive(Serialize, ToSchema)] +pub struct PinnedMediaFile { + 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, + + /// 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, + + #[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, + + pub maybe_creator: Option, + + /// 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, + + /// Statistics about the media file + pub stats: SimpleEntityStats, + + pub created_at: DateTime, + pub updated_at: DateTime, +} + +/// The key we store pinned media file tokens under +const REDIS_KEY : &str = "pinned_media_files_list"; + +#[derive(Debug, ToSchema)] +pub enum ListPinnedMediaFilesError { + NotAuthorized, + ServerError, +} + +impl std::fmt::Display for ListPinnedMediaFilesError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +impl ResponseError for ListPinnedMediaFilesError { + fn status_code(&self) -> StatusCode { + match *self { + ListPinnedMediaFilesError::NotAuthorized => StatusCode::UNAUTHORIZED, + ListPinnedMediaFilesError::ServerError => StatusCode::INTERNAL_SERVER_ERROR, + } + } +} + +/// List media files that were pinned by the staff (paginated). +#[utoipa::path( + get, + tag = "Media Files", + path = "/v1/media_files/list_pinned", + responses( + (status = 200, description = "List Featured Media Files", body = ListPinnedMediaFilesSuccessResponse), + (status = 401, description = "Not authorized", body = ListPinnedMediaFilesError), + (status = 500, description = "Server error", body = ListPinnedMediaFilesError), + ), +)] +pub async fn list_pinned_media_files_handler( + http_request: HttpRequest, + server_state: web::Data> +) -> Result { + + let mut redis = server_state.redis_pool.get() + .map_err(|err| { + error!("Could not obtain redis: {err}"); + ListPinnedMediaFilesError::ServerError + })?; + + let token_list : Option = redis.get(REDIS_KEY) + .map_err(|err| { + error!("Could not get redis result: {err}"); + ListPinnedMediaFilesError::ServerError + })?; + + let media_file_tokens = token_list + .unwrap_or_else(|| "".to_string()) + .split(",") + .into_iter() + .map(|item| item.trim()) + .filter(|item| !item.is_empty()) + .map(|item| MediaFileToken::new_from_str(item)) + .collect::>(); + + debug!("Weight tokens from Redis: {:?}", media_file_tokens); + + let mut media_files = Vec::new(); + + if !media_file_tokens.is_empty() { + let query_results = + batch_get_media_files_by_tokens(&server_state.mysql_pool, &media_file_tokens, false).await; + + media_files = match query_results { + Ok(media_files) => media_files, + Err(e) => { + warn!("Query error: {:?}", e); + return Err(ListPinnedMediaFilesError::ServerError); + } + }; + } + + let response = ListPinnedMediaFilesSuccessResponse { + success: true, + results: media_files.into_iter() + .map(|m| { + let public_bucket_path = MediaFileBucketPath::from_object_hash( + &m.public_bucket_directory_hash, + m.maybe_public_bucket_prefix.as_deref(), + m.maybe_public_bucket_extension.as_deref()) + .get_full_object_path_str() + .to_string(); + + PinnedMediaFile { + token: m.token.clone(), + media_class: m.media_class, + media_type: m.media_type, + maybe_engine_category: m.maybe_engine_category, + maybe_animation_type: m.maybe_animation_type, + public_bucket_path, + cover_image: MediaFileCoverImageDetails::from_optional_db_fields( + &m.token, + m.maybe_file_cover_image_public_bucket_hash.as_deref(), + m.maybe_file_cover_image_public_bucket_prefix.as_deref(), + m.maybe_file_cover_image_public_bucket_extension.as_deref(), + ), + origin: MediaFileOriginDetails::from_db_fields_str( + m.origin_category, + m.origin_product_category, + m.maybe_origin_model_type, + m.maybe_origin_model_token.as_deref(), + m.maybe_origin_model_title.as_deref()), + origin_category: m.origin_category, + origin_product_category: m.origin_product_category, + maybe_origin_model_type: m.maybe_origin_model_type, + maybe_origin_model_token: m.maybe_origin_model_token, + maybe_creator: UserDetailsLight::from_optional_db_fields_owned( + m.maybe_creator_user_token, + m.maybe_creator_username, + m.maybe_creator_display_name, + m.maybe_creator_email_gravatar_hash + ), + maybe_title: m.maybe_title, + maybe_text_transcript: m.maybe_text_transcript, + maybe_style_name: m.maybe_prompt_args + .as_ref() + .and_then(|args| args.style_name.as_ref()) + .and_then(|style| style.to_style_name()), + maybe_duration_millis: m.maybe_duration_millis, + stats: SimpleEntityStats { + positive_rating_count: m.maybe_ratings_positive_count.unwrap_or(0), + bookmark_count: m.maybe_bookmark_count.unwrap_or(0), + }, + created_at: m.created_at, + updated_at: m.updated_at, + } + }).collect::>(), + }; + + let body = serde_json::to_string(&response) + .map_err(|e| ListPinnedMediaFilesError::ServerError)?; + + Ok(HttpResponse::Ok() + .content_type("application/json") + .body(body)) +} 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 53237cfdc0..c458806f96 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 @@ -3,3 +3,4 @@ pub mod list_featured_media_files_handler; pub mod list_media_files_by_batch_token_handler; pub mod list_media_files_for_user_handler; pub mod list_media_files_handler; +pub mod list_pinned_media_files_handler; 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 d484942fb4..2e8bba7d71 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 @@ -14,6 +14,7 @@ use crate::http_server::endpoints::media_files::list::list_featured_media_files_ 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_media_files_by_batch_token_handler::list_media_files_by_batch_token_handler; 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_pinned_media_files_handler::list_pinned_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; @@ -72,6 +73,10 @@ pub fn add_media_file_routes (app: App) -> App .route(web::get().to(list_featured_media_files_handler)) .route(web::head().to(|| HttpResponse::Ok())) ) + .service(web::resource("/list_pinned") + .route(web::get().to(list_pinned_media_files_handler)) + .route(web::head().to(|| HttpResponse::Ok())) + ) .service(web::resource("/list/user/{username}") .route(web::get().to(list_media_files_for_user_handler)) .route(web::head().to(|| HttpResponse::Ok()))