From ee16d355f94c325ca4bb75226295c8fa94bd46b8 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Mon, 4 May 2026 02:58:39 -0400 Subject: [PATCH] moderator debug (#1473) --- ...0ea338f97091e8bfc6b68aad67416685889af.json | 80 ++++ ...da6ea02c0576cc22ee55e47eb91dd34048ef1.json | 355 ++++++++++++++++++ .../debug_logs/list_debug_logs_for_token.rs | 83 ++++ .../src/queries/debug_logs/mod.rs | 1 + .../src/queries/generic_inference/mod.rs | 1 + .../get_job_by_token_for_moderation.rs | 172 +++++++++ .../generic_inference/moderation/mod.rs | 1 + .../non_unique/debug_logs_event_token.rs | 3 +- .../web/storyteller_web/src/docs/api_doc.rs | 11 + .../endpoints/moderation/debug_logs/mod.rs | 1 + ...ation_list_debug_logs_for_token_handler.rs | 113 ++++++ .../endpoints/moderation/jobs/mod.rs | 1 + .../moderation_get_job_by_token_handler.rs | 153 ++++++++ .../http_server/endpoints/moderation/mod.rs | 1 + .../application_routes/moderation_routes.rs | 12 + 15 files changed, 987 insertions(+), 1 deletion(-) create mode 100644 .sqlx/query-a62b88fec40a11952cc199383090ea338f97091e8bfc6b68aad67416685889af.json create mode 100644 .sqlx/query-e869f065e6f184abbabae04ecd0da6ea02c0576cc22ee55e47eb91dd34048ef1.json create mode 100644 crates/schema/database/mysql_queries/src/queries/debug_logs/list_debug_logs_for_token.rs create mode 100644 crates/schema/database/mysql_queries/src/queries/generic_inference/moderation/get_job_by_token_for_moderation.rs create mode 100644 crates/schema/database/mysql_queries/src/queries/generic_inference/moderation/mod.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/moderation/debug_logs/mod.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/moderation/debug_logs/moderation_list_debug_logs_for_token_handler.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/moderation_get_job_by_token_handler.rs diff --git a/.sqlx/query-a62b88fec40a11952cc199383090ea338f97091e8bfc6b68aad67416685889af.json b/.sqlx/query-a62b88fec40a11952cc199383090ea338f97091e8bfc6b68aad67416685889af.json new file mode 100644 index 0000000000..9e56e640a1 --- /dev/null +++ b/.sqlx/query-a62b88fec40a11952cc199383090ea338f97091e8bfc6b68aad67416685889af.json @@ -0,0 +1,80 @@ +{ + "db_name": "MySQL", + "query": "\nSELECT\n id as `id: u64`,\n event_token as `event_token: tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken`,\n debug_log_type as `debug_log_type: enums::by_table::debug_logs::debug_log_type::DebugLogType`,\n maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n message,\n created_at\nFROM debug_logs\nWHERE event_token = ?\nORDER BY id ASC\nLIMIT ?\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id: u64", + "type_info": { + "type": "LongLong", + "flags": "NOT_NULL | PRIMARY_KEY | UNSIGNED | AUTO_INCREMENT", + "char_set": 63, + "max_size": 20 + } + }, + { + "ordinal": 1, + "name": "event_token: tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken", + "type_info": { + "type": "VarString", + "flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE", + "char_set": 224, + "max_size": 128 + } + }, + { + "ordinal": 2, + "name": "debug_log_type: enums::by_table::debug_logs::debug_log_type::DebugLogType", + "type_info": { + "type": "VarString", + "flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE", + "char_set": 224, + "max_size": 96 + } + }, + { + "ordinal": 3, + "name": "maybe_creator_user_token: tokens::tokens::users::UserToken", + "type_info": { + "type": "VarString", + "flags": "MULTIPLE_KEY | BINARY", + "char_set": 224, + "max_size": 128 + } + }, + { + "ordinal": 4, + "name": "message", + "type_info": { + "type": "Blob", + "flags": "NOT_NULL | BLOB | BINARY | NO_DEFAULT_VALUE", + "char_set": 224, + "max_size": 67108860 + } + }, + { + "ordinal": 5, + "name": "created_at", + "type_info": { + "type": "Datetime", + "flags": "NOT_NULL | MULTIPLE_KEY | BINARY", + "char_set": 63, + "max_size": 19 + } + } + ], + "parameters": { + "Right": 2 + }, + "nullable": [ + false, + false, + false, + true, + false, + false + ] + }, + "hash": "a62b88fec40a11952cc199383090ea338f97091e8bfc6b68aad67416685889af" +} diff --git a/.sqlx/query-e869f065e6f184abbabae04ecd0da6ea02c0576cc22ee55e47eb91dd34048ef1.json b/.sqlx/query-e869f065e6f184abbabae04ecd0da6ea02c0576cc22ee55e47eb91dd34048ef1.json new file mode 100644 index 0000000000..a81caf4e14 --- /dev/null +++ b/.sqlx/query-e869f065e6f184abbabae04ecd0da6ea02c0576cc22ee55e47eb91dd34048ef1.json @@ -0,0 +1,355 @@ +{ + "db_name": "MySQL", + "query": "\nSELECT\n token as `token: tokens::tokens::generic_inference_jobs::InferenceJobToken`,\n uuid_idempotency_token,\n job_type as `maybe_job_type`,\n product_category as `maybe_product_category`,\n inference_category,\n maybe_external_third_party,\n maybe_external_third_party_id,\n maybe_model_type,\n maybe_model_token,\n maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n maybe_inference_args,\n maybe_download_url,\n on_success_result_entity_type,\n on_success_result_entity_token,\n on_success_result_batch_token,\n maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n maybe_creator_anonymous_visitor_token,\n creator_ip_address,\n creator_set_visibility,\n maybe_wallet_ledger_entry_token,\n maybe_debug_log_event_token as `maybe_debug_log_event_token: tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken`,\n status,\n attempt_count as `attempt_count: u16`,\n failure_reason,\n internal_debugging_failure_reason,\n frontend_failure_category,\n maybe_routing_tag,\n assigned_worker,\n assigned_cluster,\n created_at,\n updated_at\nFROM generic_inference_jobs\nWHERE token = ?\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "token: tokens::tokens::generic_inference_jobs::InferenceJobToken", + "type_info": { + "type": "VarString", + "flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE", + "char_set": 224, + "max_size": 128 + } + }, + { + "ordinal": 1, + "name": "uuid_idempotency_token", + "type_info": { + "type": "VarString", + "flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE", + "char_set": 224, + "max_size": 144 + } + }, + { + "ordinal": 2, + "name": "maybe_job_type", + "type_info": { + "type": "VarString", + "flags": "MULTIPLE_KEY | BINARY", + "char_set": 224, + "max_size": 128 + } + }, + { + "ordinal": 3, + "name": "maybe_product_category", + "type_info": { + "type": "VarString", + "flags": "MULTIPLE_KEY | BINARY", + "char_set": 224, + "max_size": 128 + } + }, + { + "ordinal": 4, + "name": "inference_category", + "type_info": { + "type": "VarString", + "flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE", + "char_set": 224, + "max_size": 128 + } + }, + { + "ordinal": 5, + "name": "maybe_external_third_party", + "type_info": { + "type": "VarString", + "flags": "MULTIPLE_KEY | BINARY", + "char_set": 224, + "max_size": 64 + } + }, + { + "ordinal": 6, + "name": "maybe_external_third_party_id", + "type_info": { + "type": "VarString", + "flags": "MULTIPLE_KEY | BINARY", + "char_set": 224, + "max_size": 256 + } + }, + { + "ordinal": 7, + "name": "maybe_model_type", + "type_info": { + "type": "VarString", + "flags": "MULTIPLE_KEY | BINARY", + "char_set": 224, + "max_size": 128 + } + }, + { + "ordinal": 8, + "name": "maybe_model_token", + "type_info": { + "type": "VarString", + "flags": "MULTIPLE_KEY | BINARY", + "char_set": 224, + "max_size": 128 + } + }, + { + "ordinal": 9, + "name": "maybe_prompt_token: tokens::tokens::prompts::PromptToken", + "type_info": { + "type": "VarString", + "flags": "BINARY", + "char_set": 224, + "max_size": 128 + } + }, + { + "ordinal": 10, + "name": "maybe_inference_args", + "type_info": { + "type": "Blob", + "flags": "BLOB | BINARY", + "char_set": 224, + "max_size": 262140 + } + }, + { + "ordinal": 11, + "name": "maybe_download_url", + "type_info": { + "type": "VarString", + "flags": "BINARY", + "char_set": 224, + "max_size": 4096 + } + }, + { + "ordinal": 12, + "name": "on_success_result_entity_type", + "type_info": { + "type": "VarString", + "flags": "BINARY", + "char_set": 224, + "max_size": 128 + } + }, + { + "ordinal": 13, + "name": "on_success_result_entity_token", + "type_info": { + "type": "VarString", + "flags": "MULTIPLE_KEY | BINARY", + "char_set": 224, + "max_size": 128 + } + }, + { + "ordinal": 14, + "name": "on_success_result_batch_token", + "type_info": { + "type": "VarString", + "flags": "BINARY", + "char_set": 224, + "max_size": 128 + } + }, + { + "ordinal": 15, + "name": "maybe_creator_user_token: tokens::tokens::users::UserToken", + "type_info": { + "type": "VarString", + "flags": "MULTIPLE_KEY | BINARY", + "char_set": 224, + "max_size": 128 + } + }, + { + "ordinal": 16, + "name": "maybe_creator_anonymous_visitor_token", + "type_info": { + "type": "VarString", + "flags": "MULTIPLE_KEY | BINARY", + "char_set": 224, + "max_size": 128 + } + }, + { + "ordinal": 17, + "name": "creator_ip_address", + "type_info": { + "type": "VarString", + "flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE", + "char_set": 224, + "max_size": 160 + } + }, + { + "ordinal": 18, + "name": "creator_set_visibility", + "type_info": { + "type": "String", + "flags": "NOT_NULL | BINARY | ENUM", + "char_set": 224, + "max_size": 28 + } + }, + { + "ordinal": 19, + "name": "maybe_wallet_ledger_entry_token", + "type_info": { + "type": "VarString", + "flags": "BINARY", + "char_set": 224, + "max_size": 128 + } + }, + { + "ordinal": 20, + "name": "maybe_debug_log_event_token: tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken", + "type_info": { + "type": "VarString", + "flags": "BINARY", + "char_set": 224, + "max_size": 128 + } + }, + { + "ordinal": 21, + "name": "status", + "type_info": { + "type": "String", + "flags": "NOT_NULL | MULTIPLE_KEY | BINARY | ENUM", + "char_set": 224, + "max_size": 76 + } + }, + { + "ordinal": 22, + "name": "attempt_count: u16", + "type_info": { + "type": "Short", + "flags": "NOT_NULL | UNSIGNED", + "char_set": 63, + "max_size": 5 + } + }, + { + "ordinal": 23, + "name": "failure_reason", + "type_info": { + "type": "VarString", + "flags": "BINARY", + "char_set": 224, + "max_size": 2048 + } + }, + { + "ordinal": 24, + "name": "internal_debugging_failure_reason", + "type_info": { + "type": "VarString", + "flags": "BINARY", + "char_set": 224, + "max_size": 2048 + } + }, + { + "ordinal": 25, + "name": "frontend_failure_category", + "type_info": { + "type": "VarString", + "flags": "BINARY", + "char_set": 224, + "max_size": 128 + } + }, + { + "ordinal": 26, + "name": "maybe_routing_tag", + "type_info": { + "type": "VarString", + "flags": "MULTIPLE_KEY | BINARY", + "char_set": 224, + "max_size": 256 + } + }, + { + "ordinal": 27, + "name": "assigned_worker", + "type_info": { + "type": "VarString", + "flags": "BINARY", + "char_set": 224, + "max_size": 512 + } + }, + { + "ordinal": 28, + "name": "assigned_cluster", + "type_info": { + "type": "VarString", + "flags": "BINARY", + "char_set": 224, + "max_size": 512 + } + }, + { + "ordinal": 29, + "name": "created_at", + "type_info": { + "type": "Timestamp", + "flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP", + "char_set": 63, + "max_size": 19 + } + }, + { + "ordinal": 30, + "name": "updated_at", + "type_info": { + "type": "Timestamp", + "flags": "NOT_NULL | BINARY | TIMESTAMP | ON_UPDATE_NOW", + "char_set": 63, + "max_size": 19 + } + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false, + true, + true, + false, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + false, + false, + true, + true, + false, + false, + true, + true, + true, + true, + true, + true, + false, + false + ] + }, + "hash": "e869f065e6f184abbabae04ecd0da6ea02c0576cc22ee55e47eb91dd34048ef1" +} diff --git a/crates/schema/database/mysql_queries/src/queries/debug_logs/list_debug_logs_for_token.rs b/crates/schema/database/mysql_queries/src/queries/debug_logs/list_debug_logs_for_token.rs new file mode 100644 index 0000000000..e561ea4cfc --- /dev/null +++ b/crates/schema/database/mysql_queries/src/queries/debug_logs/list_debug_logs_for_token.rs @@ -0,0 +1,83 @@ +use std::marker::PhantomData; + +use chrono::{DateTime, NaiveDateTime, Utc}; +use sqlx::{Executor, MySql}; + +use enums::by_table::debug_logs::debug_log_type::DebugLogType; +use tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken; +use tokens::tokens::users::UserToken; + +const DEFAULT_LIMIT: u32 = 50; + +pub struct ListDebugLogsForTokenArgs<'e, 'c, E> +where + E: 'e + Executor<'c, Database = MySql>, +{ + pub event_token: &'e DebugLogEventToken, + pub limit: Option, + pub mysql_executor: E, + pub phantom: PhantomData<&'c E>, +} + +#[derive(Debug)] +pub struct DebugLogRow { + pub id: u64, + pub event_token: DebugLogEventToken, + pub debug_log_type: DebugLogType, + pub maybe_creator_user_token: Option, + pub message: String, + pub created_at: DateTime, +} + +#[derive(Debug)] +struct RawDebugLogRow { + id: u64, + event_token: DebugLogEventToken, + debug_log_type: DebugLogType, + maybe_creator_user_token: Option, + message: String, + created_at: NaiveDateTime, +} + +pub async fn list_debug_logs_for_token<'e, 'c: 'e, E>( + args: ListDebugLogsForTokenArgs<'e, 'c, E>, +) -> Result, sqlx::Error> +where + E: 'e + Executor<'c, Database = MySql>, +{ + let limit = args.limit.unwrap_or(DEFAULT_LIMIT).min(200) as i64; + + let rows = sqlx::query_as!( + RawDebugLogRow, + r#" +SELECT + id as `id: u64`, + event_token as `event_token: tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken`, + debug_log_type as `debug_log_type: enums::by_table::debug_logs::debug_log_type::DebugLogType`, + maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`, + message, + created_at +FROM debug_logs +WHERE event_token = ? +ORDER BY id ASC +LIMIT ? + "#, + args.event_token.as_str(), + limit, + ) + .fetch_all(args.mysql_executor) + .await?; + + let results = rows.into_iter().map(|row| { + DebugLogRow { + id: row.id, + event_token: row.event_token, + debug_log_type: row.debug_log_type, + maybe_creator_user_token: row.maybe_creator_user_token, + message: row.message, + created_at: row.created_at.and_utc(), + } + }).collect(); + + Ok(results) +} diff --git a/crates/schema/database/mysql_queries/src/queries/debug_logs/mod.rs b/crates/schema/database/mysql_queries/src/queries/debug_logs/mod.rs index 946a135b54..5554168d12 100644 --- a/crates/schema/database/mysql_queries/src/queries/debug_logs/mod.rs +++ b/crates/schema/database/mysql_queries/src/queries/debug_logs/mod.rs @@ -1,2 +1,3 @@ pub mod bulk_insert_debug_logs; pub mod insert_debug_log; +pub mod list_debug_logs_for_token; diff --git a/crates/schema/database/mysql_queries/src/queries/generic_inference/mod.rs b/crates/schema/database/mysql_queries/src/queries/generic_inference/mod.rs index 4946e447fa..43c4342399 100644 --- a/crates/schema/database/mysql_queries/src/queries/generic_inference/mod.rs +++ b/crates/schema/database/mysql_queries/src/queries/generic_inference/mod.rs @@ -1,5 +1,6 @@ pub mod fal; pub mod job; +pub mod moderation; pub mod seedance2pro; pub mod web; pub mod worldlabs; diff --git a/crates/schema/database/mysql_queries/src/queries/generic_inference/moderation/get_job_by_token_for_moderation.rs b/crates/schema/database/mysql_queries/src/queries/generic_inference/moderation/get_job_by_token_for_moderation.rs new file mode 100644 index 0000000000..e3e2deeaa1 --- /dev/null +++ b/crates/schema/database/mysql_queries/src/queries/generic_inference/moderation/get_job_by_token_for_moderation.rs @@ -0,0 +1,172 @@ +use std::marker::PhantomData; + +use chrono::{DateTime, Utc}; +use sqlx::{Executor, MySql}; + +use tokens::tokens::generic_inference_jobs::InferenceJobToken; +use tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken; +use tokens::tokens::prompts::PromptToken; +use tokens::tokens::users::UserToken; + +pub struct GetJobByTokenForModerationArgs<'e, 'c, E> +where + E: 'e + Executor<'c, Database = MySql>, +{ + pub job_token: &'e InferenceJobToken, + pub mysql_executor: E, + pub phantom: PhantomData<&'c E>, +} + +#[derive(Debug)] +pub struct ModerationJobDetails { + pub token: InferenceJobToken, + pub uuid_idempotency_token: String, + pub maybe_job_type: Option, + pub maybe_product_category: Option, + pub inference_category: String, + pub maybe_external_third_party: Option, + pub maybe_external_third_party_id: Option, + pub maybe_model_type: Option, + pub maybe_model_token: Option, + pub maybe_prompt_token: Option, + pub maybe_inference_args: Option, + pub maybe_download_url: Option, + pub on_success_result_entity_type: Option, + pub on_success_result_entity_token: Option, + pub on_success_result_batch_token: Option, + pub maybe_creator_user_token: Option, + pub maybe_creator_anonymous_visitor_token: Option, + pub creator_ip_address: String, + pub creator_set_visibility: String, + pub maybe_wallet_ledger_entry_token: Option, + pub maybe_debug_log_event_token: Option, + pub status: String, + pub attempt_count: u16, + pub failure_reason: Option, + pub internal_debugging_failure_reason: Option, + pub frontend_failure_category: Option, + pub maybe_routing_tag: Option, + pub assigned_worker: Option, + pub assigned_cluster: Option, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +#[derive(Debug)] +struct RawRow { + token: InferenceJobToken, + uuid_idempotency_token: String, + maybe_job_type: Option, + maybe_product_category: Option, + inference_category: String, + maybe_external_third_party: Option, + maybe_external_third_party_id: Option, + maybe_model_type: Option, + maybe_model_token: Option, + maybe_prompt_token: Option, + maybe_inference_args: Option, + maybe_download_url: Option, + on_success_result_entity_type: Option, + on_success_result_entity_token: Option, + on_success_result_batch_token: Option, + maybe_creator_user_token: Option, + maybe_creator_anonymous_visitor_token: Option, + creator_ip_address: String, + creator_set_visibility: String, + maybe_wallet_ledger_entry_token: Option, + maybe_debug_log_event_token: Option, + status: String, + attempt_count: u16, + failure_reason: Option, + internal_debugging_failure_reason: Option, + frontend_failure_category: Option, + maybe_routing_tag: Option, + assigned_worker: Option, + assigned_cluster: Option, + created_at: DateTime, + updated_at: DateTime, +} + +pub async fn get_job_by_token_for_moderation<'e, 'c: 'e, E>( + args: GetJobByTokenForModerationArgs<'e, 'c, E>, +) -> Result, sqlx::Error> +where + E: 'e + Executor<'c, Database = MySql>, +{ + let maybe_row = sqlx::query_as!( + RawRow, + r#" +SELECT + token as `token: tokens::tokens::generic_inference_jobs::InferenceJobToken`, + uuid_idempotency_token, + job_type as `maybe_job_type`, + product_category as `maybe_product_category`, + inference_category, + maybe_external_third_party, + maybe_external_third_party_id, + maybe_model_type, + maybe_model_token, + maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`, + maybe_inference_args, + maybe_download_url, + on_success_result_entity_type, + on_success_result_entity_token, + on_success_result_batch_token, + maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`, + maybe_creator_anonymous_visitor_token, + creator_ip_address, + creator_set_visibility, + maybe_wallet_ledger_entry_token, + maybe_debug_log_event_token as `maybe_debug_log_event_token: tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken`, + status, + attempt_count as `attempt_count: u16`, + failure_reason, + internal_debugging_failure_reason, + frontend_failure_category, + maybe_routing_tag, + assigned_worker, + assigned_cluster, + created_at, + updated_at +FROM generic_inference_jobs +WHERE token = ? + "#, + args.job_token.as_str(), + ) + .fetch_optional(args.mysql_executor) + .await?; + + Ok(maybe_row.map(|row| ModerationJobDetails { + token: row.token, + uuid_idempotency_token: row.uuid_idempotency_token, + maybe_job_type: row.maybe_job_type, + maybe_product_category: row.maybe_product_category, + inference_category: row.inference_category, + maybe_external_third_party: row.maybe_external_third_party, + maybe_external_third_party_id: row.maybe_external_third_party_id, + maybe_model_type: row.maybe_model_type, + maybe_model_token: row.maybe_model_token, + maybe_prompt_token: row.maybe_prompt_token, + maybe_inference_args: row.maybe_inference_args, + maybe_download_url: row.maybe_download_url, + on_success_result_entity_type: row.on_success_result_entity_type, + on_success_result_entity_token: row.on_success_result_entity_token, + on_success_result_batch_token: row.on_success_result_batch_token, + maybe_creator_user_token: row.maybe_creator_user_token, + maybe_creator_anonymous_visitor_token: row.maybe_creator_anonymous_visitor_token, + creator_ip_address: row.creator_ip_address, + creator_set_visibility: row.creator_set_visibility, + maybe_wallet_ledger_entry_token: row.maybe_wallet_ledger_entry_token, + maybe_debug_log_event_token: row.maybe_debug_log_event_token, + status: row.status, + attempt_count: row.attempt_count, + failure_reason: row.failure_reason, + internal_debugging_failure_reason: row.internal_debugging_failure_reason, + frontend_failure_category: row.frontend_failure_category, + maybe_routing_tag: row.maybe_routing_tag, + assigned_worker: row.assigned_worker, + assigned_cluster: row.assigned_cluster, + created_at: row.created_at, + updated_at: row.updated_at, + })) +} diff --git a/crates/schema/database/mysql_queries/src/queries/generic_inference/moderation/mod.rs b/crates/schema/database/mysql_queries/src/queries/generic_inference/moderation/mod.rs new file mode 100644 index 0000000000..9317c1963f --- /dev/null +++ b/crates/schema/database/mysql_queries/src/queries/generic_inference/moderation/mod.rs @@ -0,0 +1 @@ +pub mod get_job_by_token_for_moderation; diff --git a/crates/schema/public/tokens/src/tokens/non_unique/debug_logs_event_token.rs b/crates/schema/public/tokens/src/tokens/non_unique/debug_logs_event_token.rs index f325a36abc..a7428599c6 100644 --- a/crates/schema/public/tokens/src/tokens/non_unique/debug_logs_event_token.rs +++ b/crates/schema/public/tokens/src/tokens/non_unique/debug_logs_event_token.rs @@ -1,11 +1,12 @@ use serde::Deserialize; use serde::Serialize; +use utoipa::ToSchema; use crate::prefixes::TokenPrefix; /// A non-unique event token for the `debug_logs` table. /// Multiple log rows can share the same event token. -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize, ToSchema)] #[cfg_attr(feature = "database", derive(sqlx::Type))] #[cfg_attr(feature = "database", sqlx(transparent))] pub struct DebugLogEventToken(pub String); 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 35210bc1d7..bf9cf2a95b 100644 --- a/crates/service/web/storyteller_web/src/docs/api_doc.rs +++ b/crates/service/web/storyteller_web/src/docs/api_doc.rs @@ -85,6 +85,8 @@ use crate::http_server::endpoints::media_files::upsert_upload::write_engine_asse use crate::http_server::endpoints::media_files::upsert_upload::write_error::MediaFileWriteError; use crate::http_server::endpoints::media_files::upsert_upload::write_scene_file::write_scene_file_media_file_handler::*; use crate::http_server::endpoints::model_download::enqueue_gptsovits_model_download_handler::*; +use crate::http_server::endpoints::moderation::debug_logs::moderation_list_debug_logs_for_token_handler::*; +use crate::http_server::endpoints::moderation::jobs::moderation_get_job_by_token_handler::*; use crate::http_server::endpoints::moderation::user_feature_flags::moderator_edit_user_feature_flags_handler::*; use crate::http_server::endpoints::moderation::user_feature_flags::moderator_list_all_available_user_feature_flags_handler::*; use crate::http_server::endpoints::moderation::user_feature_flags::moderator_list_user_feature_flags_handler::*; @@ -519,6 +521,8 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux crate::http_server::endpoints::moderation::wallets::moderator_add_banked_balance_to_wallet_handler::moderator_add_banked_balance_to_wallet_handler, crate::http_server::endpoints::moderation::wallets::moderator_create_wallet_for_user_handler::moderator_create_wallet_for_user_handler, crate::http_server::endpoints::moderation::wallets::moderator_get_wallet_handler::moderator_get_wallet_handler, + crate::http_server::endpoints::moderation::debug_logs::moderation_list_debug_logs_for_token_handler::moderation_list_debug_logs_for_token_handler, + crate::http_server::endpoints::moderation::jobs::moderation_get_job_by_token_handler::moderation_get_job_by_token_handler, crate::http_server::endpoints::moderation::staff_audit_logs::moderator_list_staff_audit_logs_handler::moderator_list_staff_audit_logs_handler, crate::http_server::endpoints::moderation::user_bans::moderation_ban_user_handler::moderation_ban_user_handler, crate::http_server::endpoints::moderation::user_sessions::moderator_list_user_session_impersonation_requests_for_user_handler::moderator_list_user_session_impersonation_requests_for_user_handler, @@ -909,6 +913,13 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux ListAllImpersonationRequestsQueryParams, ListAllImpersonationRequestsSuccessResponse, ListImpersonationRequestsPathInfo, + GetJobByTokenPathInfo, + GetJobByTokenSuccessResponse, + ModerationJobResponse, + ListDebugLogsPathInfo, + ListDebugLogsQueryParams, + ListDebugLogsSuccessResponse, + DebugLogEntry, ListStaffAuditLogsQueryParams, ListStaffAuditLogsSuccessResponse, ListImpersonationRequestsQueryParams, diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/debug_logs/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/debug_logs/mod.rs new file mode 100644 index 0000000000..d4523970f7 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/debug_logs/mod.rs @@ -0,0 +1 @@ +pub mod moderation_list_debug_logs_for_token_handler; diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/debug_logs/moderation_list_debug_logs_for_token_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/debug_logs/moderation_list_debug_logs_for_token_handler.rs new file mode 100644 index 0000000000..1a56e26ec0 --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/debug_logs/moderation_list_debug_logs_for_token_handler.rs @@ -0,0 +1,113 @@ +use std::sync::Arc; + +use actix_web::web::Json; +use actix_web::{web, HttpRequest}; +use chrono::{DateTime, Utc}; +use log::warn; +use serde_derive::{Deserialize, Serialize}; +use utoipa::{IntoParams, ToSchema}; + +use enums::by_table::debug_logs::debug_log_type::DebugLogType; +use mysql_queries::queries::debug_logs::list_debug_logs_for_token::{ + list_debug_logs_for_token, ListDebugLogsForTokenArgs, +}; +use tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken; +use tokens::tokens::users::UserToken; + +use crate::http_server::common_responses::advanced_common_web_error::AdvancedCommonWebError; +use crate::http_server::web_utils::user_session::require_moderator::{ + require_moderator, UseDatabase, +}; +use crate::state::server_state::ServerState; + +// ── Path params ── + +#[derive(Deserialize, ToSchema)] +pub struct ListDebugLogsPathInfo { + pub token: DebugLogEventToken, +} + +// ── Query params ── + +#[derive(Deserialize, IntoParams, ToSchema)] +pub struct ListDebugLogsQueryParams { + pub limit: Option, +} + +// ── Response ── + +#[derive(Serialize, ToSchema)] +pub struct ListDebugLogsSuccessResponse { + pub success: bool, + pub debug_logs: Vec, +} + +#[derive(Serialize, ToSchema)] +pub struct DebugLogEntry { + pub id: u64, + pub event_token: DebugLogEventToken, + pub debug_log_type: DebugLogType, + pub maybe_creator_user_token: Option, + pub message: String, + pub created_at: DateTime, +} + +// ── Handler ── + +#[utoipa::path( + get, + tag = "Moderation", + path = "/v1/moderation/debug_logs/list/{token}", + params( + ("token" = String, Path, description = "Debug log event token"), + ListDebugLogsQueryParams, + ), + responses( + (status = 200, description = "Success", body = ListDebugLogsSuccessResponse), + (status = 401, description = "Unauthorized"), + (status = 500, description = "Server error"), + ), +)] +pub async fn moderation_list_debug_logs_for_token_handler( + http_request: HttpRequest, + path: web::Path, + query: web::Query, + server_state: web::Data>, +) -> Result, AdvancedCommonWebError> { + let _user_session = require_moderator( + &http_request, + &server_state, + UseDatabase::GrabNewConnection, + ).await.map_err(|err| { + warn!("Moderator check failed: {:?}", err); + AdvancedCommonWebError::NotAuthorized + })?; + + let mut mysql_connection = server_state.mysql_pool.acquire().await?; + + let rows = list_debug_logs_for_token(ListDebugLogsForTokenArgs { + event_token: &path.token, + limit: query.limit, + mysql_executor: &mut *mysql_connection, + phantom: Default::default(), + }).await.map_err(|err| { + warn!("Error listing debug logs for token {}: {:?}", path.token, err); + AdvancedCommonWebError::from_error(err) + })?; + + let debug_logs: Vec = rows.into_iter().map(|row| { + DebugLogEntry { + id: row.id, + event_token: row.event_token, + debug_log_type: row.debug_log_type, + maybe_creator_user_token: row.maybe_creator_user_token, + message: row.message, + created_at: row.created_at, + } + }).collect(); + + Ok(Json(ListDebugLogsSuccessResponse { + success: true, + debug_logs, + })) +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/mod.rs index afe73693d2..096b56dd65 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/mod.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/mod.rs @@ -1,4 +1,5 @@ pub mod get_tts_inference_queue_count; pub mod get_w2l_inference_queue_count; pub mod kill_tts_inference_jobs; +pub mod moderation_get_job_by_token_handler; pub mod user; \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/moderation_get_job_by_token_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/moderation_get_job_by_token_handler.rs new file mode 100644 index 0000000000..6656216fcd --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/jobs/moderation_get_job_by_token_handler.rs @@ -0,0 +1,153 @@ +use std::sync::Arc; + +use actix_web::web::Json; +use actix_web::{web, HttpRequest}; +use chrono::{DateTime, Utc}; +use log::warn; +use serde_derive::Serialize; +use utoipa::ToSchema; + +use mysql_queries::queries::generic_inference::moderation::get_job_by_token_for_moderation::{ + get_job_by_token_for_moderation, GetJobByTokenForModerationArgs, +}; +use tokens::tokens::generic_inference_jobs::InferenceJobToken; +use tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken; +use tokens::tokens::prompts::PromptToken; +use tokens::tokens::users::UserToken; + +use crate::http_server::common_responses::advanced_common_web_error::AdvancedCommonWebError; +use crate::http_server::web_utils::user_session::require_moderator::{ + require_moderator, UseDatabase, +}; +use crate::state::server_state::ServerState; + +// ── Path params ── + +#[derive(serde_derive::Deserialize, ToSchema)] +pub struct GetJobByTokenPathInfo { + pub token: InferenceJobToken, +} + +// ── Response ── + +#[derive(Serialize, ToSchema)] +pub struct GetJobByTokenSuccessResponse { + pub success: bool, + pub job: ModerationJobResponse, +} + +#[derive(Serialize, ToSchema)] +pub struct ModerationJobResponse { + pub token: InferenceJobToken, + pub uuid_idempotency_token: String, + pub maybe_job_type: Option, + pub maybe_product_category: Option, + pub inference_category: String, + pub maybe_external_third_party: Option, + pub maybe_external_third_party_id: Option, + pub maybe_model_type: Option, + pub maybe_model_token: Option, + pub maybe_prompt_token: Option, + pub maybe_inference_args: Option, + pub maybe_download_url: Option, + pub on_success_result_entity_type: Option, + pub on_success_result_entity_token: Option, + pub on_success_result_batch_token: Option, + pub maybe_creator_user_token: Option, + pub maybe_creator_anonymous_visitor_token: Option, + pub creator_ip_address: String, + pub creator_set_visibility: String, + pub maybe_wallet_ledger_entry_token: Option, + pub maybe_debug_log_event_token: Option, + pub status: String, + pub attempt_count: u16, + pub failure_reason: Option, + pub internal_debugging_failure_reason: Option, + pub frontend_failure_category: Option, + pub maybe_routing_tag: Option, + pub assigned_worker: Option, + pub assigned_cluster: Option, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +// ── Handler ── + +#[utoipa::path( + get, + tag = "Moderation", + path = "/v1/moderation/job/{token}", + params( + ("token" = String, Path, description = "Inference job token"), + ), + responses( + (status = 200, description = "Success", body = GetJobByTokenSuccessResponse), + (status = 401, description = "Unauthorized"), + (status = 404, description = "Not found"), + (status = 500, description = "Server error"), + ), +)] +pub async fn moderation_get_job_by_token_handler( + http_request: HttpRequest, + path: web::Path, + server_state: web::Data>, +) -> Result, AdvancedCommonWebError> { + let _user_session = require_moderator( + &http_request, + &server_state, + UseDatabase::GrabNewConnection, + ).await.map_err(|err| { + warn!("Moderator check failed: {:?}", err); + AdvancedCommonWebError::NotAuthorized + })?; + + let mut mysql_connection = server_state.mysql_pool.acquire().await?; + + let job = get_job_by_token_for_moderation(GetJobByTokenForModerationArgs { + job_token: &path.token, + mysql_executor: &mut *mysql_connection, + phantom: Default::default(), + }).await.map_err(|err| { + warn!("Error fetching job by token {}: {:?}", path.token, err); + AdvancedCommonWebError::from_error(err) + })?; + + let job = job.ok_or(AdvancedCommonWebError::NotFound)?; + + Ok(Json(GetJobByTokenSuccessResponse { + success: true, + job: ModerationJobResponse { + token: job.token, + uuid_idempotency_token: job.uuid_idempotency_token, + maybe_job_type: job.maybe_job_type, + maybe_product_category: job.maybe_product_category, + inference_category: job.inference_category, + maybe_external_third_party: job.maybe_external_third_party, + maybe_external_third_party_id: job.maybe_external_third_party_id, + maybe_model_type: job.maybe_model_type, + maybe_model_token: job.maybe_model_token, + maybe_prompt_token: job.maybe_prompt_token, + maybe_inference_args: job.maybe_inference_args, + maybe_download_url: job.maybe_download_url, + on_success_result_entity_type: job.on_success_result_entity_type, + on_success_result_entity_token: job.on_success_result_entity_token, + on_success_result_batch_token: job.on_success_result_batch_token, + maybe_creator_user_token: job.maybe_creator_user_token, + maybe_creator_anonymous_visitor_token: job.maybe_creator_anonymous_visitor_token, + creator_ip_address: job.creator_ip_address, + creator_set_visibility: job.creator_set_visibility, + maybe_wallet_ledger_entry_token: job.maybe_wallet_ledger_entry_token, + maybe_debug_log_event_token: job.maybe_debug_log_event_token, + status: job.status, + attempt_count: job.attempt_count, + failure_reason: job.failure_reason, + internal_debugging_failure_reason: job.internal_debugging_failure_reason, + frontend_failure_category: job.frontend_failure_category, + maybe_routing_tag: job.maybe_routing_tag, + assigned_worker: job.assigned_worker, + assigned_cluster: job.assigned_cluster, + created_at: job.created_at, + updated_at: job.updated_at, + }, + })) +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/mod.rs index beafc6be82..10042dd601 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/mod.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/moderation/mod.rs @@ -1,4 +1,5 @@ pub mod alerts; +pub mod debug_logs; pub mod info; pub mod ip_bans; pub mod jobs; 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 f802bf59f7..6dd4c1bae5 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 @@ -36,6 +36,8 @@ use crate::http_server::endpoints::moderation::user::moderator_user_lookup_by_st use crate::http_server::endpoints::moderation::user::moderator_user_lookup_handler::moderator_user_lookup_handler; use crate::http_server::endpoints::moderation::alerts::moderation_send_alert_handler::moderation_send_alert_handler; use crate::http_server::endpoints::moderation::staff_audit_logs::moderator_list_staff_audit_logs_handler::moderator_list_staff_audit_logs_handler; +use crate::http_server::endpoints::moderation::debug_logs::moderation_list_debug_logs_for_token_handler::moderation_list_debug_logs_for_token_handler; +use crate::http_server::endpoints::moderation::jobs::moderation_get_job_by_token_handler::moderation_get_job_by_token_handler; use crate::http_server::endpoints::moderation::user_feature_flags::moderator_edit_user_feature_flags_handler::moderator_edit_user_feature_flags_handler; use crate::http_server::endpoints::moderation::user_feature_flags::moderator_list_all_available_user_feature_flags_handler::moderator_list_all_available_user_feature_flags_handler; use crate::http_server::endpoints::moderation::user_feature_flags::moderator_list_user_feature_flags_handler::moderator_list_user_feature_flags_handler; @@ -61,6 +63,12 @@ pub fn add_moderator_routes (app: App) -> App .route(web::head().to(|| HttpResponse::Ok())) ) ) + .service(web::scope("/debug_logs") + .service(web::resource("/list/{token}") + .route(web::get().to(moderation_list_debug_logs_for_token_handler)) + .route(web::head().to(|| HttpResponse::Ok())) + ) + ) .service(web::resource("/user_feature_flags/list") .route(web::get().to(moderator_list_all_available_user_feature_flags_handler)) .route(web::head().to(|| HttpResponse::Ok())) @@ -88,6 +96,10 @@ pub fn add_moderator_routes (app: App) -> App .route(web::head().to(|| HttpResponse::Ok())) ) ) + .service(web::resource("/job/{token}") + .route(web::get().to(moderation_get_job_by_token_handler)) + .route(web::head().to(|| HttpResponse::Ok())) + ) .service(web::scope("/jobs") .service(web::resource("/user/{user_token}/list") .route(web::get().to(list_user_jobs_handler))