mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-05 01:38:44 +00:00
Merge pull request #265 from storytold/brandon-batch-get-user-bookmarks
batch get user bookmarks
This commit is contained in:
7 files changed
+291
-3
No files matched your search
@@ -0,0 +1,83 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use log::error;
|
||||
use sqlx::{MySql, QueryBuilder};
|
||||
use sqlx::pool::PoolConnection;
|
||||
|
||||
use enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType;
|
||||
use errors::AnyhowResult;
|
||||
use tokens::tokens::user_bookmarks::UserBookmarkToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
pub struct BatchUserBookmark {
|
||||
pub token: UserBookmarkToken,
|
||||
pub entity_token: String,
|
||||
pub entity_type: UserBookmarkEntityType,
|
||||
}
|
||||
|
||||
pub async fn batch_get_user_bookmarks(
|
||||
user_token: &UserToken,
|
||||
tokens: &HashSet<String>,
|
||||
mysql_connection: &mut PoolConnection<MySql>,
|
||||
) -> AnyhowResult<Vec<BatchUserBookmark>>
|
||||
{
|
||||
if tokens.len() == 0 {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let mut query_builder: QueryBuilder<MySql> = QueryBuilder::new(
|
||||
r#"
|
||||
SELECT
|
||||
token,
|
||||
entity_type,
|
||||
entity_token
|
||||
|
||||
FROM user_bookmarks
|
||||
|
||||
WHERE user_token =
|
||||
"#);
|
||||
|
||||
query_builder.push_bind(user_token.as_str());
|
||||
|
||||
query_builder.push(" AND entity_token IN ( ");
|
||||
|
||||
// NB: Syntax will be wrong if list has zero length
|
||||
let mut separated = query_builder.separated(", ");
|
||||
|
||||
for token in tokens.iter() {
|
||||
separated.push_bind(token);
|
||||
}
|
||||
|
||||
separated.push_unseparated(") ");
|
||||
|
||||
let query = query_builder.build_query_as::<RawRating>();
|
||||
|
||||
let maybe_results = query.fetch_all(&mut **mysql_connection).await;
|
||||
|
||||
match maybe_results {
|
||||
Ok(records) => Ok(records
|
||||
.into_iter()
|
||||
.map(|record| BatchUserBookmark {
|
||||
token: UserBookmarkToken::new_from_str(&record.token),
|
||||
// NB: Fail open; W2lTemplates are dead, so this is a good sentinel value
|
||||
entity_type: UserBookmarkEntityType::from_str(&record.entity_type)
|
||||
.unwrap_or(UserBookmarkEntityType::W2lTemplate),
|
||||
entity_token: record.entity_token,
|
||||
}).collect::<Vec<_>>()),
|
||||
Err(err) => match err {
|
||||
sqlx::Error::RowNotFound => Ok(Vec::new()),
|
||||
_ => {
|
||||
error!("error querying : {:?}", err);
|
||||
Err(anyhow!("error querying : {:?}", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(sqlx::FromRow)]
|
||||
struct RawRating {
|
||||
token: String,
|
||||
entity_token: String,
|
||||
entity_type: String,
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod batch_get_user_bookmarks;
|
||||
pub mod create_user_bookmark;
|
||||
pub mod delete_user_bookmark;
|
||||
pub mod get_total_bookmark_count_for_entity;
|
||||
|
||||
@@ -15,7 +15,6 @@ pub struct BatchUserRating {
|
||||
pub rating_value: UserRatingValue,
|
||||
}
|
||||
|
||||
/// Look up the user rating
|
||||
pub async fn batch_get_user_ratings(
|
||||
user_token: &UserToken,
|
||||
tokens: &HashSet<String>,
|
||||
@@ -70,8 +69,8 @@ WHERE user_token =
|
||||
Err(err) => match err {
|
||||
sqlx::Error::RowNotFound => Ok(Vec::new()),
|
||||
_ => {
|
||||
error!("error querying job record: {:?}", err);
|
||||
Err(anyhow!("error querying job record: {:?}", err))
|
||||
error!("error querying : {:?}", err);
|
||||
Err(anyhow!("error querying : {:?}", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ use crate::http_server::endpoints::media_files::list_media_files::*;
|
||||
use crate::http_server::endpoints::media_files::list_media_files_for_user::*;
|
||||
use crate::http_server::endpoints::media_files::upload::upload_error::MediaFileUploadError;
|
||||
use crate::http_server::endpoints::media_files::upload_media_file::*;
|
||||
use crate::http_server::endpoints::user_bookmarks::batch_get_user_bookmarks_handler::*;
|
||||
use crate::http_server::endpoints::user_bookmarks::create_user_bookmark_handler::*;
|
||||
use crate::http_server::endpoints::user_bookmarks::delete_user_bookmark_handler::*;
|
||||
use crate::http_server::endpoints::user_bookmarks::list_user_bookmarks_for_entity_handler::*;
|
||||
@@ -49,6 +50,7 @@ use crate::http_server::web_utils::response_success_helpers::*;
|
||||
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,
|
||||
crate::http_server::endpoints::media_files::upload_media_file::upload_media_file_handler,
|
||||
crate::http_server::endpoints::user_bookmarks::batch_get_user_bookmarks_handler::batch_get_user_bookmarks_handler,
|
||||
crate::http_server::endpoints::user_bookmarks::create_user_bookmark_handler::create_user_bookmark_handler,
|
||||
crate::http_server::endpoints::user_bookmarks::delete_user_bookmark_handler::delete_user_bookmark_handler,
|
||||
crate::http_server::endpoints::user_bookmarks::list_user_bookmarks_for_entity_handler::list_user_bookmarks_for_entity_handler,
|
||||
@@ -104,6 +106,7 @@ use crate::http_server::web_utils::response_success_helpers::*;
|
||||
ListWeightsByUserError,ListWeightsByUserSuccessResponse,ListWeightsByUserPathInfo,Weight,
|
||||
WeightsData,MediaFileData,
|
||||
BatchGetUserRatingQueryParams,BatchGetUserRatingResponse,BatchGetUserRatingError,RatingRow,
|
||||
BatchGetUserBookmarksQueryParams,BatchGetUserBookmarksResponse,BatchGetUserBookmarksError,BookmarkRow,
|
||||
ListFeaturedMediaFilesSuccessResponse,MediaFile, ListFeaturedMediaFilesError,
|
||||
ListMediaFilesSuccessResponse, ListMediaFilesError, MediaFileListItem, ListMediaFilesQueryParams,
|
||||
ListMediaFilesForUserSuccessResponse,ListMediaFilesForUserQueryParams,ListMediaFilesForUserError,ListMediaFilesForUserPathInfo,
|
||||
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Arc;
|
||||
|
||||
use actix_web::{HttpRequest, HttpResponse, ResponseError, web};
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web_lab::extract::Query;
|
||||
use log::error;
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
|
||||
use enums::by_table::user_bookmarks::user_bookmark_entity_type::UserBookmarkEntityType;
|
||||
use http_server_common::response::serialize_as_json_error::serialize_as_json_error;
|
||||
use mysql_queries::queries::user_bookmarks::batch_get_user_bookmarks::{batch_get_user_bookmarks, BatchUserBookmark};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use tokens::tokens::model_weights::ModelWeightToken;
|
||||
use tokens::tokens::tts_models::TtsModelToken;
|
||||
|
||||
use crate::server_state::ServerState;
|
||||
|
||||
const MAX_BATCH_SIZE : usize = 200;
|
||||
|
||||
// =============== Request ===============
|
||||
|
||||
#[derive(Deserialize, ToSchema, IntoParams)]
|
||||
pub struct BatchGetUserBookmarksQueryParams {
|
||||
/// A grab bag of tokens of various types.
|
||||
/// Technically we should pair with token types, as that's the unique index.
|
||||
/// But since each token family generally has its own prefix, this should be fine.
|
||||
///
|
||||
/// NB: We're using actix_web_lab's Query<T>, because the default actix_web Query<T> doesn't support URL
|
||||
/// decoding sequences yet.
|
||||
/// See https://github.com/actix/actix-web/issues/1301
|
||||
///
|
||||
pub tokens: HashSet<String>,
|
||||
}
|
||||
|
||||
// =============== Success Response ===============
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct BatchGetUserBookmarksResponse {
|
||||
pub success: bool,
|
||||
|
||||
/// Ratings on each item passed to us.
|
||||
pub bookmarks: Vec<BookmarkRow>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct BookmarkRow {
|
||||
/// The passed token
|
||||
pub entity_token: String,
|
||||
/// The type of entity
|
||||
pub entity_type: UserBookmarkEntityType,
|
||||
/// Whether the entity is bookmarked or not
|
||||
pub is_bookmarked: bool,
|
||||
}
|
||||
|
||||
// =============== Error Response ===============
|
||||
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub enum BatchGetUserBookmarksError {
|
||||
BadInput(String),
|
||||
NotAuthorized,
|
||||
ServerError,
|
||||
}
|
||||
|
||||
impl ResponseError for BatchGetUserBookmarksError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match *self {
|
||||
BatchGetUserBookmarksError::BadInput(_) => StatusCode::BAD_REQUEST,
|
||||
BatchGetUserBookmarksError::NotAuthorized => StatusCode::UNAUTHORIZED,
|
||||
BatchGetUserBookmarksError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
serialize_as_json_error(self)
|
||||
}
|
||||
}
|
||||
|
||||
// NB: Not using DeriveMore since Clion doesn't understand it.
|
||||
impl std::fmt::Display for BatchGetUserBookmarksError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
// =============== Handler ===============
|
||||
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/v1/user_bookmarks/batch",
|
||||
params(
|
||||
BatchGetUserBookmarksQueryParams,
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "List User Bookmarks", body = BatchGetUserBookmarksResponse),
|
||||
(status = 400, description = "Bad input", body = BatchGetUserBookmarksError),
|
||||
(status = 401, description = "Not authorized", body = BatchGetUserBookmarksError),
|
||||
(status = 500, description = "Server error", body = BatchGetUserBookmarksError),
|
||||
),
|
||||
)]
|
||||
pub async fn batch_get_user_bookmarks_handler(
|
||||
http_request: HttpRequest,
|
||||
query: Query<BatchGetUserBookmarksQueryParams>,
|
||||
server_state: web::Data<Arc<ServerState>>) -> Result<HttpResponse, BatchGetUserBookmarksError>
|
||||
{
|
||||
let mut mysql_connection = server_state.mysql_pool.acquire()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("Could not acquire DB pool: {:?}", e);
|
||||
BatchGetUserBookmarksError::ServerError
|
||||
})?;
|
||||
|
||||
let maybe_user_session = server_state
|
||||
.session_checker
|
||||
.maybe_get_user_session_from_connection(&http_request, &mut mysql_connection)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("Session checker error: {:?}", e);
|
||||
BatchGetUserBookmarksError::ServerError
|
||||
})?;
|
||||
|
||||
// NB: Force move of tokens from the Query<T>.
|
||||
// The auto-magical Query<T> will ordinarily try to force a Copy, which isn't on HashSet.
|
||||
let mut tokens = query.0.tokens;
|
||||
|
||||
// Don't allow bad actors to flood our DB.
|
||||
tokens.shrink_to(MAX_BATCH_SIZE);
|
||||
|
||||
let user_session = match maybe_user_session {
|
||||
Some(session) => session,
|
||||
None => {
|
||||
let bookmarks = fill_in_missed_bookmarks(&tokens, Vec::new());
|
||||
|
||||
// NB: Just return "neutral" for everything.
|
||||
return Ok(HttpResponse::Ok()
|
||||
.content_type("application/json")
|
||||
.json(BatchGetUserBookmarksResponse {
|
||||
success: true,
|
||||
bookmarks,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
batch_get_user_bookmarks(
|
||||
&user_session.user_token_typed,
|
||||
&tokens,
|
||||
&mut mysql_connection
|
||||
).await
|
||||
.map_err(|e| {
|
||||
error!("Batch get user ratings DB error: {:?}", e);
|
||||
BatchGetUserBookmarksError::ServerError
|
||||
})
|
||||
.map(|bookmarks| {
|
||||
HttpResponse::Ok()
|
||||
.content_type("application/json")
|
||||
.json(BatchGetUserBookmarksResponse {
|
||||
success: true,
|
||||
bookmarks: fill_in_missed_bookmarks(&tokens, bookmarks),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn fill_in_missed_bookmarks(request_tokens: &HashSet<String>, db_response: Vec<BatchUserBookmark>) -> Vec<BookmarkRow> {
|
||||
let mut outputs = HashMap::with_capacity(request_tokens.len());
|
||||
|
||||
for record in db_response.into_iter() {
|
||||
outputs.insert(record.entity_token.clone(), BookmarkRow {
|
||||
entity_token: record.entity_token,
|
||||
entity_type: record.entity_type,
|
||||
is_bookmarked: true,
|
||||
});
|
||||
}
|
||||
|
||||
for request_token in request_tokens.iter() {
|
||||
if !outputs.contains_key(request_token) {
|
||||
outputs.insert(request_token.clone(), BookmarkRow {
|
||||
entity_token: request_token.clone(),
|
||||
entity_type: {
|
||||
if request_token.starts_with(MediaFileToken::token_prefix()) {
|
||||
UserBookmarkEntityType::MediaFile
|
||||
} else if request_token.starts_with(ModelWeightToken::token_prefix()) {
|
||||
UserBookmarkEntityType::ModelWeight
|
||||
} else if request_token.starts_with(TtsModelToken::token_prefix()) {
|
||||
UserBookmarkEntityType::TtsModel
|
||||
} else {
|
||||
// NB: Fail open; W2lTemplates are dead, so this is a good sentinel value
|
||||
UserBookmarkEntityType::W2lTemplate
|
||||
}
|
||||
},
|
||||
is_bookmarked: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
outputs.into_iter()
|
||||
.map(|(_key, value)| value)
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod batch_get_user_bookmarks_handler;
|
||||
pub mod create_user_bookmark_handler;
|
||||
pub mod delete_user_bookmark_handler;
|
||||
pub mod list_user_bookmarks_for_entity_handler;
|
||||
|
||||
@@ -108,6 +108,7 @@ use crate::http_server::endpoints::twitch::oauth::check_oauth_status::check_oaut
|
||||
use crate::http_server::endpoints::twitch::oauth::oauth_begin_json::oauth_begin_enroll_json;
|
||||
use crate::http_server::endpoints::twitch::oauth::oauth_begin_redirect::oauth_begin_enroll_redirect;
|
||||
use crate::http_server::endpoints::twitch::oauth::oauth_end::oauth_end_enroll_from_redirect;
|
||||
use crate::http_server::endpoints::user_bookmarks::batch_get_user_bookmarks_handler::batch_get_user_bookmarks_handler;
|
||||
use crate::http_server::endpoints::user_bookmarks::create_user_bookmark_handler::create_user_bookmark_handler;
|
||||
use crate::http_server::endpoints::user_bookmarks::delete_user_bookmark_handler::delete_user_bookmark_handler;
|
||||
use crate::http_server::endpoints::user_bookmarks::list_user_bookmarks_for_entity_handler::list_user_bookmarks_for_entity_handler;
|
||||
@@ -210,6 +211,7 @@ pub fn add_routes<T, B> (app: App<T>, server_environment: ServerEnvironment) ->
|
||||
// ==================== User Bookmarks ====================
|
||||
|
||||
let mut app = RouteBuilder::from_app(app)
|
||||
.add_get("/v1/user_bookmarks/batch", batch_get_user_bookmarks_handler)
|
||||
.add_post("/v1/user_bookmarks/create", create_user_bookmark_handler)
|
||||
.add_post("/v1/user_bookmarks/delete/{user_bookmark_token}", delete_user_bookmark_handler)
|
||||
.add_get("/v1/user_bookmarks/list/session", list_user_bookmarks_for_session_handler)
|
||||
|
||||
Reference in new issue
Block a user