From f17388c9de5618f38d3d4e47e136fa4d2ca7d6fa Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Thu, 5 Mar 2026 07:18:27 -0500 Subject: [PATCH] more moderator handlers (#1096) --- ...f794aadaea55400527bcfe99f0a5e9678fcf7.json | 80 +++++++++++++++++++ ...145ea297dd9dfebef64c4359cf68e720760f.json} | 6 +- ...34a24f845b0b7395483b316e22dc8ccda3e0.json} | 6 +- ...b9cb4672a0c19e58723964eb265d3187751a.json} | 6 +- .../src/moderation/user/mod.rs | 1 + .../src/moderation/user/user_lookup.rs | 5 +- .../user/user_lookup_by_stripe_customer_id.rs | 26 ++++++ .../user/get/lookup_user_for_moderation.rs | 9 ++- .../lookup_users_by_stripe_customer_id.rs | 52 ++++++++++++ .../queries/users/user_subscriptions/mod.rs | 1 + .../web/storyteller_web/src/docs/api_doc.rs | 5 ++ .../endpoints/moderation/user/mod.rs | 1 + ...er_lookup_by_stripe_customer_id_handler.rs | 69 ++++++++++++++++ .../user/moderator_user_lookup_handler.rs | 2 +- .../application_routes/moderation_routes.rs | 13 ++- 15 files changed, 263 insertions(+), 19 deletions(-) create mode 100644 .sqlx/query-5ac058d9ebc84ffbd94e0997522f794aadaea55400527bcfe99f0a5e9678fcf7.json rename .sqlx/{query-2ed95dcd9f68ddfc3c4fe4885a06827151ee92cc17034dd4683544c34a7d320b.json => query-c59b2997ec10c611ce6ebe4ee813145ea297dd9dfebef64c4359cf68e720760f.json} (90%) rename .sqlx/{query-34455ac40b35a73439f2158ff6e9e14cae5d025508cfa9f76546ee9b48b7fbd4.json => query-d7980481552925c47afb4a2a94ad34a24f845b0b7395483b316e22dc8ccda3e0.json} (90%) rename .sqlx/{query-b983acd5a1f5446f6edfbfe21a0c3d50b0fe0b471d22877e86b690dc0d7e24c8.json => query-ed1ef2c6aeaaf33053807758524bb9cb4672a0c19e58723964eb265d3187751a.json} (90%) create mode 100644 crates/api_clients/artcraft/artcraft_api_defs/src/moderation/user/user_lookup_by_stripe_customer_id.rs create mode 100644 crates/schema/database/mysql_queries/src/queries/users/user_subscriptions/lookup_users_by_stripe_customer_id.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user/moderator_user_lookup_by_stripe_customer_id_handler.rs diff --git a/.sqlx/query-5ac058d9ebc84ffbd94e0997522f794aadaea55400527bcfe99f0a5e9678fcf7.json b/.sqlx/query-5ac058d9ebc84ffbd94e0997522f794aadaea55400527bcfe99f0a5e9678fcf7.json new file mode 100644 index 0000000000..2c735a8213 --- /dev/null +++ b/.sqlx/query-5ac058d9ebc84ffbd94e0997522f794aadaea55400527bcfe99f0a5e9678fcf7.json @@ -0,0 +1,80 @@ +{ + "db_name": "MySQL", + "query": "\nSELECT\n user_subscriptions.subscription_namespace,\n user_subscriptions.maybe_stripe_subscription_id,\n users.token as `user_token: tokens::tokens::users::UserToken`,\n users.email_address,\n users.username,\n users.display_name\nFROM user_subscriptions\nINNER JOIN users ON users.token = user_subscriptions.user_token\nWHERE\n user_subscriptions.maybe_stripe_customer_id = ?\n AND user_subscriptions.deleted_at IS NULL\n AND users.user_deleted_at IS NULL\n AND users.mod_deleted_at IS NULL\nORDER BY user_subscriptions.created_at DESC\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "subscription_namespace", + "type_info": { + "type": "VarString", + "flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE", + "char_set": 224, + "max_size": 128 + } + }, + { + "ordinal": 1, + "name": "maybe_stripe_subscription_id", + "type_info": { + "type": "VarString", + "flags": "UNIQUE_KEY | BINARY", + "char_set": 224, + "max_size": 1020 + } + }, + { + "ordinal": 2, + "name": "user_token: tokens::tokens::users::UserToken", + "type_info": { + "type": "VarString", + "flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE", + "char_set": 224, + "max_size": 128 + } + }, + { + "ordinal": 3, + "name": "email_address", + "type_info": { + "type": "VarString", + "flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE", + "char_set": 224, + "max_size": 1020 + } + }, + { + "ordinal": 4, + "name": "username", + "type_info": { + "type": "VarString", + "flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE", + "char_set": 224, + "max_size": 80 + } + }, + { + "ordinal": 5, + "name": "display_name", + "type_info": { + "type": "VarString", + "flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE", + "char_set": 224, + "max_size": 80 + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + true, + false, + false, + false, + false + ] + }, + "hash": "5ac058d9ebc84ffbd94e0997522f794aadaea55400527bcfe99f0a5e9678fcf7" +} diff --git a/.sqlx/query-2ed95dcd9f68ddfc3c4fe4885a06827151ee92cc17034dd4683544c34a7d320b.json b/.sqlx/query-c59b2997ec10c611ce6ebe4ee813145ea297dd9dfebef64c4359cf68e720760f.json similarity index 90% rename from .sqlx/query-2ed95dcd9f68ddfc3c4fe4885a06827151ee92cc17034dd4683544c34a7d320b.json rename to .sqlx/query-c59b2997ec10c611ce6ebe4ee813145ea297dd9dfebef64c4359cf68e720760f.json index 95aed34f73..bf4accb88b 100644 --- a/.sqlx/query-2ed95dcd9f68ddfc3c4fe4885a06827151ee92cc17034dd4683544c34a7d320b.json +++ b/.sqlx/query-c59b2997ec10c611ce6ebe4ee813145ea297dd9dfebef64c4359cf68e720760f.json @@ -1,6 +1,6 @@ { "db_name": "MySQL", - "query": "\nSELECT\n users.token as `user_token: tokens::tokens::users::UserToken`,\n username,\n display_name,\n username_is_generated as `username_is_generated: bool`,\n is_temporary as `is_temporary: bool`,\n username_is_not_customized as `username_is_not_customized: bool`,\n email_address,\n email_confirmed as `email_confirmed: bool`,\n email_is_synthetic as `email_is_synthetic: bool`,\n is_without_password as `is_without_password: bool`,\n ip_address_creation,\n ip_address_last_login,\n maybe_avatar_media_file_token,\n email_gravatar_hash,\n created_at\nFROM users\nWHERE\n users.token = ?\n AND users.user_deleted_at IS NULL\n AND users.mod_deleted_at IS NULL\nLIMIT 1\n ", + "query": "\nSELECT\n users.token as `user_token: tokens::tokens::users::UserToken`,\n username,\n display_name,\n username_is_generated as `username_is_generated: bool`,\n is_temporary as `is_temporary: bool`,\n username_is_not_customized as `username_is_not_customized: bool`,\n email_address,\n email_confirmed as `email_confirmed: bool`,\n email_is_synthetic as `email_is_synthetic: bool`,\n is_without_password as `is_without_password: bool`,\n ip_address_creation,\n ip_address_last_login,\n maybe_avatar_media_file_token as `maybe_avatar_media_file_token: tokens::tokens::media_files::MediaFileToken`,\n email_gravatar_hash,\n created_at\nFROM users\nWHERE\n users.token = ?\n AND users.user_deleted_at IS NULL\n AND users.mod_deleted_at IS NULL\nLIMIT 1\n ", "describe": { "columns": [ { @@ -125,7 +125,7 @@ }, { "ordinal": 12, - "name": "maybe_avatar_media_file_token", + "name": "maybe_avatar_media_file_token: tokens::tokens::media_files::MediaFileToken", "type_info": { "type": "VarString", "flags": "MULTIPLE_KEY | BINARY", @@ -175,5 +175,5 @@ false ] }, - "hash": "2ed95dcd9f68ddfc3c4fe4885a06827151ee92cc17034dd4683544c34a7d320b" + "hash": "c59b2997ec10c611ce6ebe4ee813145ea297dd9dfebef64c4359cf68e720760f" } diff --git a/.sqlx/query-34455ac40b35a73439f2158ff6e9e14cae5d025508cfa9f76546ee9b48b7fbd4.json b/.sqlx/query-d7980481552925c47afb4a2a94ad34a24f845b0b7395483b316e22dc8ccda3e0.json similarity index 90% rename from .sqlx/query-34455ac40b35a73439f2158ff6e9e14cae5d025508cfa9f76546ee9b48b7fbd4.json rename to .sqlx/query-d7980481552925c47afb4a2a94ad34a24f845b0b7395483b316e22dc8ccda3e0.json index 6e197a3fba..5ddc9b3840 100644 --- a/.sqlx/query-34455ac40b35a73439f2158ff6e9e14cae5d025508cfa9f76546ee9b48b7fbd4.json +++ b/.sqlx/query-d7980481552925c47afb4a2a94ad34a24f845b0b7395483b316e22dc8ccda3e0.json @@ -1,6 +1,6 @@ { "db_name": "MySQL", - "query": "\nSELECT\n users.token as `user_token: tokens::tokens::users::UserToken`,\n username,\n display_name,\n username_is_generated as `username_is_generated: bool`,\n is_temporary as `is_temporary: bool`,\n username_is_not_customized as `username_is_not_customized: bool`,\n email_address,\n email_confirmed as `email_confirmed: bool`,\n email_is_synthetic as `email_is_synthetic: bool`,\n is_without_password as `is_without_password: bool`,\n ip_address_creation,\n ip_address_last_login,\n maybe_avatar_media_file_token,\n email_gravatar_hash,\n created_at\nFROM users\nWHERE\n users.email_address = ?\n AND users.user_deleted_at IS NULL\n AND users.mod_deleted_at IS NULL\nLIMIT 1\n ", + "query": "\nSELECT\n users.token as `user_token: tokens::tokens::users::UserToken`,\n username,\n display_name,\n username_is_generated as `username_is_generated: bool`,\n is_temporary as `is_temporary: bool`,\n username_is_not_customized as `username_is_not_customized: bool`,\n email_address,\n email_confirmed as `email_confirmed: bool`,\n email_is_synthetic as `email_is_synthetic: bool`,\n is_without_password as `is_without_password: bool`,\n ip_address_creation,\n ip_address_last_login,\n maybe_avatar_media_file_token as `maybe_avatar_media_file_token: tokens::tokens::media_files::MediaFileToken`,\n email_gravatar_hash,\n created_at\nFROM users\nWHERE\n users.username = ?\n AND users.user_deleted_at IS NULL\n AND users.mod_deleted_at IS NULL\nLIMIT 1\n ", "describe": { "columns": [ { @@ -125,7 +125,7 @@ }, { "ordinal": 12, - "name": "maybe_avatar_media_file_token", + "name": "maybe_avatar_media_file_token: tokens::tokens::media_files::MediaFileToken", "type_info": { "type": "VarString", "flags": "MULTIPLE_KEY | BINARY", @@ -175,5 +175,5 @@ false ] }, - "hash": "34455ac40b35a73439f2158ff6e9e14cae5d025508cfa9f76546ee9b48b7fbd4" + "hash": "d7980481552925c47afb4a2a94ad34a24f845b0b7395483b316e22dc8ccda3e0" } diff --git a/.sqlx/query-b983acd5a1f5446f6edfbfe21a0c3d50b0fe0b471d22877e86b690dc0d7e24c8.json b/.sqlx/query-ed1ef2c6aeaaf33053807758524bb9cb4672a0c19e58723964eb265d3187751a.json similarity index 90% rename from .sqlx/query-b983acd5a1f5446f6edfbfe21a0c3d50b0fe0b471d22877e86b690dc0d7e24c8.json rename to .sqlx/query-ed1ef2c6aeaaf33053807758524bb9cb4672a0c19e58723964eb265d3187751a.json index 1fc68a0c6b..8bad48be8d 100644 --- a/.sqlx/query-b983acd5a1f5446f6edfbfe21a0c3d50b0fe0b471d22877e86b690dc0d7e24c8.json +++ b/.sqlx/query-ed1ef2c6aeaaf33053807758524bb9cb4672a0c19e58723964eb265d3187751a.json @@ -1,6 +1,6 @@ { "db_name": "MySQL", - "query": "\nSELECT\n users.token as `user_token: tokens::tokens::users::UserToken`,\n username,\n display_name,\n username_is_generated as `username_is_generated: bool`,\n is_temporary as `is_temporary: bool`,\n username_is_not_customized as `username_is_not_customized: bool`,\n email_address,\n email_confirmed as `email_confirmed: bool`,\n email_is_synthetic as `email_is_synthetic: bool`,\n is_without_password as `is_without_password: bool`,\n ip_address_creation,\n ip_address_last_login,\n maybe_avatar_media_file_token,\n email_gravatar_hash,\n created_at\nFROM users\nWHERE\n users.username = ?\n AND users.user_deleted_at IS NULL\n AND users.mod_deleted_at IS NULL\nLIMIT 1\n ", + "query": "\nSELECT\n users.token as `user_token: tokens::tokens::users::UserToken`,\n username,\n display_name,\n username_is_generated as `username_is_generated: bool`,\n is_temporary as `is_temporary: bool`,\n username_is_not_customized as `username_is_not_customized: bool`,\n email_address,\n email_confirmed as `email_confirmed: bool`,\n email_is_synthetic as `email_is_synthetic: bool`,\n is_without_password as `is_without_password: bool`,\n ip_address_creation,\n ip_address_last_login,\n maybe_avatar_media_file_token as `maybe_avatar_media_file_token: tokens::tokens::media_files::MediaFileToken`,\n email_gravatar_hash,\n created_at\nFROM users\nWHERE\n users.email_address = ?\n AND users.user_deleted_at IS NULL\n AND users.mod_deleted_at IS NULL\nLIMIT 1\n ", "describe": { "columns": [ { @@ -125,7 +125,7 @@ }, { "ordinal": 12, - "name": "maybe_avatar_media_file_token", + "name": "maybe_avatar_media_file_token: tokens::tokens::media_files::MediaFileToken", "type_info": { "type": "VarString", "flags": "MULTIPLE_KEY | BINARY", @@ -175,5 +175,5 @@ false ] }, - "hash": "b983acd5a1f5446f6edfbfe21a0c3d50b0fe0b471d22877e86b690dc0d7e24c8" + "hash": "ed1ef2c6aeaaf33053807758524bb9cb4672a0c19e58723964eb265d3187751a" } 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 index 889e777eca..614cd113b3 100644 --- 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 @@ -1 +1,2 @@ pub mod user_lookup; +pub mod user_lookup_by_stripe_customer_id; 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 index 9a6846ab37..d9956df1f9 100644 --- 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 @@ -1,7 +1,8 @@ use serde_derive::{Deserialize, Serialize}; +use tokens::tokens::media_files::MediaFileToken; use utoipa::ToSchema; -pub const MODERATOR_USER_LOOKUP_PATH: &str = "/v1/moderation/user_lookup"; +pub const MODERATOR_USER_LOOKUP_PATH: &str = "/v1/moderation/users/lookup"; #[derive(Deserialize, ToSchema)] pub struct ModeratorUserLookupRequest { @@ -28,6 +29,6 @@ pub struct ModeratorUserLookupUserDetails { pub is_without_password: bool, pub ip_address_creation: String, pub ip_address_last_login: String, - pub maybe_avatar_media_file_token: Option, + pub maybe_avatar_media_file_token: Option, pub email_gravatar_hash: String, } diff --git a/crates/api_clients/artcraft/artcraft_api_defs/src/moderation/user/user_lookup_by_stripe_customer_id.rs b/crates/api_clients/artcraft/artcraft_api_defs/src/moderation/user/user_lookup_by_stripe_customer_id.rs new file mode 100644 index 0000000000..7cae29b8ce --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_api_defs/src/moderation/user/user_lookup_by_stripe_customer_id.rs @@ -0,0 +1,26 @@ +use serde_derive::{Deserialize, Serialize}; +use tokens::tokens::users::UserToken; +use utoipa::ToSchema; + +pub const MODERATOR_USER_LOOKUP_BY_STRIPE_CUSTOMER_ID_PATH: &str = "/v1/moderation/users/lookup_by_stripe_customer_id"; + +#[derive(Deserialize, ToSchema)] +pub struct ModeratorUserLookupByStripeCustomerIdRequest { + pub stripe_customer_id: String, +} + +#[derive(Serialize, ToSchema)] +pub struct ModeratorUserLookupByStripeCustomerIdResponse { + pub success: bool, + pub users: Vec, +} + +#[derive(Serialize, ToSchema)] +pub struct ModeratorUserLookupByStripeCustomerIdEntry { + pub subscription_namespace: String, + pub maybe_stripe_subscription_id: Option, + pub token: UserToken, + pub email_address: String, + pub username: String, + pub display_name: String, +} diff --git a/crates/schema/database/mysql_queries/src/queries/users/user/get/lookup_user_for_moderation.rs b/crates/schema/database/mysql_queries/src/queries/users/user/get/lookup_user_for_moderation.rs index 5ada496e68..675cae3be0 100644 --- a/crates/schema/database/mysql_queries/src/queries/users/user/get/lookup_user_for_moderation.rs +++ b/crates/schema/database/mysql_queries/src/queries/users/user/get/lookup_user_for_moderation.rs @@ -4,6 +4,7 @@ use log::warn; use sqlx::MySqlPool; use errors::AnyhowResult; +use tokens::tokens::media_files::MediaFileToken; use tokens::tokens::users::UserToken; pub struct LookupUserForModerationResult { @@ -19,7 +20,7 @@ pub struct LookupUserForModerationResult { pub is_without_password: bool, pub ip_address_creation: String, pub ip_address_last_login: String, - pub maybe_avatar_media_file_token: Option, + pub maybe_avatar_media_file_token: Option, pub email_gravatar_hash: String, pub created_at: DateTime, } @@ -44,7 +45,7 @@ SELECT is_without_password as `is_without_password: bool`, ip_address_creation, ip_address_last_login, - maybe_avatar_media_file_token, + maybe_avatar_media_file_token as `maybe_avatar_media_file_token: tokens::tokens::media_files::MediaFileToken`, email_gravatar_hash, created_at FROM users @@ -91,7 +92,7 @@ SELECT is_without_password as `is_without_password: bool`, ip_address_creation, ip_address_last_login, - maybe_avatar_media_file_token, + maybe_avatar_media_file_token as `maybe_avatar_media_file_token: tokens::tokens::media_files::MediaFileToken`, email_gravatar_hash, created_at FROM users @@ -138,7 +139,7 @@ SELECT is_without_password as `is_without_password: bool`, ip_address_creation, ip_address_last_login, - maybe_avatar_media_file_token, + maybe_avatar_media_file_token as `maybe_avatar_media_file_token: tokens::tokens::media_files::MediaFileToken`, email_gravatar_hash, created_at FROM users diff --git a/crates/schema/database/mysql_queries/src/queries/users/user_subscriptions/lookup_users_by_stripe_customer_id.rs b/crates/schema/database/mysql_queries/src/queries/users/user_subscriptions/lookup_users_by_stripe_customer_id.rs new file mode 100644 index 0000000000..60c327d3c7 --- /dev/null +++ b/crates/schema/database/mysql_queries/src/queries/users/user_subscriptions/lookup_users_by_stripe_customer_id.rs @@ -0,0 +1,52 @@ +use anyhow::anyhow; +use log::warn; +use sqlx::MySqlPool; + +use errors::AnyhowResult; +use tokens::tokens::users::UserToken; + +pub struct UserByStripeCustomerIdResult { + pub subscription_namespace: String, + pub maybe_stripe_subscription_id: Option, + pub user_token: UserToken, + pub email_address: String, + pub username: String, + pub display_name: String, +} + +pub async fn lookup_users_by_stripe_customer_id( + stripe_customer_id: &str, + mysql_pool: &MySqlPool, +) -> AnyhowResult> { + let results = sqlx::query_as!( + UserByStripeCustomerIdResult, + r#" +SELECT + user_subscriptions.subscription_namespace, + user_subscriptions.maybe_stripe_subscription_id, + users.token as `user_token: tokens::tokens::users::UserToken`, + users.email_address, + users.username, + users.display_name +FROM user_subscriptions +INNER JOIN users ON users.token = user_subscriptions.user_token +WHERE + user_subscriptions.maybe_stripe_customer_id = ? + AND user_subscriptions.deleted_at IS NULL + AND users.user_deleted_at IS NULL + AND users.mod_deleted_at IS NULL +ORDER BY user_subscriptions.created_at DESC + "#, + stripe_customer_id, + ) + .fetch_all(mysql_pool) + .await; + + match results { + Ok(records) => Ok(records), + Err(err) => { + warn!("lookup_users_by_stripe_customer_id query error: {:?}", err); + Err(anyhow!("query error")) + } + } +} diff --git a/crates/schema/database/mysql_queries/src/queries/users/user_subscriptions/mod.rs b/crates/schema/database/mysql_queries/src/queries/users/user_subscriptions/mod.rs index 2cb597ed1e..1a20b3c1af 100644 --- a/crates/schema/database/mysql_queries/src/queries/users/user_subscriptions/mod.rs +++ b/crates/schema/database/mysql_queries/src/queries/users/user_subscriptions/mod.rs @@ -1,4 +1,5 @@ pub mod find_possibly_inactive_first_subscription_for_owner_user; +pub mod lookup_users_by_stripe_customer_id; pub mod find_subscription_for_owner_user; pub mod get_user_subscription_by_stripe_subscription_id; pub mod list_active_user_subscriptions; 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 283ab5cba3..5a2eabd0eb 100644 --- a/crates/service/web/storyteller_web/src/docs/api_doc.rs +++ b/crates/service/web/storyteller_web/src/docs/api_doc.rs @@ -266,6 +266,7 @@ 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 artcraft_api_defs::moderation::user::user_lookup::*; +use artcraft_api_defs::moderation::user::user_lookup_by_stripe_customer_id::*; 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::*; @@ -436,6 +437,7 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux // Moderation crate::http_server::endpoints::moderation::info::moderator_token_info_handler::moderator_get_token_info_handler, crate::http_server::endpoints::moderation::user::moderator_user_lookup_handler::moderator_user_lookup_handler, + crate::http_server::endpoints::moderation::user::moderator_user_lookup_by_stripe_customer_id_handler::moderator_user_lookup_by_stripe_customer_id_handler, // Credits crate::http_server::endpoints::credits::get_session_credits_handler::get_session_credits_handler, // Subscriptions @@ -616,6 +618,9 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux ModeratorUserLookupRequest, ModeratorUserLookupSuccessResponse, ModeratorUserLookupUserDetails, + ModeratorUserLookupByStripeCustomerIdRequest, + ModeratorUserLookupByStripeCustomerIdResponse, + ModeratorUserLookupByStripeCustomerIdEntry, GptImage1EditImageRequest, GptImage1EditImageImageSize, GptImage1EditImageNumImages, diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user/mod.rs index b024cbf035..06d18121cb 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user/mod.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user/mod.rs @@ -1 +1,2 @@ +pub mod moderator_user_lookup_by_stripe_customer_id_handler; pub mod moderator_user_lookup_handler; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user/moderator_user_lookup_by_stripe_customer_id_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user/moderator_user_lookup_by_stripe_customer_id_handler.rs new file mode 100644 index 0000000000..875160d246 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/user/moderator_user_lookup_by_stripe_customer_id_handler.rs @@ -0,0 +1,69 @@ +use std::sync::Arc; + +use actix_web::web::Json; +use actix_web::{web, HttpRequest}; +use log::warn; + +use artcraft_api_defs::moderation::user::user_lookup_by_stripe_customer_id::{ + ModeratorUserLookupByStripeCustomerIdEntry, + ModeratorUserLookupByStripeCustomerIdRequest, + ModeratorUserLookupByStripeCustomerIdResponse, +}; +use mysql_queries::queries::users::user_subscriptions::lookup_users_by_stripe_customer_id::lookup_users_by_stripe_customer_id; + +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; + +/// Moderator User Lookup by Stripe Customer ID +#[utoipa::path( + post, + tag = "Moderation", + path = "/v1/moderation/users/lookup_by_stripe_customer_id", + request_body = ModeratorUserLookupByStripeCustomerIdRequest, + responses( + (status = 200, description = "Success", body = ModeratorUserLookupByStripeCustomerIdResponse), + (status = 401, description = "Unauthorized"), + (status = 500, description = "Server error"), + ), +)] +pub async fn moderator_user_lookup_by_stripe_customer_id_handler( + http_request: HttpRequest, + request: Json, + server_state: web::Data>, +) -> Result, CommonWebError> { + + let _user_session = require_moderator(&http_request, &server_state, UseDatabase::Implicit) + .await + .map_err(|_| CommonWebError::NotAuthorized)?; + + let stripe_customer_id = request.stripe_customer_id.trim(); + + if stripe_customer_id.is_empty() { + return Ok(Json(ModeratorUserLookupByStripeCustomerIdResponse { + success: true, + users: vec![], + })); + } + + let results = lookup_users_by_stripe_customer_id(stripe_customer_id, &server_state.mysql_pool) + .await + .map_err(|err| { + warn!("moderator_user_lookup_by_stripe_customer_id error: {:?}", err); + CommonWebError::ServerError + })?; + + let users = results.into_iter().map(|row| ModeratorUserLookupByStripeCustomerIdEntry { + subscription_namespace: row.subscription_namespace, + maybe_stripe_subscription_id: row.maybe_stripe_subscription_id, + token: row.user_token, + email_address: row.email_address, + username: row.username, + display_name: row.display_name, + }).collect(); + + Ok(Json(ModeratorUserLookupByStripeCustomerIdResponse { + success: true, + users, + })) +} 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 3bd9b532e0..268f9072e6 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 @@ -26,7 +26,7 @@ const LEGACY_USER_TOKEN_PREFIX: &str = "U:"; #[utoipa::path( post, tag = "Moderation", - path = "/v1/moderation/user_lookup", + path = "/v1/moderation/users/lookup", request_body = ModeratorUserLookupRequest, responses( (status = 200, description = "Success", body = ModeratorUserLookupSuccessResponse), diff --git a/crates/service/web/storyteller_web/src/http_server/routes/application_routes/moderation_routes.rs b/crates/service/web/storyteller_web/src/http_server/routes/application_routes/moderation_routes.rs index 6cef88d248..f715c61ca5 100644 --- a/crates/service/web/storyteller_web/src/http_server/routes/application_routes/moderation_routes.rs +++ b/crates/service/web/storyteller_web/src/http_server/routes/application_routes/moderation_routes.rs @@ -24,6 +24,7 @@ use crate::http_server::endpoints::moderation::jobs::get_w2l_inference_queue_cou use crate::http_server::endpoints::moderation::jobs::kill_tts_inference_jobs::kill_tts_inference_jobs_handler; use crate::http_server::endpoints::moderation::user_bans::ban_user::ban_user_handler; use crate::http_server::endpoints::moderation::user_bans::list_banned_users::list_banned_users_handler; +use crate::http_server::endpoints::moderation::user::moderator_user_lookup_by_stripe_customer_id_handler::moderator_user_lookup_by_stripe_customer_id_handler; use crate::http_server::endpoints::moderation::user::moderator_user_lookup_handler::moderator_user_lookup_handler; use crate::http_server::endpoints::moderation::user_feature_flags::edit_user_feature_flags_handler::edit_user_feature_flags_handler; @@ -43,9 +44,15 @@ pub fn add_moderator_routes (app: App) -> App .route(web::post().to(edit_user_feature_flags_handler)) .route(web::head().to(|| HttpResponse::Ok())) ) - .service(web::resource("/user_lookup") - .route(web::post().to(moderator_user_lookup_handler)) - .route(web::head().to(|| HttpResponse::Ok())) + .service(web::scope("/users") + .service(web::resource("/lookup") + .route(web::post().to(moderator_user_lookup_handler)) + .route(web::head().to(|| HttpResponse::Ok())) + ) + .service(web::resource("/lookup_by_stripe_customer_id") + .route(web::post().to(moderator_user_lookup_by_stripe_customer_id_handler)) + .route(web::head().to(|| HttpResponse::Ok())) + ) ) ) .service(web::scope("/moderation")