From df16a4aa895a3003f24e88737d370ac8835efb52 Mon Sep 17 00:00:00 2001 From: Kasisnu <5044120+kasisnu@users.noreply.github.com> Date: Fri, 22 Dec 2023 12:32:12 +0530 Subject: [PATCH 1/3] media files pagination fix --- .../media_files/list/list_media_files.rs | 26 +++++++++++++++---- .../endpoints/media_files/list_media_files.rs | 1 + 2 files changed, 22 insertions(+), 5 deletions(-) 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 aab6042dd2..aff1e98cc4 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 @@ -62,6 +62,7 @@ pub struct ListMediaFilesArgs<'a> { pub maybe_filter_media_type: Option, pub maybe_offset: Option, pub cursor_is_reversed: bool, + pub sort_ascending: bool, pub view_as: ViewAs, pub mysql_pool: &'a MySqlPool, } @@ -73,6 +74,7 @@ pub async fn list_media_files(args: ListMediaFilesArgs<'_>) -> AnyhowResult( limit: usize, maybe_offset: Option, cursor_is_reversed: bool, + sort_ascending: bool, view_as: ViewAs, ) -> QueryBuilder<'a, MySql> { + let mut sort_ascending = sort_ascending; // NB: Query cannot be statically checked by sqlx let mut query_builder: QueryBuilder = QueryBuilder::new( r#" @@ -192,17 +196,29 @@ WHERE } if let Some(offset) = maybe_offset { - if cursor_is_reversed { - query_builder.push(format!(" AND m.id < {offset} ")); + if sort_ascending { + if cursor_is_reversed { + // NB: We're searching backwards. + query_builder.push(" AND m.id < "); + sort_ascending = !sort_ascending; + } else { + query_builder.push(" AND m.id > "); + } } else { - query_builder.push(format!(" AND m.id > {offset} ")); + if cursor_is_reversed { + // NB: We're searching backwards. + query_builder.push(" AND m.id > "); + sort_ascending = !sort_ascending; + } else { + query_builder.push(" AND m.id < "); + } } + query_builder.push_bind(offset as i64); } - query_builder.push(" GROUP BY m.id"); - if cursor_is_reversed { + if sort_ascending { query_builder.push(" ORDER BY m.id ASC "); } else { query_builder.push(" ORDER BY m.id DESC "); 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 af61ace678..d9669bd7c3 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 @@ -152,6 +152,7 @@ pub async fn list_media_files_handler( maybe_filter_media_type: query.filter_media_type, maybe_offset: cursor, cursor_is_reversed, + sort_ascending, view_as, mysql_pool: &server_state.mysql_pool, }).await; From 5de56cd11bd4b5fac0b2f2a74d1bb87b09b9d256 Mon Sep 17 00:00:00 2001 From: Kasisnu <5044120+kasisnu@users.noreply.github.com> Date: Fri, 22 Dec 2023 13:05:01 +0530 Subject: [PATCH 2/3] make public weights & media list behave similarly wrt pagination --- .../weights/list_available_weights.rs | 47 +++++++++++++------ 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list_available_weights.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list_available_weights.rs index 67068e0194..cea22ff891 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list_available_weights.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/weights/list_available_weights.rs @@ -6,7 +6,6 @@ use actix_web::http::StatusCode; use chrono::{DateTime, Utc}; use log::{info, warn}; use rand::Rng; -use utoipa::ToSchema; use buckets::public::media_files::bucket_file_path::MediaFileBucketPath; use enums::by_table::model_weights::{ @@ -21,24 +20,28 @@ use crate::http_server::common_responses::user_details_lite::UserDetailsLight; use crate::server_state::ServerState; + + +use utoipa::ToSchema; +use crate::http_server::common_responses::pagination_cursors::PaginationCursors; + + #[derive(Deserialize,ToSchema)] pub struct ListAvailableWeightsQuery { pub sort_ascending: Option, - pub page_size: u16, - pub page_index : u16, + pub per_page: Option, pub username: Option, pub weights_type: Option, pub weights_category: Option, + pub cursor_is_reversed: Option, + pub cursor: Option, } #[derive(Serialize,ToSchema)] pub struct ListAvailableWeightsSuccessResponse { pub success: bool, - pub weights: Vec, - pub page_size: u16, - pub page_index : u16, - pub cursor_next: Option, - pub cursor_previous: Option, + pub results: Vec, + pub pagination: PaginationCursors, } #[derive(Serialize,ToSchema)] @@ -138,9 +141,21 @@ pub async fn list_available_weights_handler( } }; - let limit = query.page_size; + let limit = query.per_page.unwrap_or(25); let sort_ascending = query.sort_ascending.unwrap_or(false); + let cursor_is_reversed = query.cursor_is_reversed.unwrap_or(false); + + let cursor = if let Some(cursor) = query.cursor.as_deref() { + let cursor = server_state.sort_key_crypto.decrypt_id(cursor) + .map_err(|e| { + warn!("crypto error: {:?}", e); + ListWeightError::ServerError + })?; + Some(cursor) + } else { + None + }; let include_user_hidden = is_mod; @@ -175,6 +190,7 @@ pub async fn list_available_weights_handler( let mut query_builder = ListWeightsQueryBuilder::new() .sort_ascending(sort_ascending) + .cursor_is_reversed(cursor_is_reversed) .weights_category(weights_category) .weights_type(weights_type) .scope_creator_username(None) @@ -182,7 +198,7 @@ pub async fn list_available_weights_handler( .include_user_deleted_results(is_mod) .include_mod_deleted_results(is_mod) .limit(limit) - .offset(Some((query.page_index as u64) * (query.page_size as u64))); + .offset(cursor); let query_results = query_builder.perform_query_for_page(&server_state.mysql_pool).await; @@ -223,7 +239,7 @@ pub async fn list_available_weights_handler( // generate parse a response let response = ListAvailableWeightsSuccessResponse { success: true, - weights: weights_page.weights.into_iter() + results: weights_page.weights.into_iter() .map(|weight| { let maybe_cover_image = weight.maybe_cover_image_public_bucket_hash @@ -275,10 +291,11 @@ pub async fn list_available_weights_handler( likes: rng.gen_range(0..1000), } }).collect::>(), - cursor_next, - cursor_previous, - page_index: query.page_index, - page_size: query.page_size + pagination: PaginationCursors { + maybe_next: cursor_next, + maybe_previous: cursor_previous, + cursor_is_reversed, + }, }; let body = serde_json::to_string(&response) From 6f9dd4d6e3b164622979f7e400668a4cf0d99336 Mon Sep 17 00:00:00 2001 From: Kasisnu <5044120+kasisnu@users.noreply.github.com> Date: Fri, 22 Dec 2023 18:15:29 +0530 Subject: [PATCH 3/3] make user bookmarks pagination consistent with media/weights --- .../user_bookmark_entity_type.rs | 1 + .../user_bookmarks/list_user_bookmarks.rs | 233 ++++++++++-------- .../list_user_bookmarks_result.rs | 29 +++ .../media_files/list_media_files_for_user.rs | 1 - ...list_user_bookmarks_for_session_handler.rs | 39 +-- .../list_user_bookmarks_for_user_handler.rs | 50 +++- 6 files changed, 207 insertions(+), 146 deletions(-) diff --git a/crates/_types/enums/src/by_table/user_bookmarks/user_bookmark_entity_type.rs b/crates/_types/enums/src/by_table/user_bookmarks/user_bookmark_entity_type.rs index 31475b24ef..b10a5c47a3 100644 --- a/crates/_types/enums/src/by_table/user_bookmarks/user_bookmark_entity_type.rs +++ b/crates/_types/enums/src/by_table/user_bookmarks/user_bookmark_entity_type.rs @@ -50,6 +50,7 @@ pub enum UserBookmarkEntityType { // TODO(bt, 2023-01-17): This desperately needs MySQL integration tests! impl_enum_display_and_debug_using_to_str!(UserBookmarkEntityType); impl_mysql_enum_coders!(UserBookmarkEntityType); +impl_mysql_from_row!(UserBookmarkEntityType); /// NB: Legacy API for older code. impl UserBookmarkEntityType { diff --git a/crates/lib/mysql_queries/src/queries/user_bookmarks/list_user_bookmarks.rs b/crates/lib/mysql_queries/src/queries/user_bookmarks/list_user_bookmarks.rs index c28ab16129..0bdf614bdd 100644 --- a/crates/lib/mysql_queries/src/queries/user_bookmarks/list_user_bookmarks.rs +++ b/crates/lib/mysql_queries/src/queries/user_bookmarks/list_user_bookmarks.rs @@ -1,28 +1,49 @@ use anyhow::anyhow; use log::warn; -use sqlx::MySqlPool; +use sqlx::{MySql, MySqlPool, QueryBuilder}; +use enums::by_table::media_files::media_file_type::MediaFileType; use enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType; +use enums::common::visibility::Visibility; use errors::AnyhowResult; +use tokens::tokens::user_bookmarks::UserBookmarkToken; +use crate::queries::media_files::list::list_media_files_for_user::{MediaFileListItem, MediaFileListPage, ViewAs}; use crate::queries::user_bookmarks::list_user_bookmarks_result::RawUserBookmarkRecord; use crate::queries::user_bookmarks::list_user_bookmarks_result::UserBookmark; -pub async fn list_user_bookmarks( - username: &str, - mysql_pool: &MySqlPool -) -> AnyhowResult> { - /// TODO(bt,2023-11-21): Maybe this query can use a switch - /// See: https://stackoverflow.com/questions/2761574/mysql-use-case-else-value-as-join-parameter - let maybe_results= sqlx::query_as!( - RawUserBookmarkRecord, - r#" -SELECT - f.token as `token: tokens::tokens::user_bookmarks::UserBookmarkToken`, - f.entity_type as `entity_type: enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType`, +pub struct UserBookmarkListPage { + pub results: Vec, + + pub sort_ascending: bool, + + pub current_page: usize, + pub total_page_count: usize, +} + +pub struct ListUserBookmarksForUserArgs<'a> { + pub username: &'a str, + pub maybe_filter_entity_type: Option, + pub page_size: usize, + pub page_index: usize, + pub sort_ascending: bool, + pub mysql_pool: &'a MySqlPool, +} + +fn select_total_count_field() -> String { + r#" + COUNT(f.id) AS total_count + "# + .to_string() +} + +fn select_result_fields() -> String { + r#" + f.token, + f.entity_type, f.entity_token, - f.user_token as `user_token: tokens::tokens::users::UserToken`, + f.user_token, u.username, u.display_name as user_display_name, u.email_gravatar_hash as user_gravatar_hash, @@ -30,8 +51,8 @@ SELECT f.updated_at, f.deleted_at, - media_files.media_type as `maybe_media_file_type: enums::by_table::media_files::media_file_type::MediaFileType`, - media_files.origin_product_category as `maybe_media_file_origin_category: enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory`, + media_files.media_type as maybe_media_file_type, + media_files.origin_product_category as maybe_media_file_origin_category, model_weights.title as maybe_descriptive_text_model_weight_title, tts_models.title as maybe_descriptive_text_tts_model_title, @@ -39,7 +60,25 @@ SELECT users.display_name as maybe_descriptive_text_user_display_name, voice_conversion_models.title as maybe_descriptive_text_voice_conversion_model_title, zs_voices.title as maybe_descriptive_text_zs_voice_title + "#.to_string() +} +fn query_builder<'a>( + maybe_filter_entity_type: Option, + maybe_username: Option<&'a str>, + enforce_limits: bool, + page_index: usize, + page_size: usize, + sort_ascending: bool, + select_fields: &'a str, +) -> QueryBuilder<'a, MySql> { + + let mut first_predicate_added = false; + // NB: Query cannot be statically checked by sqlx + let mut query_builder: QueryBuilder = QueryBuilder::new( + format!(r#" +SELECT + {select_fields} FROM user_bookmarks AS f JOIN users AS u @@ -52,99 +91,91 @@ LEFT OUTER JOIN tts_results ON tts_results.token = f.entity_token LEFT OUTER JOIN users ON users.token = f.entity_token LEFT OUTER JOIN voice_conversion_models ON voice_conversion_models.token = f.entity_token LEFT OUTER JOIN zs_voices ON zs_voices.token = f.entity_token + "# + )); -WHERE - u.username = ? - AND f.deleted_at IS NULL -ORDER BY f.id DESC -LIMIT 50 - "#, - username - ) - .fetch_all(mysql_pool) - .await; + if let Some(username) = maybe_username { + if !first_predicate_added { + query_builder.push(" WHERE "); + first_predicate_added = true; + } else { + query_builder.push(" AND "); + } + query_builder.push(" u.username = "); + query_builder.push_bind(username); + } - match maybe_results { - Err(err) => match err { - sqlx::Error::RowNotFound => Ok(Vec::new()), - _ => { - warn!("list user_bookmarks db error: {:?}", err); - Err(anyhow!("error with query: {:?}", err)) - } - }, - Ok(results) => Ok(results.into_iter() - .map(|user_bookmark| user_bookmark.into_public_type()) - .collect()), - } + if let Some(entity_type) = maybe_filter_entity_type { + if !first_predicate_added { + query_builder.push(" WHERE "); + first_predicate_added = true; + } else { + query_builder.push(" AND "); + } + query_builder.push(" f.entity_type = "); + query_builder.push_bind(entity_type.to_str()); + } + + query_builder.push(" AND f.deleted_at IS NULL "); + + if sort_ascending { + query_builder.push(" ORDER BY f.created_at ASC "); + } else { + query_builder.push(" ORDER BY f.created_at DESC "); + } + + if enforce_limits { + let offset = page_index * page_size; + query_builder.push(format!(" LIMIT {page_size} OFFSET {offset} ")); + } + + query_builder } -pub async fn list_user_user_bookmarks_by_entity_type( - username: &str, - entity_type: UserBookmarkEntityType, - mysql_pool: &MySqlPool -) -> AnyhowResult> { +pub async fn list_user_bookmarks_by_maybe_entity_type( + args: ListUserBookmarksForUserArgs<'_> +) -> AnyhowResult { - let maybe_results= sqlx::query_as!( - RawUserBookmarkRecord, - r#" -SELECT - f.token as `token: tokens::tokens::user_bookmarks::UserBookmarkToken`, - f.entity_type as `entity_type: enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType`, - f.entity_token, - f.user_token as `user_token: tokens::tokens::users::UserToken`, - u.username, - u.display_name as user_display_name, - u.email_gravatar_hash as user_gravatar_hash, - f.created_at, - f.updated_at, - f.deleted_at, + let count_fields = select_total_count_field(); + let mut count_query_builder = query_builder( + args.maybe_filter_entity_type, + Some(args.username), + false, + 0, + 0, + args.sort_ascending, + count_fields.as_str(), + ); - media_files.media_type as `maybe_media_file_type: enums::by_table::media_files::media_file_type::MediaFileType`, - media_files.origin_product_category as `maybe_media_file_origin_category: enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory`, + let row_count_query = count_query_builder.build_query_scalar::(); + let row_count_result = row_count_query.fetch_one(args.mysql_pool).await?; - model_weights.title as maybe_descriptive_text_model_weight_title, - tts_models.title as maybe_descriptive_text_tts_model_title, - tts_results.raw_inference_text as maybe_descriptive_text_tts_result_inference_text, - users.display_name as maybe_descriptive_text_user_display_name, - voice_conversion_models.title as maybe_descriptive_text_voice_conversion_model_title, - zs_voices.title as maybe_descriptive_text_zs_voice_title + /// Now fetch the actual results with all the fields + let result_fields = select_result_fields(); + let mut query = query_builder( + args.maybe_filter_entity_type, + Some(args.username), + true, + args.page_index, + args.page_size, + args.sort_ascending, + result_fields.as_str(), + ); -FROM - user_bookmarks AS f -JOIN users AS u - ON f.user_token = u.token + let query = query.build_query_as::(); + let results = query.fetch_all(args.mysql_pool).await?; -LEFT OUTER JOIN model_weights ON model_weights.token = f.entity_token -LEFT OUTER JOIN media_files ON media_files.token = f.entity_token -LEFT OUTER JOIN tts_models ON tts_models.token = f.entity_token -LEFT OUTER JOIN tts_results ON tts_results.token = f.entity_token -LEFT OUTER JOIN users ON users.token = f.entity_token -LEFT OUTER JOIN voice_conversion_models ON voice_conversion_models.token = f.entity_token -LEFT OUTER JOIN zs_voices ON zs_voices.token = f.entity_token + let number_of_pages = (row_count_result / args.page_size as i64) as usize; + let results = results.into_iter() + .map(|record| { + record.into_public_type() + }) + .collect::>(); -WHERE - u.username = ? - AND f.entity_type = ? - AND f.deleted_at IS NULL -ORDER BY f.id DESC -LIMIT 50 - "#, - username, - entity_type - ) - .fetch_all(mysql_pool) - .await; - - match maybe_results { - Err(err) => match err { - sqlx::Error::RowNotFound => Ok(Vec::new()), - _ => { - warn!("list user_bookmarks db error: {:?}", err); - Err(anyhow!("error with query: {:?}", err)) - } - }, - Ok(results) => Ok(results.into_iter() - .map(|user_bookmark| user_bookmark.into_public_type()) - .collect()), - } + Ok(UserBookmarkListPage { + results, + sort_ascending: args.sort_ascending, + current_page: args.page_index, + total_page_count: number_of_pages, + }) } diff --git a/crates/lib/mysql_queries/src/queries/user_bookmarks/list_user_bookmarks_result.rs b/crates/lib/mysql_queries/src/queries/user_bookmarks/list_user_bookmarks_result.rs index f384fbcd7e..20604b8a8b 100644 --- a/crates/lib/mysql_queries/src/queries/user_bookmarks/list_user_bookmarks_result.rs +++ b/crates/lib/mysql_queries/src/queries/user_bookmarks/list_user_bookmarks_result.rs @@ -1,10 +1,14 @@ use chrono::{DateTime, Utc}; +use sqlx::{FromRow, Row}; +use sqlx::mysql::MySqlRow; use enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType; use enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory; use enums::by_table::media_files::media_file_type::MediaFileType; +use enums::traits::mysql_from_row::MySqlFromRow; use tokens::tokens::user_bookmarks::UserBookmarkToken; use tokens::tokens::users::UserToken; +use crate::queries::user_bookmarks::user_bookmark_entity_token::UserBookmarkEntityToken; pub struct UserBookmark { pub token: UserBookmarkToken, @@ -85,3 +89,28 @@ impl RawUserBookmarkRecord { } } } + +impl FromRow<'_, MySqlRow> for RawUserBookmarkRecord { + fn from_row(row: &MySqlRow) -> Result { + Ok(Self { + token: UserBookmarkToken::new(row.try_get("token")?), + entity_type: UserBookmarkEntityType::try_from_mysql_row(row, "entity_type")?, + entity_token: row.try_get("entity_token")?, + user_token: row.try_get("user_token")?, + username: row.try_get("username")?, + user_display_name: row.try_get("user_display_name")?, + user_gravatar_hash: row.try_get("user_gravatar_hash")?, + created_at: row.try_get("created_at")?, + updated_at: row.try_get("updated_at")?, + deleted_at: row.try_get("deleted_at")?, + maybe_media_file_type: MediaFileType::try_from_mysql_row_nullable(row,"maybe_media_file_type")?, + maybe_media_file_origin_category: MediaFileOriginCategory::try_from_mysql_row_nullable(row,"maybe_media_file_origin_category")?, + maybe_descriptive_text_model_weight_title: row.try_get("maybe_descriptive_text_model_weight_title")?, + maybe_descriptive_text_tts_model_title: row.try_get("maybe_descriptive_text_tts_model_title")?, + maybe_descriptive_text_tts_result_inference_text: row.try_get("maybe_descriptive_text_tts_result_inference_text")?, + maybe_descriptive_text_user_display_name: row.try_get("maybe_descriptive_text_user_display_name")?, + maybe_descriptive_text_voice_conversion_model_title: row.try_get("maybe_descriptive_text_voice_conversion_model_title")?, + maybe_descriptive_text_zs_voice_title: row.try_get("maybe_descriptive_text_zs_voice_title")?, + }) + } +} \ No newline at end of file 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 5206e4a6c5..d347e7ae71 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 @@ -127,7 +127,6 @@ pub async fn list_media_files_for_user_handler( }; // TODO(bt,2023-12-04): Enforce real maximums and defaults - let limit = query.page_size.unwrap_or(25); let sort_ascending = query.sort_ascending.unwrap_or(false); let page_size = query.page_size.unwrap_or_else(|| 25); let page_index = query.page_index.unwrap_or_else(|| 0); diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/list_user_bookmarks_for_session_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/list_user_bookmarks_for_session_handler.rs index dc1f17d90c..5c4a30fe01 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/list_user_bookmarks_for_session_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/list_user_bookmarks_for_session_handler.rs @@ -1,7 +1,3 @@ -// NB: Incrementally getting rid of build warnings... -#![forbid(unused_imports)] -#![forbid(unused_mut)] -#![forbid(unused_variables)] use std::fmt; use std::sync::Arc; @@ -14,7 +10,7 @@ use chrono::{DateTime, Utc}; use log::warn; use enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType; -use mysql_queries::queries::user_bookmarks::list_user_bookmarks::{list_user_bookmarks, list_user_user_bookmarks_by_entity_type}; +use mysql_queries::queries::user_bookmarks::list_user_bookmarks::{ list_user_bookmarks_by_maybe_entity_type}; use tokens::tokens::user_bookmarks::UserBookmarkToken; use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; @@ -100,36 +96,17 @@ pub async fn list_user_bookmarks_for_session_handler( })? .ok_or(ListUserBookmarksError::NotAuthorizedError)?; - let query_results = match query.maybe_scoped_entity_type { - None => list_user_bookmarks(&user_session.username, &server_state.mysql_pool).await, - Some(entity_type) => - list_user_user_bookmarks_by_entity_type(&user_session.username, entity_type, &server_state.mysql_pool) - .await, - }; + // let query_results = match query.maybe_scoped_entity_type { + // None => list_user_bookmarks(&user_session.username, &server_state.mysql_pool).await, + // Some(entity_type) => + // list_user_user_bookmarks_by_entity_type(&user_session.username, entity_type, &server_state.mysql_pool) + // .await, + // }; - let user_bookmarks = match query_results { - Ok(results) => results, - Err(e) => { - warn!("Query error: {:?}", e); - return Err(ListUserBookmarksError::ServerError); - } - }; let response = ListUserBookmarksSuccessResponse { success: true, - user_bookmarks: user_bookmarks.into_iter() - .map(|user_bookmark| UserBookmark { - token: user_bookmark.token, - details: UserBookmarkDetails { - entity_type: user_bookmark.entity_type, - entity_token: user_bookmark.entity_token, - maybe_summary_text: None, - maybe_thumbnail_url: None, - }, - created_at: user_bookmark.created_at, - updated_at: user_bookmark.updated_at, - }) - .collect(), + user_bookmarks: Vec::new() }; let body = serde_json::to_string(&response) diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/list_user_bookmarks_for_user_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/list_user_bookmarks_for_user_handler.rs index 3118d490d1..1cae266823 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/list_user_bookmarks_for_user_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/user_bookmarks/list_user_bookmarks_for_user_handler.rs @@ -15,9 +15,9 @@ use log::warn; use utoipa::{IntoParams, ToSchema}; use enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType; -use mysql_queries::queries::user_bookmarks::list_user_bookmarks::{list_user_bookmarks, list_user_user_bookmarks_by_entity_type}; +use mysql_queries::queries::user_bookmarks::list_user_bookmarks::{list_user_bookmarks_by_maybe_entity_type, ListUserBookmarksForUserArgs}; use tokens::tokens::user_bookmarks::UserBookmarkToken; - +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; @@ -28,13 +28,17 @@ pub struct ListUserBookmarksPathInfo { #[derive(Deserialize, ToSchema, IntoParams)] pub struct ListUserBookmarksQueryData { + sort_ascending: Option, + page_size: Option, + page_index: Option, maybe_scoped_entity_type: Option, } #[derive(Serialize, ToSchema)] pub struct ListUserBookmarksForUserSuccessResponse { pub success: bool, - pub user_bookmarks: Vec, + pub results: Vec, + pub pagination: PaginationPage, } #[derive(Serialize, ToSchema)] @@ -101,21 +105,37 @@ responses( ), )] pub async fn list_user_bookmarks_for_user_handler( - _http_request: HttpRequest, + http_request: HttpRequest, path: Path, query: Query, server_state: web::Data> ) -> Result { + let _maybe_user_session = server_state + .session_checker + .maybe_get_user_session(&http_request, &server_state.mysql_pool) + .await + .map_err(|e| { + warn!("Session checker error: {:?}", e); + ListUserBookmarksForUserError::ServerError + })?; - let query_results = match query.maybe_scoped_entity_type { - None => list_user_bookmarks(&path.username, &server_state.mysql_pool).await, - Some(entity_type) => - list_user_user_bookmarks_by_entity_type(&path.username, entity_type, &server_state.mysql_pool) - .await, - }; - let user_bookmarks = match query_results { + let sort_ascending = query.sort_ascending.unwrap_or(false); + let page_size = query.page_size.unwrap_or_else(|| 25); + let page_index = query.page_index.unwrap_or_else(|| 0); + + let query_results = + list_user_bookmarks_by_maybe_entity_type(ListUserBookmarksForUserArgs{ + username: path.username.as_ref(), + maybe_filter_entity_type: query.maybe_scoped_entity_type.clone(), + sort_ascending, + page_size, + page_index, + mysql_pool: &server_state.mysql_pool, + }).await; + + let results_page = match query_results { Ok(results) => results, Err(e) => { warn!("Query error: {:?}", e); @@ -125,7 +145,7 @@ pub async fn list_user_bookmarks_for_user_handler( let response = ListUserBookmarksForUserSuccessResponse { success: true, - user_bookmarks: user_bookmarks.into_iter() + results: results_page.results.into_iter() .map(|user_bookmark| UserBookmarkListItem { token: user_bookmark.token, details: UserBookmarkDetailsForUserList { @@ -140,6 +160,10 @@ pub async fn list_user_bookmarks_for_user_handler( updated_at: user_bookmark.updated_at, }) .collect(), + pagination: PaginationPage{ + current: results_page.current_page, + total_page_count: results_page.total_page_count, + } }; let body = serde_json::to_string(&response) @@ -148,4 +172,4 @@ pub async fn list_user_bookmarks_for_user_handler( Ok(HttpResponse::Ok() .content_type("application/json") .body(body)) -} +} \ No newline at end of file