Compare commits

...
Author SHA1 Message Date
Brandon Thomas fc0b513cca more cleanup 2026-07-04 11:52:32 -04:00
5 changed files with 0 additions and 433 deletions
@@ -1,279 +0,0 @@
use chrono::{DateTime, Utc};
use sqlx::{MySql, MySqlPool};
use sqlx::pool::PoolConnection;
use errors::AnyhowResult;
use crate::helpers::boolean_converters::{i8_to_bool, nullable_i8_to_optional_bool};
/// List of assigned categories for a TTS model
/// This is *NOT* paginated, as that defeats the purpose of having a category system.
#[derive(Serialize)]
pub struct AssignedCategoryList {
pub categories: Vec<AssignedCategory>,
}
#[derive(Serialize)]
pub struct AssignedCategory {
pub category_token: String,
pub model_type: String, // TODO: ENUM
pub maybe_super_category_token: Option<String>,
pub can_directly_have_models: bool,
pub can_have_subcategories: bool,
pub can_only_mods_apply: bool,
pub name: String,
pub maybe_dropdown_name: Option<String>,
pub category_creator_user_token: Option<String>,
pub category_creator_username: Option<String>,
pub category_creator_display_name: Option<String>,
pub category_creator_gravatar_hash: Option<String>,
// Moderator fields
pub is_mod_approved: Option<bool>,
pub maybe_mod_comments: Option<String>,
//pub maybe_mod_user_token: Option<String>,
//pub maybe_mod_username: Option<String>,
//pub maybe_mod_display_name: Option<String>,
pub category_created_at: DateTime<Utc>,
pub category_updated_at: DateTime<Utc>,
pub category_deleted_at: Option<DateTime<Utc>>,
pub category_assignment_created_at: DateTime<Utc>,
pub category_assignment_updated_at: DateTime<Utc>,
pub category_assignment_deleted_at: Option<DateTime<Utc>>,
}
/// Query Builder for listing categories
/// These are very difficult queries, so the builder helps for
/// testability, construction, and correctness.
pub struct ListAssignedTtsCategoriesQueryBuilder {
// REQUIRED.
scope_tts_model_token: String,
// Sometimes "can_directly_have_models" categories change state.
// Mods will want to see invalid uses.
show_invalid_model_not_allowed_categories: bool,
// The public lists won't include unapproved, but mod views
// will want to see them.
show_unapproved: bool,
// The public lists won't include deleted models, but mod views
// will want to see them.
show_deleted: bool,
}
impl ListAssignedTtsCategoriesQueryBuilder {
pub fn for_model_token(model_token: &str) -> Self {
Self {
scope_tts_model_token: model_token.to_string(),
show_invalid_model_not_allowed_categories: false,
show_unapproved: false,
show_deleted: false,
}
}
pub fn show_invalid_model_not_allowed_categories(mut self, show_invalid_model_not_allowed_categories: bool) -> Self {
self.show_invalid_model_not_allowed_categories = show_invalid_model_not_allowed_categories;
self
}
pub fn show_unapproved(mut self, show_unapproved: bool) -> Self {
self.show_unapproved = show_unapproved;
self
}
pub fn show_deleted(mut self, show_deleted: bool) -> Self {
self.show_deleted = show_deleted;
self
}
/// Perform the query based on the set predicates.
#[deprecated = "Use the PoolConnection<MySql> method instead of the MySqlPool one."]
pub async fn perform_query(
&self,
mysql_pool: &MySqlPool
) -> AnyhowResult<AssignedCategoryList> {
let mut mysql_connection = mysql_pool.acquire().await?;
self.perform_query_by_connection(&mut mysql_connection).await
}
/// Perform the query based on the set predicates.
pub async fn perform_query_by_connection(
&self,
mysql_connection: &mut PoolConnection<MySql>
) -> AnyhowResult<AssignedCategoryList> {
let internal_results = self.perform_internal_query(mysql_connection).await?;
let categories = internal_results
.into_iter()
.map(|c| {
AssignedCategory {
category_token: c.category_token,
model_type: c.model_type,
maybe_super_category_token: c.maybe_super_category_token,
can_directly_have_models: i8_to_bool(c.can_directly_have_models),
can_have_subcategories: i8_to_bool(c.can_have_subcategories),
can_only_mods_apply: i8_to_bool(c.can_only_mods_apply),
name: c.name,
maybe_dropdown_name: c.maybe_dropdown_name,
category_creator_user_token: c.category_creator_user_token,
category_creator_username: c.category_creator_username,
category_creator_display_name: c.category_creator_display_name,
category_creator_gravatar_hash: c.category_creator_gravatar_hash,
is_mod_approved: nullable_i8_to_optional_bool(c.is_mod_approved),
maybe_mod_comments: c.maybe_mod_comments,
category_created_at: c.category_created_at,
category_updated_at: c.category_updated_at,
category_deleted_at: c.category_deleted_at,
category_assignment_created_at: c.category_assignment_created_at,
category_assignment_updated_at: c.category_assignment_updated_at,
category_assignment_deleted_at: c.category_assignment_deleted_at,
}
})
.collect::<Vec<AssignedCategory>>();
Ok(AssignedCategoryList {
categories,
})
}
/// Perform the query based on the set predicates.
async fn perform_internal_query(
&self,
mysql_connection: &mut PoolConnection<MySql>
) -> AnyhowResult<Vec<RawInternalCategoryRecord>> {
let query = self.build_query_string();
let mut query = sqlx::query_as::<_, RawInternalCategoryRecord>(&query);
// NB: The following bindings must match the order of the query builder !!
// NB: Model token is always used for scoping.
query = query.bind(&self.scope_tts_model_token);
let results = query.fetch_all(&mut **mysql_connection)
.await?;
Ok(results)
}
fn build_query_string(&self) -> String {
// TODO: I haven't figured out how to get field name disambiguation and type coercion working here.
// (1) tts_results.creator_set_visibility `creator_set_visibility: crate::database::enums::record_visibility::RecordVisibility`,
// Query error: no column found for name: creator_set_visibility
// (2) creator_set_visibility `creator_set_visibility: crate::database::enums::record_visibility::RecordVisibility`,
// Column 'creator_set_visibility' in field list is ambiguous
// TODO/NB: Unfortunately SQLx can't statically typecheck this query
let mut query = r#"
SELECT
model_categories.token as category_token,
model_categories.model_type,
model_categories.maybe_super_category_token,
model_categories.can_directly_have_models,
model_categories.can_have_subcategories,
model_categories.can_only_mods_apply,
model_categories.name,
model_categories.maybe_dropdown_name,
users.token as category_creator_user_token,
users.username as category_creator_username,
users.display_name as category_creator_display_name,
users.email_gravatar_hash as category_creator_gravatar_hash,
model_categories.is_mod_approved,
model_categories.maybe_mod_comments,
model_categories.created_at as category_created_at,
model_categories.updated_at as category_updated_at,
model_categories.deleted_at as category_deleted_at,
tts_category_assignments.created_at as category_assignment_created_at,
tts_category_assignments.updated_at as category_assignment_updated_at,
tts_category_assignments.deleted_at as category_assignment_deleted_at
FROM model_categories
JOIN tts_category_assignments
ON tts_category_assignments.category_token = model_categories.token
LEFT OUTER JOIN users
ON model_categories.creator_user_token = users.token
"#.to_string();
query.push_str(&self.build_predicates());
query
}
pub fn build_predicates(&self) -> String {
let mut query = "".to_string();
// Scoping to tts model is required.
query.push_str(" WHERE tts_category_assignments.model_token = ?");
query.push_str(" AND model_categories.model_type = 'tts'");
// We also don't care to ever show soft-deleted assignments
query.push_str(" AND tts_category_assignments.deleted_at IS NULL");
if !self.show_invalid_model_not_allowed_categories {
query.push_str(" AND model_categories.can_directly_have_models IS TRUE");
}
if !self.show_unapproved {
query.push_str(" AND model_categories.is_mod_approved IS TRUE");
}
if !self.show_deleted {
query.push_str(" AND model_categories.deleted_at IS NULL");
}
query
}
}
#[derive(sqlx::FromRow)]
pub struct RawInternalCategoryRecord {
pub category_token: String,
pub model_type: String, // TODO: ENUM
pub maybe_super_category_token: Option<String>,
pub can_directly_have_models: i8,
pub can_have_subcategories: i8,
pub can_only_mods_apply: i8,
pub name: String,
pub maybe_dropdown_name: Option<String>,
pub category_creator_user_token: Option<String>,
pub category_creator_username: Option<String>,
pub category_creator_display_name: Option<String>,
pub category_creator_gravatar_hash: Option<String>,
// Moderator fields
pub is_mod_approved: Option<i8>,
pub maybe_mod_comments: Option<String>,
pub category_created_at: DateTime<Utc>,
pub category_updated_at: DateTime<Utc>,
pub category_deleted_at: Option<DateTime<Utc>>,
pub category_assignment_created_at: DateTime<Utc>,
pub category_assignment_updated_at: DateTime<Utc>,
pub category_assignment_deleted_at: Option<DateTime<Utc>>,
}
#[cfg(test)]
mod tests {
}
@@ -1,2 +1 @@
pub mod fetch_and_build_tts_model_category_map;
pub mod list_assigned_tts_categories_query_builder;
@@ -1,143 +0,0 @@
// NB: Incrementally getting rid of build warnings...
#![forbid(unused_imports)]
#![forbid(unused_mut)]
#![forbid(unused_variables)]
use std::sync::Arc;
use actix_web::web::Path;
use actix_web::{web, HttpRequest, HttpResponse};
use chrono::{DateTime, Utc};
use log::warn;
use mysql_queries::queries::tts::tts_category_assignments::list_assigned_tts_categories_query_builder::ListAssignedTtsCategoriesQueryBuilder;
use crate::state::server_state::ServerState;
use crate::http_server::common_responses::common_web_error::CommonWebError;
// =============== Request ===============
/// For the URL PathInfo
#[derive(Deserialize)]
pub struct ListTtsModelAssignedCategoriesPathInfo {
/// TTS model token
token: String,
}
// =============== Success Response ===============
#[derive(Serialize)]
pub struct ListTtsModelAssignedCategoriesResponse {
pub success: bool,
pub categories: Vec<DisplayCategory>,
}
/// Public-facing and optimized (non-irrelevant fields) category list
/// Used for the main TTS dropdown as well as the TTS edit/tagging UI
#[derive(Serialize)]
pub struct DisplayCategory {
pub category_token: String,
pub model_type: String, // TODO: ENUM
pub maybe_super_category_token: Option<String>,
pub can_directly_have_models: bool,
pub can_have_subcategories: bool,
pub can_only_mods_apply: bool,
pub name: String,
pub maybe_dropdown_name: Option<String>,
// NB: It's okay for non-mods to see this.
pub is_mod_approved: Option<bool>,
pub category_created_at: DateTime<Utc>,
pub category_updated_at: DateTime<Utc>,
pub category_deleted_at: Option<DateTime<Utc>>,
//pub creator_user_token: Option<String>,
//pub creator_username: Option<String>,
//pub creator_display_name: Option<String>,
}
// =============== Error Response ===============
// NB: Not using derive_more::Display since Clion doesn't understand it.
// =============== Handler ===============
pub async fn list_tts_model_assigned_categories_handler(
http_request: HttpRequest,
path: Path<ListTtsModelAssignedCategoriesPathInfo>,
server_state: web::Data<Arc<ServerState>>) -> Result<HttpResponse, CommonWebError>
{
let mut mysql_connection = server_state.mysql_pool.acquire()
.await
.map_err(|e| {
warn!("Could not acquire DB pool: {:?}", e);
CommonWebError::from_error(e)
})?;
let maybe_user_session = server_state
.session_checker
.maybe_get_user_session_from_connection(&http_request, &mut mysql_connection)
.await
.map_err(|e| {
warn!("Session checker error: {:?}", e);
CommonWebError::from_error(e)
})?;
// TODO: We don't have any permissions for categories. This is a proxy.
let is_mod = maybe_user_session
.map(|session| session.can_ban_users)
.unwrap_or(false);
let query_builder = ListAssignedTtsCategoriesQueryBuilder::for_model_token(&path.token)
.show_invalid_model_not_allowed_categories(is_mod)
.show_deleted(is_mod)
.show_unapproved(is_mod);
let query_result =
query_builder.perform_query_by_connection(&mut mysql_connection).await;
let results = match query_result {
Ok(results) => results,
Err(err) => {
warn!("DB error: {:?}", err);
return Err(CommonWebError::from_anyhow_error(err));
}
};
let mut categories = results.categories.into_iter()
.map(|c| {
DisplayCategory {
category_token: c.category_token,
model_type: c.model_type,
maybe_super_category_token: c.maybe_super_category_token,
can_directly_have_models: c.can_directly_have_models,
can_have_subcategories: c.can_have_subcategories,
can_only_mods_apply: c.can_only_mods_apply,
name: c.name.clone(),
maybe_dropdown_name:c.maybe_dropdown_name,
is_mod_approved: c.is_mod_approved,
category_created_at: c.category_created_at,
category_updated_at: c.category_updated_at,
category_deleted_at: c.category_deleted_at,
}
})
.collect::<Vec<DisplayCategory>>();
// TODO: Sort by dropdown name too.
categories.sort_by(|c1, c2| c1.name.cmp(&c2.name));
let response = ListTtsModelAssignedCategoriesResponse {
success: true,
categories,
};
let body = serde_json::to_string(&response)
.map_err(CommonWebError::from_error)?;
Ok(HttpResponse::Ok()
.content_type("application/json")
.body(body))
}
@@ -1,4 +1,3 @@
pub mod assign_tts_category;
pub mod list_fully_computed_assigned_tts_categories;
pub mod list_tts_categories;
pub mod list_tts_model_assigned_categories;
@@ -9,7 +9,6 @@ use crate::http_server::deprecated_endpoints::categories::get_category::get_cate
use crate::http_server::deprecated_endpoints::categories::tts::assign_tts_category::assign_tts_category_handler;
use crate::http_server::deprecated_endpoints::categories::tts::list_fully_computed_assigned_tts_categories::list_fully_computed_assigned_tts_categories::list_fully_computed_assigned_tts_categories_handler;
use crate::http_server::deprecated_endpoints::categories::tts::list_tts_categories::list_tts_categories_handler;
use crate::http_server::deprecated_endpoints::categories::tts::list_tts_model_assigned_categories::list_tts_model_assigned_categories_handler;
use crate::http_server::deprecated_endpoints::conversion::enqueue_fbx_to_gltf_handler::enqueue_fbx_to_gltf_handler;
use crate::http_server::deprecated_endpoints::conversion::enqueue_render_engine_scene_to_video_handler::enqueue_render_engine_scene_to_video_handler;
use crate::http_server::deprecated_endpoints::engine::create_scene_handler::create_scene_handler;
@@ -233,14 +232,6 @@ where
.route(web::head().to(|| HttpResponse::Ok()))
)
)
.service(
web::scope("/assignments")
.service(
web::resource("/tts/{token}")
.route(web::get().to(list_tts_model_assigned_categories_handler))
.route(web::head().to(|| HttpResponse::Ok()))
)
)
)
}