diff --git a/crates/_types/enums/src/by_table/media_files/media_file_origin_category.rs b/crates/_types/enums/src/by_table/media_files/media_file_origin_category.rs index 39c87a33e0..fd6f36c06e 100644 --- a/crates/_types/enums/src/by_table/media_files/media_file_origin_category.rs +++ b/crates/_types/enums/src/by_table/media_files/media_file_origin_category.rs @@ -6,12 +6,13 @@ use serde::Serialize; use strum::EnumCount; #[cfg(test)] use strum::EnumIter; +use utoipa::ToSchema; /// Used in the `media_files` table in a `VARCHAR` field. /// /// DO NOT CHANGE VALUES WITHOUT A MIGRATION STRATEGY. #[cfg_attr(test, derive(EnumIter, EnumCount))] -#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)] +#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize, ToSchema)] #[serde(rename_all = "snake_case")] pub enum MediaFileOriginCategory { /// ML model inference output - uploaded models or zero shot. diff --git a/crates/_types/enums/src/by_table/media_files/media_file_origin_model_type.rs b/crates/_types/enums/src/by_table/media_files/media_file_origin_model_type.rs index fa75c7f791..3fc35a9bb1 100644 --- a/crates/_types/enums/src/by_table/media_files/media_file_origin_model_type.rs +++ b/crates/_types/enums/src/by_table/media_files/media_file_origin_model_type.rs @@ -6,12 +6,13 @@ use serde::Serialize; use strum::EnumCount; #[cfg(test)] use strum::EnumIter; +use utoipa::ToSchema; /// Used in the `media_files` table in a `VARCHAR` field. /// /// DO NOT CHANGE VALUES WITHOUT A MIGRATION STRATEGY. #[cfg_attr(test, derive(EnumIter, EnumCount))] -#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)] +#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize, ToSchema)] #[serde(rename_all = "snake_case")] pub enum MediaFileOriginModelType { /// RVC (v2) voice conversion models diff --git a/crates/_types/enums/src/by_table/media_files/media_file_origin_product_category.rs b/crates/_types/enums/src/by_table/media_files/media_file_origin_product_category.rs index e74da29ecf..ccf1266246 100644 --- a/crates/_types/enums/src/by_table/media_files/media_file_origin_product_category.rs +++ b/crates/_types/enums/src/by_table/media_files/media_file_origin_product_category.rs @@ -4,6 +4,7 @@ use std::collections::BTreeSet; use strum::EnumCount; #[cfg(test)] use strum::EnumIter; +use utoipa::ToSchema; /// Used in the `media_files` table in `VARCHAR(16)` field `origin_product_category`. /// @@ -14,7 +15,7 @@ use strum::EnumIter; /// /// DO NOT CHANGE VALUES WITHOUT A MIGRATION STRATEGY. #[cfg_attr(test, derive(EnumIter, EnumCount))] -#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)] +#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize, ToSchema)] pub enum MediaFileOriginProductCategory { /// Media files created by (or uploaded for) the Face Animator product. /// The underlying model could be SadTalker, Wav2Lip, or some future model diff --git a/crates/_types/enums/src/by_table/media_files/media_file_type.rs b/crates/_types/enums/src/by_table/media_files/media_file_type.rs index 08fe71f572..8ba5f0ecb9 100644 --- a/crates/_types/enums/src/by_table/media_files/media_file_type.rs +++ b/crates/_types/enums/src/by_table/media_files/media_file_type.rs @@ -4,12 +4,13 @@ use std::collections::BTreeSet; use strum::EnumCount; #[cfg(test)] use strum::EnumIter; +use utoipa::ToSchema; /// Used in the `media_files` table in a `VARCHAR` field. /// /// DO NOT CHANGE VALUES WITHOUT A MIGRATION STRATEGY. #[cfg_attr(test, derive(EnumIter, EnumCount))] -#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Deserialize, Serialize)] +#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Deserialize, Serialize, ToSchema)] #[serde(rename_all = "lowercase")] pub enum MediaFileType { /// Audio files: wav, mp3, etc. diff --git a/crates/_types/tokens/src/tokens/media_files.rs b/crates/_types/tokens/src/tokens/media_files.rs index 6fa878d6b1..4a304ca810 100644 --- a/crates/_types/tokens/src/tokens/media_files.rs +++ b/crates/_types/tokens/src/tokens/media_files.rs @@ -2,11 +2,12 @@ use std::fmt::Debug; use serde::Deserialize; use serde::Serialize; +use utoipa::ToSchema; use crate::prefixes::TokenPrefix; /// The primary key for Audit Logs. -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, sqlx::Type, Debug, Serialize, Deserialize)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, sqlx::Type, Debug, Serialize, Deserialize, ToSchema)] #[sqlx(transparent)] pub struct MediaFileToken(pub String); diff --git a/crates/lib/mysql_queries/src/queries/media_files/list_media_files.rs b/crates/lib/mysql_queries/src/queries/media_files/list_media_files.rs index 15571f0563..aab6042dd2 100644 --- a/crates/lib/mysql_queries/src/queries/media_files/list_media_files.rs +++ b/crates/lib/mysql_queries/src/queries/media_files/list_media_files.rs @@ -44,6 +44,9 @@ pub struct MediaFileListItem { pub creator_set_visibility: Visibility, + pub comment_count: u64, + pub favorite_count: u64, + pub created_at: DateTime, pub updated_at: DateTime, } @@ -100,6 +103,8 @@ pub async fn list_media_files(args: ListMediaFilesArgs<'_>) -> AnyhowResult, updated_at: DateTime, } @@ -257,6 +276,8 @@ impl FromRow<'_, MySqlRow> for MediaFileListItemInternal { creator_set_visibility: Visibility::try_from_mysql_row(row, "creator_set_visibility")?, created_at: row.try_get("created_at")?, updated_at: row.try_get("updated_at")?, + comment_count: row.try_get("comment_count")?, + favorite_count: row.try_get("favorite_count")?, }) } } diff --git a/crates/lib/mysql_queries/src/queries/media_files/list_media_files_for_user.rs b/crates/lib/mysql_queries/src/queries/media_files/list_media_files_for_user.rs index c8f32280d8..cfe474efbf 100644 --- a/crates/lib/mysql_queries/src/queries/media_files/list_media_files_for_user.rs +++ b/crates/lib/mysql_queries/src/queries/media_files/list_media_files_for_user.rs @@ -51,7 +51,7 @@ pub struct ListMediaFileForUserArgs<'a> { pub maybe_filter_media_type: Option, pub page_size: usize, pub page_index: usize, - pub cursor_is_reversed: bool, + pub sort_ascending: bool, pub view_as: ViewAs, pub mysql_pool: &'a MySqlPool, } @@ -66,16 +66,13 @@ pub async fn list_media_files_for_user(args: ListMediaFileForUserArgs<'_>) -> An false, 0, 0, - args.cursor_is_reversed, + args.sort_ascending, args.view_as, count_fields.as_str(), ); let row_count_query = count_query_builder.build_query_scalar::(); let row_count_result = row_count_query.fetch_one(args.mysql_pool).await?; - /// Figure out limit start and end based on page size and indexes as requested - let limit_start = args.page_size * args.page_index; - let limit_end = limit_start + args.page_size; /// Now fetch the actual results with all the fields let result_fields = select_result_fields(); @@ -83,9 +80,9 @@ pub async fn list_media_files_for_user(args: ListMediaFileForUserArgs<'_>) -> An args.maybe_filter_media_type, args.username, true, - limit_start, - limit_end, - args.cursor_is_reversed, + args.page_index, + args.page_size, + args.sort_ascending, args.view_as, result_fields.as_str(), ); @@ -115,7 +112,7 @@ pub async fn list_media_files_for_user(args: ListMediaFileForUserArgs<'_>) -> An Ok(MediaFileListPage { records: results, - sort_ascending: !args.cursor_is_reversed, + sort_ascending: args.sort_ascending, current_page: args.page_index, total_page_count: number_of_pages, }) @@ -157,9 +154,9 @@ fn query_builder<'a>( maybe_filter_media_type: Option, username: &'a str, enforce_limits: bool, - limit_start: usize, - limit_end: usize, - cursor_is_reversed: bool, + page_index: usize, + page_size: usize, + sort_ascending: bool, view_as: ViewAs, select_fields: &'a str, ) -> QueryBuilder<'a, MySql> { @@ -201,18 +198,15 @@ WHERE m.user_deleted_at IS NULL } } - if cursor_is_reversed { - query_builder.push(" ORDER BY m.id ASC "); + if sort_ascending { + query_builder.push(" ORDER BY m.created_at ASC "); } else { - query_builder.push(" ORDER BY m.id DESC "); + query_builder.push(" ORDER BY m.created_at DESC "); } if enforce_limits { - if cursor_is_reversed { - query_builder.push(format!(" LIMIT {limit_start}, {limit_end} ")); - } else { - query_builder.push(format!(" LIMIT {limit_end}, {limit_start} ")); - } + let offset = page_index * page_size; + query_builder.push(format!(" LIMIT {page_size} OFFSET {offset} ")); } query_builder diff --git a/crates/service/web/storyteller_web/src/api_doc.rs b/crates/service/web/storyteller_web/src/api_doc.rs index 1d96b220e0..e6808517b7 100644 --- a/crates/service/web/storyteller_web/src/api_doc.rs +++ b/crates/service/web/storyteller_web/src/api_doc.rs @@ -1,4 +1,3 @@ - use crate::http_server::endpoints::voice_designer::inference::enqueue_tts_request::*; use crate::http_server::endpoints::weights::list_available_weights::*; @@ -8,26 +7,36 @@ use crate::http_server::endpoints::weights::get_weight::*; use crate::http_server::endpoints::weights::update_weight::*; use crate::http_server::endpoints::weights::list_weights_by_user::*; use crate::http_server::common_responses::user_details_lite::{DefaultAvatarInfo, UserDetailsLight}; -use enums::by_table::model_weights::{ - weights_types::WeightsType, - weights_category::WeightsCategory, -}; +use crate::http_server::common_responses::media_file_social_meta_lite::MediaFileSocialMetaLight; +use crate::http_server::common_responses::pagination_cursors::PaginationCursors; +use crate::http_server::common_responses::pagination_page::PaginationPage; +use crate::http_server::endpoints::media_files::list_featured_media_files::*; +use crate::http_server::endpoints::media_files::list_media_files::*; +use crate::http_server::endpoints::media_files::list_media_files_for_user::*; +use enums::by_table::model_weights::{weights_types::WeightsType, weights_category::WeightsCategory}; +use enums::by_table::media_files::media_file_type::MediaFileType; +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 tokens::tokens::generic_inference_jobs::*; use tokens::tokens::model_weights::*; use tokens::tokens::users::*; +use tokens::tokens::media_files::*; use enums::common::visibility::Visibility; use chrono::{DateTime, Utc}; use utoipa::OpenApi; - #[derive(OpenApi)] #[openapi(paths(crate::http_server::endpoints::voice_designer::inference::enqueue_tts_request::enqueue_tts_request, crate::http_server::endpoints::weights::list_available_weights::list_available_weights_handler, crate::http_server::endpoints::weights::delete_weight::delete_weight_handler, crate::http_server::endpoints::weights::update_weight::update_weight_handler, crate::http_server::endpoints::weights::get_weight::get_weight_handler, - crate::http_server::endpoints::weights::list_weights_by_user::list_weights_by_user_handler + crate::http_server::endpoints::weights::list_weights_by_user::list_weights_by_user_handler, + crate::http_server::endpoints::media_files::list_featured_media_files::list_featured_media_files_handler, + crate::http_server::endpoints::media_files::list_media_files::list_media_files_handler, + crate::http_server::endpoints::media_files::list_media_files_for_user::list_media_files_for_user_handler ),components(schemas( // Tokens UserToken,Visibility, @@ -40,10 +49,17 @@ use utoipa::OpenApi; GetWeightPathInfo,GetWeightResponse,GetWeightError, UpdateWeightRequest,UpdateWeightRequest,UpdateWeightPathInfo,UpdateWeightRequest,UpdateWeightError, DeleteWeightPathInfo,DeleteWeightRequest,DeleteWeightError,DeleteWeightRequest,DeleteWeightError, - ListWeightError,ModelWeightForList,ListWeightError, + ListWeightError,ModelWeightForList,ListWeightError, ListAvailableWeightsQuery,ListAvailableWeightsSuccessResponse,ModelWeightForList, ListWeightsByUserError,ListWeightsByUserSuccessResponse,ListWeightsByUserPathInfo,Weight, - UserDetailsLight,DefaultAvatarInfo + UserDetailsLight,DefaultAvatarInfo, + PaginationCursors, + PaginationPage, + MediaFileOriginModelType,MediaFileOriginProductCategory,MediaFileOriginCategory, + ListFeaturedMediaFilesSuccessResponse,MediaFile,MediaFileToken,MediaFileType, ListFeaturedMediaFilesError, + ListMediaFilesSuccessResponse, ListMediaFilesError, MediaFileListItem, ListMediaFilesQueryParams, + MediaFileSocialMetaLight, + ListMediaFilesForUserSuccessResponse,ListMediaFilesForUserQueryParams,ListMediaFilesForUserError,ListMediaFilesForUserPathInfo, + MediaFileForUserListItem, MediaFileForUserListItem )))] pub struct ApiDoc; - diff --git a/crates/service/web/storyteller_web/src/http_server/common_responses/media_file_social_meta_lite.rs b/crates/service/web/storyteller_web/src/http_server/common_responses/media_file_social_meta_lite.rs new file mode 100644 index 0000000000..6f32750d0c --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/common_responses/media_file_social_meta_lite.rs @@ -0,0 +1,22 @@ +use utoipa::ToSchema; +use tokens::tokens::media_files::MediaFileToken; + + +/// Fields useful for enriching media file listings +#[derive(Clone, Serialize, ToSchema)] +pub struct MediaFileSocialMetaLight { + pub favorites_count: u64, + pub comments_count: u64, +} + +impl MediaFileSocialMetaLight { + pub fn from_db_fields( + favorites_count: u64, + comments_count: u64, + ) -> Self { + Self { + favorites_count, + comments_count, + } + } +} diff --git a/crates/service/web/storyteller_web/src/http_server/common_responses/mod.rs b/crates/service/web/storyteller_web/src/http_server/common_responses/mod.rs index d84a319a04..f22f30d692 100644 --- a/crates/service/web/storyteller_web/src/http_server/common_responses/mod.rs +++ b/crates/service/web/storyteller_web/src/http_server/common_responses/mod.rs @@ -1,3 +1,4 @@ pub mod pagination_cursors; pub mod pagination_page; -pub mod user_details_lite; \ No newline at end of file +pub mod user_details_lite; +pub mod media_file_social_meta_lite; \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/http_server/common_responses/pagination_cursors.rs b/crates/service/web/storyteller_web/src/http_server/common_responses/pagination_cursors.rs index c801faea59..be6377ba7e 100644 --- a/crates/service/web/storyteller_web/src/http_server/common_responses/pagination_cursors.rs +++ b/crates/service/web/storyteller_web/src/http_server/common_responses/pagination_cursors.rs @@ -1,10 +1,12 @@ +use utoipa::ToSchema; + /// Pagination by cursors /// These types of pagination are for "infinite scrolling", which do not reveal a number of pages. /// This is good so that investors and competitors cannot reveal how many database records we have. /// This is typically used for "discovery" type pages, not user profiles. /// /// See `PaginationPage` for the other type of pagination. -#[derive(Serialize)] +#[derive(Serialize, ToSchema)] pub struct PaginationCursors { /// The "next" cursor. diff --git a/crates/service/web/storyteller_web/src/http_server/common_responses/pagination_page.rs b/crates/service/web/storyteller_web/src/http_server/common_responses/pagination_page.rs index 50534944ef..83f6d93961 100644 --- a/crates/service/web/storyteller_web/src/http_server/common_responses/pagination_page.rs +++ b/crates/service/web/storyteller_web/src/http_server/common_responses/pagination_page.rs @@ -1,3 +1,5 @@ +use utoipa::ToSchema; + /// Pagination by page id /// This type of pagination is by "page id" and "page count". /// This should never be used to walk the entire public database as competitors and investors @@ -5,7 +7,7 @@ /// This is best used for user profiles and scoped down results. /// /// See `PaginationCursors` for the other type of pagination. -#[derive(Serialize)] +#[derive(Serialize, ToSchema)] pub struct PaginationPage { /// The current page number pub current: usize, diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list_media_files.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list_media_files.rs index e1c0869e90..e48d6d00b0 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list_media_files.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list_media_files.rs @@ -7,6 +7,7 @@ use actix_web::web::{Path, Query}; use chrono::{DateTime, Utc}; use log::{info, warn}; use buckets::public::media_files::bucket_file_path::MediaFileBucketPath; +use utoipa::{IntoParams, path, ToSchema}; use enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory; use enums::by_table::media_files::media_file_origin_model_type::MediaFileOriginModelType; @@ -15,14 +16,15 @@ use enums::by_table::media_files::media_file_type::MediaFileType; use enums::common::visibility::Visibility; use mysql_queries::queries::media_files::list_media_files::{list_media_files, ListMediaFilesArgs, ViewAs}; use tokens::tokens::media_files::MediaFileToken; +use crate::http_server::common_responses::media_file_social_meta_lite::MediaFileSocialMetaLight; use crate::http_server::common_responses::pagination_cursors::PaginationCursors; use crate::http_server::common_responses::user_details_lite::UserDetailsLight; use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; use crate::server_state::ServerState; -#[derive(Deserialize)] -pub struct QueryParams { +#[derive(Deserialize, ToSchema, IntoParams)] +pub struct ListMediaFilesQueryParams { pub sort_ascending: Option, pub per_page: Option, pub cursor: Option, @@ -30,14 +32,14 @@ pub struct QueryParams { pub filter_media_type: Option, } -#[derive(Serialize)] -pub struct SuccessResponse { +#[derive(Serialize, ToSchema)] +pub struct ListMediaFilesSuccessResponse { pub success: bool, pub results: Vec, pub pagination: PaginationCursors, } -#[derive(Serialize)] +#[derive(Serialize, ToSchema)] pub struct MediaFileListItem { pub token: MediaFileToken, @@ -52,6 +54,7 @@ pub struct MediaFileListItem { pub public_bucket_path: String, pub maybe_creator: Option, + pub maybe_social_meta: Option, pub creator_set_visibility: Visibility, @@ -59,38 +62,49 @@ pub struct MediaFileListItem { pub updated_at: DateTime, } -#[derive(Debug)] -pub enum ErrorResponse { +#[derive(Debug, ToSchema)] +pub enum ListMediaFilesError { ServerError, } -impl ResponseError for ErrorResponse { +impl ResponseError for ListMediaFilesError { fn status_code(&self) -> StatusCode { match *self { - ErrorResponse::ServerError => StatusCode::INTERNAL_SERVER_ERROR, + ListMediaFilesError::ServerError => StatusCode::INTERNAL_SERVER_ERROR, } } fn error_response(&self) -> HttpResponse { let error_reason = match self { - ErrorResponse::ServerError => "server error".to_string(), + ListMediaFilesError::ServerError => "server error".to_string(), }; to_simple_json_error(&error_reason, self.status_code()) } } -impl std::fmt::Display for ErrorResponse { +impl std::fmt::Display for ListMediaFilesError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}", self) } } +#[utoipa::path( + get, + path = "/v1/media_files/list", + params( + ListMediaFilesQueryParams, + ), + responses( + (status = 200, description = "List Featured Media Files", body = ListMediaFilesSuccessResponse), + (status = 500, description = "Server error", body = ListMediaFilesError), + ), +)] pub async fn list_media_files_handler( - http_request: HttpRequest, - query: Query, - server_state: web::Data> -) -> Result + http_request: HttpRequest, + query: Query, + server_state: web::Data> +) -> Result { let maybe_user_session = server_state .session_checker @@ -98,7 +112,7 @@ pub async fn list_media_files_handler( .await .map_err(|e| { warn!("Session checker error: {:?}", e); - ErrorResponse::ServerError + ListMediaFilesError::ServerError })?; let mut is_mod = false; @@ -120,7 +134,7 @@ pub async fn list_media_files_handler( let cursor = server_state.sort_key_crypto.decrypt_id(cursor) .map_err(|e| { warn!("crypto error: {:?}", e); - ErrorResponse::ServerError + ListMediaFilesError::ServerError })?; Some(cursor as usize) } else { @@ -147,7 +161,7 @@ pub async fn list_media_files_handler( Ok(results) => results, Err(e) => { warn!("Query error: {:?}", e); - return Err(ErrorResponse::ServerError); + return Err(ListMediaFilesError::ServerError); } }; @@ -155,7 +169,7 @@ pub async fn list_media_files_handler( let cursor = server_state.sort_key_crypto.encrypt_id(id as u64) .map_err(|e| { warn!("crypto error: {:?}", e); - ErrorResponse::ServerError + ListMediaFilesError::ServerError })?; Some(cursor) } else { @@ -166,7 +180,7 @@ pub async fn list_media_files_handler( let cursor = server_state.sort_key_crypto.encrypt_id(id as u64) .map_err(|e| { warn!("crypto error: {:?}", e); - ErrorResponse::ServerError + ListMediaFilesError::ServerError })?; Some(cursor) } else { @@ -175,7 +189,7 @@ pub async fn list_media_files_handler( let results = results_page.records.into_iter() .map(|record| MediaFileListItem { - token: record.token, + token: record.token.clone(), origin_category: record.origin_category, origin_product_category: record.origin_product_category, maybe_origin_model_type: record.maybe_origin_model_type, @@ -193,13 +207,17 @@ pub async fn list_media_files_handler( record.maybe_creator_display_name, record.maybe_creator_gravatar_hash, ), + maybe_social_meta: Option::from(MediaFileSocialMetaLight::from_db_fields( + record.favorite_count, + record.comment_count, + )), creator_set_visibility: record.creator_set_visibility, created_at: record.created_at, updated_at: record.updated_at, }) .collect::>(); - let response = SuccessResponse { + let response = ListMediaFilesSuccessResponse { success: true, results, pagination: PaginationCursors { @@ -210,7 +228,7 @@ pub async fn list_media_files_handler( }; let body = serde_json::to_string(&response) - .map_err(|e| ErrorResponse::ServerError)?; + .map_err(|e| ListMediaFilesError::ServerError)?; Ok(HttpResponse::Ok() .content_type("application/json") diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list_media_files_for_user.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list_media_files_for_user.rs index f634072842..f066cea8c8 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list_media_files_for_user.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_files/list_media_files_for_user.rs @@ -7,6 +7,7 @@ use actix_web::web::{Path, Query}; use chrono::{DateTime, Utc}; use log::{info, warn}; use buckets::public::media_files::bucket_file_path::MediaFileBucketPath; +use utoipa::{IntoParams, path, ToSchema}; use enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory; use enums::by_table::media_files::media_file_origin_model_type::MediaFileOriginModelType; @@ -16,35 +17,32 @@ use enums::common::visibility::Visibility; use mysql_queries::queries::media_files::list_media_files_for_user::{list_media_files_for_user, ListMediaFileForUserArgs, ViewAs}; use tokens::tokens::media_files::MediaFileToken; -use crate::http_server::common_responses::pagination_cursors::PaginationCursors; use crate::http_server::common_responses::pagination_page::PaginationPage; use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; use crate::server_state::ServerState; -#[derive(Deserialize)] -pub struct PathInfo { +#[derive(Deserialize, ToSchema)] +pub struct ListMediaFilesForUserPathInfo { username: String, } -#[derive(Deserialize)] -pub struct QueryParams { +#[derive(Deserialize, ToSchema, IntoParams)] +pub struct ListMediaFilesForUserQueryParams { pub sort_ascending: Option, pub page_size: Option, pub page_index: Option, - pub cursor: Option, - pub cursor_is_reversed: Option, pub filter_media_type: Option, } -#[derive(Serialize)] -pub struct SuccessResponse { +#[derive(Serialize, ToSchema)] +pub struct ListMediaFilesForUserSuccessResponse { pub success: bool, - pub results: Vec, + pub results: Vec, pub pagination: PaginationPage, } -#[derive(Serialize)] -pub struct MediaFileListItem { +#[derive(Serialize, ToSchema)] +pub struct MediaFileForUserListItem { pub token: MediaFileToken, pub origin_category: MediaFileOriginCategory, @@ -63,39 +61,50 @@ pub struct MediaFileListItem { pub updated_at: DateTime, } -#[derive(Debug)] -pub enum ErrorResponse { +#[derive(Debug, ToSchema)] +pub enum ListMediaFilesForUserError { ServerError, } -impl ResponseError for ErrorResponse { +impl ResponseError for ListMediaFilesForUserError { fn status_code(&self) -> StatusCode { match *self { - ErrorResponse::ServerError => StatusCode::INTERNAL_SERVER_ERROR, + ListMediaFilesForUserError::ServerError => StatusCode::INTERNAL_SERVER_ERROR, } } fn error_response(&self) -> HttpResponse { let error_reason = match self { - ErrorResponse::ServerError => "server error".to_string(), + ListMediaFilesForUserError::ServerError => "server error".to_string(), }; to_simple_json_error(&error_reason, self.status_code()) } } -impl std::fmt::Display for ErrorResponse { +impl std::fmt::Display for ListMediaFilesForUserError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}", self) } } +#[utoipa::path( + get, + path = "/v1/media_files/list/user/{username}", + params( + ListMediaFilesForUserQueryParams, + ), + responses( + (status = 200, description = "List Featured Media Files", body = ListMediaFilesForUserSuccessResponse), + (status = 500, description = "Server error", body = ListMediaFilesForUserError), + ), +)] pub async fn list_media_files_for_user_handler( http_request: HttpRequest, - path: Path, - query: Query, + path: Path, + query: Query, server_state: web::Data> -) -> Result +) -> Result { let maybe_user_session = server_state .session_checker @@ -103,7 +112,7 @@ pub async fn list_media_files_for_user_handler( .await .map_err(|e| { warn!("Session checker error: {:?}", e); - ErrorResponse::ServerError + ListMediaFilesForUserError::ServerError })?; let mut is_author = false; @@ -121,7 +130,6 @@ pub async fn list_media_files_for_user_handler( let limit = query.page_size.unwrap_or(25); let sort_ascending = query.sort_ascending.unwrap_or(false); - let cursor_is_reversed = query.cursor_is_reversed.unwrap_or(false); let page_size = query.page_size.unwrap_or_else(|| 25); let page_index = query.page_index.unwrap_or_else(|| 0); @@ -140,7 +148,7 @@ pub async fn list_media_files_for_user_handler( maybe_filter_media_type: query.filter_media_type, page_size, page_index, - cursor_is_reversed, + sort_ascending, view_as, mysql_pool: &server_state.mysql_pool, }).await; @@ -150,13 +158,13 @@ pub async fn list_media_files_for_user_handler( Ok(results) => results, Err(e) => { warn!("Query error: {:?}", e); - return Err(ErrorResponse::ServerError); + return Err(ListMediaFilesForUserError::ServerError); } }; let results = results_page.records.into_iter() - .map(|record| MediaFileListItem { + .map(|record| MediaFileForUserListItem { token: record.token, origin_category: record.origin_category, origin_product_category: record.origin_product_category, @@ -175,7 +183,7 @@ pub async fn list_media_files_for_user_handler( }) .collect::>(); - let response = SuccessResponse { + let response = ListMediaFilesForUserSuccessResponse { success: true, results, pagination: PaginationPage{ @@ -185,7 +193,7 @@ pub async fn list_media_files_for_user_handler( }; let body = serde_json::to_string(&response) - .map_err(|e| ErrorResponse::ServerError)?; + .map_err(|e| ListMediaFilesForUserError::ServerError)?; Ok(HttpResponse::Ok() .content_type("application/json")