switch beta keys to index pagination rather than encrypted cursors

This commit is contained in:
Brandon Thomas committed 2024-05-23 03:55:31 -04:00
1 parent a9332722e2
commit d5f42e39e5
2 files changed
+112 -115

No files matched your search

@@ -15,11 +15,8 @@ pub struct BetaKeyListPage {
pub sort_ascending: bool,
/// ID of the first record in the result set.
pub first_id: Option<i64>,
/// ID of the last record in the result set.
pub last_id: Option<i64>,
pub current_page: usize,
pub total_page_count: usize,
}
pub struct BetaKeyListItem {
@@ -53,38 +50,53 @@ pub struct BetaKeyListItem {
pub struct ListBetaKeysArgs<'a> {
pub filter_to_referrer_user_token: Option<&'a UserToken>,
pub filter_to_remaining_keys: bool,
pub limit: usize,
pub maybe_offset: Option<usize>,
pub cursor_is_reversed: bool,
pub page_size: usize,
pub page_index: usize,
pub sort_ascending: bool,
pub mysql_pool: &'a MySqlPool,
}
pub async fn list_beta_keys(args: ListBetaKeysArgs<'_>) -> AnyhowResult<BetaKeyListPage> {
/// Let's figure out how many results we could have returned total
let count_fields = select_total_count_field();
let mut query = query_builder(
let mut count_query_builder = query_builder(
count_fields,
args.filter_to_referrer_user_token,
args.filter_to_remaining_keys,
args.limit,
args.maybe_offset,
args.cursor_is_reversed,
false,
args.page_index,
args.page_size,
args.sort_ascending,
);
info!("query_builder query (wip): {:?}", query.sql());
info!("count query: {:?}", count_query_builder.sql());
let query = query.build_query_as::<MediaFileListItemInternal>();
let row_count_query = count_query_builder.build_query_scalar::<i64>();
let row_count_result = row_count_query.fetch_one(args.mysql_pool).await?;
info!("query dsl query (wip): {:?}", query.sql());
let number_of_pages = (row_count_result / args.page_size as i64) as usize;
/// Now fetch the actual results with all the fields
let result_fields = select_result_fields();
let mut query = query_builder(
result_fields,
args.filter_to_referrer_user_token,
args.filter_to_remaining_keys,
true,
args.page_index,
args.page_size,
args.sort_ascending,
);
info!("actual query: {:?}", query.sql());
let query = query.build_query_as::<BetaKeyListItemInternal>();
let results = query.fetch_all(args.mysql_pool).await?;
let first_id = results.first()
.map(|raw_result| raw_result.id);
let last_id = results.last()
.map(|raw_result| raw_result.id);
let results = results.into_iter()
.map(|record| {
BetaKeyListItem {
@@ -113,26 +125,14 @@ pub async fn list_beta_keys(args: ListBetaKeysArgs<'_>) -> AnyhowResult<BetaKeyL
Ok(BetaKeyListPage {
records: results,
sort_ascending: !args.cursor_is_reversed,
first_id,
last_id,
sort_ascending: args.sort_ascending,
current_page: args.page_index,
total_page_count: number_of_pages,
})
}
fn query_builder<'a>(
filter_to_referrer_user_token: Option<&'a UserToken>,
filter_to_remaining_keys: bool,
limit: usize,
maybe_offset: Option<usize>,
cursor_is_reversed: bool,
sort_ascending: bool,
) -> QueryBuilder<'a, MySql> {
let mut sort_ascending = sort_ascending;
// NB: Query cannot be statically checked by sqlx
let mut query_builder: QueryBuilder<MySql> = QueryBuilder::new(
r#"
SELECT
fn select_result_fields() -> &'static str {
r#"
b.id,
b.token,
@@ -159,6 +159,34 @@ SELECT
b.created_at,
b.maybe_redeemed_at
"#
}
fn select_total_count_field() -> &'static str {
r#"
COUNT(b.id) AS total_count
"#
}
fn query_builder<'a>(
select_fields: &'a str,
filter_to_referrer_user_token: Option<&'a UserToken>,
filter_to_remaining_keys: bool,
enforce_limits: bool,
page_index: usize,
page_size: usize,
sort_ascending: bool,
) -> QueryBuilder<'a, MySql> {
let mut sort_ascending = sort_ascending;
// NB: Query cannot be statically checked by sqlx
let mut query_builder: QueryBuilder<MySql> = QueryBuilder::new(
format!(r#"
SELECT
{select_fields}
FROM beta_keys AS b
@@ -171,7 +199,7 @@ LEFT OUTER JOIN users AS referrer
LEFT OUTER JOIN users AS redeemer
ON b.maybe_redeemer_user_token = redeemer.token
"#
);
));
let mut first_predicate_added = false;
@@ -198,33 +226,33 @@ LEFT OUTER JOIN users AS redeemer
query_builder.push(" b.maybe_redeemed_at IS NULL ");
}
if let Some(offset) = maybe_offset {
if !first_predicate_added {
query_builder.push(" WHERE ");
first_predicate_added = true;
} else {
query_builder.push(" AND ");
}
if sort_ascending {
if cursor_is_reversed {
// NB: We're searching backwards.
query_builder.push(" b.id < ");
sort_ascending = !sort_ascending;
} else {
query_builder.push(" b.id > ");
}
} else {
if cursor_is_reversed {
// NB: We're searching backwards.
query_builder.push(" b.id > ");
sort_ascending = !sort_ascending;
} else {
query_builder.push(" b.id < ");
}
}
query_builder.push_bind(offset as i64);
}
// if let Some(offset) = maybe_offset {
// if !first_predicate_added {
// query_builder.push(" WHERE ");
// first_predicate_added = true;
// } else {
// query_builder.push(" AND ");
// }
//
// if sort_ascending {
// if cursor_is_reversed {
// // NB: We're searching backwards.
// query_builder.push(" b.id < ");
// sort_ascending = !sort_ascending;
// } else {
// query_builder.push(" b.id > ");
// }
// } else {
// if cursor_is_reversed {
// // NB: We're searching backwards.
// query_builder.push(" b.id > ");
// sort_ascending = !sort_ascending;
// } else {
// query_builder.push(" b.id < ");
// }
// }
// query_builder.push_bind(offset as i64);
// }
if sort_ascending {
query_builder.push(" ORDER BY b.id ASC ");
@@ -232,12 +260,17 @@ LEFT OUTER JOIN users AS redeemer
query_builder.push(" ORDER BY b.id DESC ");
}
query_builder.push(format!(" LIMIT {limit} "));
// query_builder.push(format!(" LIMIT {limit} "));
if enforce_limits {
let offset = page_index * page_size;
query_builder.push(format!(" LIMIT {page_size} OFFSET {offset} "));
}
query_builder
}
struct MediaFileListItemInternal {
struct BetaKeyListItemInternal {
id: i64,
token: BetaKeyToken,
@@ -266,7 +299,7 @@ struct MediaFileListItemInternal {
maybe_redeemed_at: Option<DateTime<Utc>>,
}
impl FromRow<'_, MySqlRow> for MediaFileListItemInternal {
impl FromRow<'_, MySqlRow> for BetaKeyListItemInternal {
fn from_row(row: &MySqlRow) -> Result<Self, sqlx::Error> {
let creator_user_token : String = row.try_get("creator_user_token")?;
let creator_user_token = UserToken::new(creator_user_token);
@@ -29,6 +29,7 @@ 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::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;
@@ -44,8 +45,7 @@ use crate::util::allowed_explore_media_access::allowed_explore_media_access;
pub struct ListBetaKeysQueryParams {
pub sort_ascending: Option<bool>,
pub page_size: Option<usize>,
pub cursor: Option<String>,
pub cursor_is_reversed: Option<bool>,
pub page_index: Option<usize>,
/// Scope the beta keys to a referrer user.
pub maybe_referrer_username: Option<String>,
@@ -58,7 +58,7 @@ pub struct ListBetaKeysQueryParams {
pub struct ListBetaKeysSuccessResponse {
pub success: bool,
pub beta_keys: Vec<BetaKeyItem>,
pub pagination: PaginationCursors,
pub pagination: PaginationPage,
}
#[derive(Serialize, ToSchema)]
@@ -150,28 +150,15 @@ pub async fn list_beta_keys_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 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);
ListBetaKeysError::ServerError
})?;
Some(cursor as usize)
} else {
None
};
let page_size = query.page_size.unwrap_or_else(|| 500);
let page_index = query.page_index.unwrap_or_else(|| 0);
let query_results = list_beta_keys(ListBetaKeysArgs {
filter_to_referrer_user_token: maybe_scope_user_token.as_ref(),
filter_to_remaining_keys: query.only_list_remaining.unwrap_or(false),
limit,
maybe_offset: cursor,
cursor_is_reversed,
page_size,
page_index,
sort_ascending,
mysql_pool: &server_state.mysql_pool,
}).await;
@@ -184,28 +171,6 @@ pub async fn list_beta_keys_handler(
}
};
let cursor_next = if let Some(id) = results_page.last_id {
let cursor = server_state.sort_key_crypto.encrypt_id(id as u64)
.map_err(|e| {
warn!("crypto error: {:?}", e);
ListBetaKeysError::ServerError
})?;
Some(cursor)
} else {
None
};
let cursor_previous = if let Some(id) = results_page.first_id {
let cursor = server_state.sort_key_crypto.encrypt_id(id as u64)
.map_err(|e| {
warn!("crypto error: {:?}", e);
ListBetaKeysError::ServerError
})?;
Some(cursor)
} else {
None
};
let results = results_page.records.into_iter()
.map(|beta_key| {
BetaKeyItem {
@@ -239,10 +204,9 @@ pub async fn list_beta_keys_handler(
let response = ListBetaKeysSuccessResponse {
success: true,
beta_keys: results,
pagination: PaginationCursors {
maybe_next: cursor_next,
maybe_previous: cursor_previous,
cursor_is_reversed,
pagination: PaginationPage{
current: results_page.current_page,
total_page_count: results_page.total_page_count,
}
};