diff --git a/crates/api_clients/artcraft/artcraft_api_defs/src/lib.rs b/crates/api_clients/artcraft/artcraft_api_defs/src/lib.rs index d1b178bb3a..5330fe68c8 100644 --- a/crates/api_clients/artcraft/artcraft_api_defs/src/lib.rs +++ b/crates/api_clients/artcraft/artcraft_api_defs/src/lib.rs @@ -4,6 +4,7 @@ pub mod credits; pub mod generate; pub mod jobs; pub mod media_file; +pub mod moderation; pub mod prompts; pub mod stripe_artcraft; pub mod subscriptions; diff --git a/crates/api_clients/artcraft/artcraft_api_defs/src/moderation/mod.rs b/crates/api_clients/artcraft/artcraft_api_defs/src/moderation/mod.rs new file mode 100644 index 0000000000..22d12a3827 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_api_defs/src/moderation/mod.rs @@ -0,0 +1 @@ +pub mod user; diff --git a/crates/api_clients/artcraft/artcraft_api_defs/src/moderation/user/mod.rs b/crates/api_clients/artcraft/artcraft_api_defs/src/moderation/user/mod.rs new file mode 100644 index 0000000000..889e777eca --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_api_defs/src/moderation/user/mod.rs @@ -0,0 +1 @@ +pub mod user_lookup; diff --git a/crates/api_clients/artcraft/artcraft_api_defs/src/moderation/user/user_lookup.rs b/crates/api_clients/artcraft/artcraft_api_defs/src/moderation/user/user_lookup.rs new file mode 100644 index 0000000000..9a6846ab37 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_api_defs/src/moderation/user/user_lookup.rs @@ -0,0 +1,33 @@ +use serde_derive::{Deserialize, Serialize}; +use utoipa::ToSchema; + +pub const MODERATOR_USER_LOOKUP_PATH: &str = "/v1/moderation/user_lookup"; + +#[derive(Deserialize, ToSchema)] +pub struct ModeratorUserLookupRequest { + pub search: String, +} + +#[derive(Serialize, ToSchema)] +pub struct ModeratorUserLookupSuccessResponse { + pub success: bool, + pub maybe_user: Option, +} + +#[derive(Serialize, ToSchema)] +pub struct ModeratorUserLookupUserDetails { + pub token: String, + pub username: String, + pub display_name: String, + pub username_is_generated: bool, + pub is_temporary: bool, + pub username_is_not_customized: bool, + pub email_address: String, + pub email_confirmed: bool, + pub email_is_synthetic: bool, + pub is_without_password: bool, + pub ip_address_creation: String, + pub ip_address_last_login: String, + pub maybe_avatar_media_file_token: Option, + pub email_gravatar_hash: String, +} diff --git a/crates/schema/database/mysql_queries/src/queries/users/user_profiles/lookup_user_for_moderation.rs b/crates/schema/database/mysql_queries/src/queries/users/user/get/lookup_user_for_moderation.rs similarity index 55% rename from crates/schema/database/mysql_queries/src/queries/users/user_profiles/lookup_user_for_moderation.rs rename to crates/schema/database/mysql_queries/src/queries/users/user/get/lookup_user_for_moderation.rs index c87a9bc840..5ada496e68 100644 --- a/crates/schema/database/mysql_queries/src/queries/users/user_profiles/lookup_user_for_moderation.rs +++ b/crates/schema/database/mysql_queries/src/queries/users/user/get/lookup_user_for_moderation.rs @@ -6,8 +6,6 @@ use sqlx::MySqlPool; use errors::AnyhowResult; use tokens::tokens::users::UserToken; -use crate::helpers::boolean_converters::i8_to_bool; - pub struct LookupUserForModerationResult { pub user_token: UserToken, pub username: String, @@ -26,89 +24,24 @@ pub struct LookupUserForModerationResult { pub created_at: DateTime, } -struct RawLookupUserRecord { - user_token: UserToken, - username: String, - display_name: String, - username_is_generated: i8, - is_temporary: i8, - username_is_not_customized: i8, - email_address: String, - email_confirmed: i8, - email_is_synthetic: i8, - is_without_password: i8, - ip_address_creation: String, - ip_address_last_login: String, - maybe_avatar_media_file_token: Option, - email_gravatar_hash: String, - created_at: DateTime, -} - -impl From for LookupUserForModerationResult { - fn from(raw: RawLookupUserRecord) -> Self { - Self { - user_token: raw.user_token, - username: raw.username, - display_name: raw.display_name, - username_is_generated: i8_to_bool(raw.username_is_generated), - is_temporary: i8_to_bool(raw.is_temporary), - username_is_not_customized: i8_to_bool(raw.username_is_not_customized), - email_address: raw.email_address, - email_confirmed: i8_to_bool(raw.email_confirmed), - email_is_synthetic: i8_to_bool(raw.email_is_synthetic), - is_without_password: i8_to_bool(raw.is_without_password), - ip_address_creation: raw.ip_address_creation, - ip_address_last_login: raw.ip_address_last_login, - maybe_avatar_media_file_token: raw.maybe_avatar_media_file_token, - email_gravatar_hash: raw.email_gravatar_hash, - created_at: raw.created_at, - } - } -} - -const QUERY: &str = r#" -SELECT - users.token as `user_token: tokens::tokens::users::UserToken`, - username, - display_name, - username_is_generated, - is_temporary, - username_is_not_customized, - email_address, - email_confirmed, - email_is_synthetic, - is_without_password, - ip_address_creation, - ip_address_last_login, - maybe_avatar_media_file_token, - email_gravatar_hash, - created_at -FROM users -WHERE - users.token = ? - AND users.user_deleted_at IS NULL - AND users.mod_deleted_at IS NULL -LIMIT 1 -"#; - pub async fn lookup_user_for_moderation_by_token( token: &str, mysql_pool: &MySqlPool, ) -> AnyhowResult> { let result = sqlx::query_as!( - RawLookupUserRecord, + LookupUserForModerationResult, r#" SELECT users.token as `user_token: tokens::tokens::users::UserToken`, username, display_name, - username_is_generated, - is_temporary, - username_is_not_customized, + username_is_generated as `username_is_generated: bool`, + is_temporary as `is_temporary: bool`, + username_is_not_customized as `username_is_not_customized: bool`, email_address, - email_confirmed, - email_is_synthetic, - is_without_password, + email_confirmed as `email_confirmed: bool`, + email_is_synthetic as `email_is_synthetic: bool`, + is_without_password as `is_without_password: bool`, ip_address_creation, ip_address_last_login, maybe_avatar_media_file_token, @@ -127,7 +60,7 @@ LIMIT 1 .await; match result { - Ok(record) => Ok(Some(record.into())), + Ok(record) => Ok(Some(record)), Err(sqlx::Error::RowNotFound) => Ok(None), Err(err) => { warn!("lookup_user_for_moderation_by_token query error: {:?}", err); @@ -143,19 +76,19 @@ pub async fn lookup_user_for_moderation_by_email( let email = email.trim().to_lowercase(); let result = sqlx::query_as!( - RawLookupUserRecord, + LookupUserForModerationResult, r#" SELECT users.token as `user_token: tokens::tokens::users::UserToken`, username, display_name, - username_is_generated, - is_temporary, - username_is_not_customized, + username_is_generated as `username_is_generated: bool`, + is_temporary as `is_temporary: bool`, + username_is_not_customized as `username_is_not_customized: bool`, email_address, - email_confirmed, - email_is_synthetic, - is_without_password, + email_confirmed as `email_confirmed: bool`, + email_is_synthetic as `email_is_synthetic: bool`, + is_without_password as `is_without_password: bool`, ip_address_creation, ip_address_last_login, maybe_avatar_media_file_token, @@ -174,7 +107,7 @@ LIMIT 1 .await; match result { - Ok(record) => Ok(Some(record.into())), + Ok(record) => Ok(Some(record)), Err(sqlx::Error::RowNotFound) => Ok(None), Err(err) => { warn!("lookup_user_for_moderation_by_email query error: {:?}", err); @@ -190,19 +123,19 @@ pub async fn lookup_user_for_moderation_by_username( let username = username.trim().to_lowercase(); let result = sqlx::query_as!( - RawLookupUserRecord, + LookupUserForModerationResult, r#" SELECT users.token as `user_token: tokens::tokens::users::UserToken`, username, display_name, - username_is_generated, - is_temporary, - username_is_not_customized, + username_is_generated as `username_is_generated: bool`, + is_temporary as `is_temporary: bool`, + username_is_not_customized as `username_is_not_customized: bool`, email_address, - email_confirmed, - email_is_synthetic, - is_without_password, + email_confirmed as `email_confirmed: bool`, + email_is_synthetic as `email_is_synthetic: bool`, + is_without_password as `is_without_password: bool`, ip_address_creation, ip_address_last_login, maybe_avatar_media_file_token, @@ -221,7 +154,7 @@ LIMIT 1 .await; match result { - Ok(record) => Ok(Some(record.into())), + Ok(record) => Ok(Some(record)), Err(sqlx::Error::RowNotFound) => Ok(None), Err(err) => { warn!("lookup_user_for_moderation_by_username query error: {:?}", err); diff --git a/crates/schema/database/mysql_queries/src/queries/users/user/get/mod.rs b/crates/schema/database/mysql_queries/src/queries/users/user/get/mod.rs index afef756395..9f0444e3f5 100644 --- a/crates/schema/database/mysql_queries/src/queries/users/user/get/mod.rs +++ b/crates/schema/database/mysql_queries/src/queries/users/user/get/mod.rs @@ -3,3 +3,4 @@ pub mod lookup_user_for_login_by_email; pub mod lookup_user_for_login_by_email_with_transactor; pub mod lookup_user_for_login_by_username; pub mod lookup_user_for_login_result; +pub mod lookup_user_for_moderation; diff --git a/crates/schema/database/mysql_queries/src/queries/users/user_profiles/mod.rs b/crates/schema/database/mysql_queries/src/queries/users/user_profiles/mod.rs index 06a79a5238..0d6cdc7c6c 100644 --- a/crates/schema/database/mysql_queries/src/queries/users/user_profiles/mod.rs +++ b/crates/schema/database/mysql_queries/src/queries/users/user_profiles/mod.rs @@ -2,4 +2,3 @@ pub mod edit_user_profile_as_account_holder; pub mod edit_user_profile_as_mod; pub mod get_user_profile_by_token; pub mod get_user_profile_by_username; -pub mod lookup_user_for_moderation; \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/docs/api_doc.rs b/crates/service/web/storyteller_web/src/docs/api_doc.rs index 93ae367b71..283ab5cba3 100644 --- a/crates/service/web/storyteller_web/src/docs/api_doc.rs +++ b/crates/service/web/storyteller_web/src/docs/api_doc.rs @@ -265,7 +265,7 @@ use artcraft_api_defs::subscriptions::get_session_subscription::*; use artcraft_api_defs::media_file::list_batch_generated_media_files::*; // Handler modules with locally-defined types use crate::http_server::endpoints::moderation::info::moderator_token_info_handler::*; -use crate::http_server::endpoints::moderation::user::moderator_user_lookup_handler::*; +use artcraft_api_defs::moderation::user::user_lookup::*; use crate::http_server::endpoints::webhooks::fal_webhook_handler::*; use crate::http_server::endpoints::image_studio::update_gpt_image_job_status_handler::*; use crate::http_server::endpoints::voice_conversion::enqueue_seed_vc_inference_handler::*; @@ -616,7 +616,6 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux ModeratorUserLookupRequest, ModeratorUserLookupSuccessResponse, ModeratorUserLookupUserDetails, - ModeratorUserLookupError, GptImage1EditImageRequest, GptImage1EditImageImageSize, GptImage1EditImageNumImages, diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user/moderator_user_lookup_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user/moderator_user_lookup_handler.rs index 4c503afb90..3bd9b532e0 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user/moderator_user_lookup_handler.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user/moderator_user_lookup_handler.rs @@ -1,101 +1,28 @@ -use std::fmt; use std::sync::Arc; -use actix_web::error::ResponseError; -use actix_web::http::StatusCode; -use actix_web::{web, HttpRequest, HttpResponse}; +use actix_web::web::Json; +use actix_web::{web, HttpRequest}; use log::warn; -use utoipa::ToSchema; -use mysql_queries::queries::users::user_profiles::lookup_user_for_moderation::{ +use artcraft_api_defs::moderation::user::user_lookup::{ + ModeratorUserLookupRequest, + ModeratorUserLookupSuccessResponse, + ModeratorUserLookupUserDetails, +}; +use mysql_queries::queries::users::user::get::lookup_user_for_moderation::{ lookup_user_for_moderation_by_email, lookup_user_for_moderation_by_token, lookup_user_for_moderation_by_username, - LookupUserForModerationResult, }; use tokens::tokens::users::UserToken; -use crate::http_server::web_utils::serialize_as_json_error::serialize_as_json_error; -use crate::http_server::web_utils::user_session::require_moderator::{require_moderator, RequireModeratorError, UseDatabase}; +use crate::http_server::common_responses::common_web_error::CommonWebError; +use crate::http_server::web_utils::user_session::require_moderator::{require_moderator, UseDatabase}; use crate::state::server_state::ServerState; const LEGACY_USER_TOKEN_PREFIX: &str = "U:"; -#[derive(Deserialize, ToSchema)] -pub struct ModeratorUserLookupRequest { - pub search: String, -} - -#[derive(Serialize, ToSchema)] -pub struct ModeratorUserLookupSuccessResponse { - pub success: bool, - pub maybe_user: Option, -} - -#[derive(Serialize, ToSchema)] -pub struct ModeratorUserLookupUserDetails { - pub token: String, - pub username: String, - pub display_name: String, - pub username_is_generated: bool, - pub is_temporary: bool, - pub username_is_not_customized: bool, - pub email_address: String, - pub email_confirmed: bool, - pub email_is_synthetic: bool, - pub is_without_password: bool, - pub ip_address_creation: String, - pub ip_address_last_login: String, - pub maybe_avatar_media_file_token: Option, - pub email_gravatar_hash: String, -} - -impl From for ModeratorUserLookupUserDetails { - fn from(result: LookupUserForModerationResult) -> Self { - Self { - token: result.user_token.to_string(), - username: result.username, - display_name: result.display_name, - username_is_generated: result.username_is_generated, - is_temporary: result.is_temporary, - username_is_not_customized: result.username_is_not_customized, - email_address: result.email_address, - email_confirmed: result.email_confirmed, - email_is_synthetic: result.email_is_synthetic, - is_without_password: result.is_without_password, - ip_address_creation: result.ip_address_creation, - ip_address_last_login: result.ip_address_last_login, - maybe_avatar_media_file_token: result.maybe_avatar_media_file_token, - email_gravatar_hash: result.email_gravatar_hash, - } - } -} - -#[derive(Debug, Serialize, ToSchema)] -pub enum ModeratorUserLookupError { - ServerError, - Unauthorized, -} - -impl ResponseError for ModeratorUserLookupError { - fn status_code(&self) -> StatusCode { - match *self { - ModeratorUserLookupError::ServerError => StatusCode::INTERNAL_SERVER_ERROR, - ModeratorUserLookupError::Unauthorized => StatusCode::UNAUTHORIZED, - } - } - - fn error_response(&self) -> HttpResponse { - serialize_as_json_error(self) - } -} - -impl fmt::Display for ModeratorUserLookupError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?}", self) - } -} - +/// Moderator User Lookup #[utoipa::path( post, tag = "Moderation", @@ -103,35 +30,27 @@ impl fmt::Display for ModeratorUserLookupError { request_body = ModeratorUserLookupRequest, responses( (status = 200, description = "Success", body = ModeratorUserLookupSuccessResponse), - (status = 401, description = "Unauthorized", body = ModeratorUserLookupError), - (status = 500, description = "Server error", body = ModeratorUserLookupError), + (status = 401, description = "Unauthorized"), + (status = 500, description = "Server error"), ), )] pub async fn moderator_user_lookup_handler( http_request: HttpRequest, - request: web::Json, + request: Json, server_state: web::Data>, -) -> Result { +) -> Result, CommonWebError> { let _user_session = require_moderator(&http_request, &server_state, UseDatabase::Implicit) .await - .map_err(|err| match err { - RequireModeratorError::ServerError => ModeratorUserLookupError::ServerError, - RequireModeratorError::NotAuthorized => ModeratorUserLookupError::Unauthorized, - })?; + .map_err(|_| CommonWebError::NotAuthorized)?; let search = request.search.trim(); if search.is_empty() { - let response = ModeratorUserLookupSuccessResponse { + return Ok(Json(ModeratorUserLookupSuccessResponse { success: true, maybe_user: None, - }; - let body = serde_json::to_string(&response) - .map_err(|_| ModeratorUserLookupError::ServerError)?; - return Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)); + })); } let maybe_result = if search.starts_with(UserToken::token_prefix()) || search.starts_with(LEGACY_USER_TOKEN_PREFIX) { @@ -145,19 +64,28 @@ pub async fn moderator_user_lookup_handler( let maybe_user = maybe_result .map_err(|err| { warn!("moderator_user_lookup error: {:?}", err); - ModeratorUserLookupError::ServerError - })? - .map(ModeratorUserLookupUserDetails::from); + CommonWebError::ServerError + })?; - let response = ModeratorUserLookupSuccessResponse { + let maybe_user_details = maybe_user.map(|user| ModeratorUserLookupUserDetails { + token: user.user_token.to_string(), + username: user.username, + display_name: user.display_name, + username_is_generated: user.username_is_generated, + is_temporary: user.is_temporary, + username_is_not_customized: user.username_is_not_customized, + email_address: user.email_address, + email_confirmed: user.email_confirmed, + email_is_synthetic: user.email_is_synthetic, + is_without_password: user.is_without_password, + ip_address_creation: user.ip_address_creation, + ip_address_last_login: user.ip_address_last_login, + maybe_avatar_media_file_token: user.maybe_avatar_media_file_token, + email_gravatar_hash: user.email_gravatar_hash, + }); + + Ok(Json(ModeratorUserLookupSuccessResponse { success: true, - maybe_user, - }; - - let body = serde_json::to_string(&response) - .map_err(|_| ModeratorUserLookupError::ServerError)?; - - Ok(HttpResponse::Ok() - .content_type("application/json") - .body(body)) + maybe_user: maybe_user_details, + })) }