Compare commits

...
Author SHA1 Message Date
Brandon Thomas bb93924652 refactor routes 2026-03-05 08:14:48 -05:00
Brandon Thomas 6c4c683639 codegen db 2026-03-05 08:04:50 -05:00
Brandon Thomas 5ec18490f5 user jobs moved / refactored 2026-03-05 08:04:29 -05:00
Brandon Thomas 23ab957105 user jobs mod handler 2026-03-05 07:55:29 -05:00
13 changed files with 280 additions and 2 deletions
@@ -0,0 +1,102 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n j.status as `job_status: enums::common::job_status_plus::JobStatusPlus`,\n j.failure_reason as job_failure_reason,\n wle.credits_delta,\n wle.maybe_linked_refund_ledger_token as `maybe_linked_refund_ledger_token: tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken`,\n j.on_success_result_entity_token as `on_success_result_media_token: tokens::tokens::media_files::MediaFileToken`,\n j.token as `job_token: tokens::tokens::generic_inference_jobs::InferenceJobToken`,\n wle.token as `wallet_ledger_entry_token: tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken`,\n wle.entry_type as `wallet_ledger_entry_type: enums::by_table::wallet_ledger_entries::wallet_ledger_entry_type::WalletLedgerEntryType`\nFROM generic_inference_jobs as j\nLEFT OUTER JOIN users as u\n ON j.maybe_creator_user_token = u.token\nLEFT OUTER JOIN wallet_ledger_entries as wle\n ON wle.maybe_entity_ref = j.token\nWHERE u.token = ?\nORDER BY j.id DESC\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "job_status: enums::common::job_status_plus::JobStatusPlus",
"type_info": {
"type": "String",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | ENUM",
"char_set": 224,
"max_size": 76
}
},
{
"ordinal": 1,
"name": "job_failure_reason",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 2048
}
},
{
"ordinal": 2,
"name": "credits_delta",
"type_info": {
"type": "Long",
"flags": "",
"char_set": 63,
"max_size": 11
}
},
{
"ordinal": 3,
"name": "maybe_linked_refund_ledger_token: tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 4,
"name": "on_success_result_media_token: tokens::tokens::media_files::MediaFileToken",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 5,
"name": "job_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": 6,
"name": "wallet_ledger_entry_token: tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken",
"type_info": {
"type": "VarString",
"flags": "UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 7,
"name": "wallet_ledger_entry_type: enums::by_table::wallet_ledger_entries::wallet_ledger_entry_type::WalletLedgerEntryType",
"type_info": {
"type": "VarString",
"flags": "BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
true,
true,
true,
true,
false,
true,
true
]
},
"hash": "0757d81ba153b2ad2df25df3dd92b655ef08b95f5f83251bf3573f9be3a9f8d1"
}
@@ -0,0 +1 @@
pub mod user;
@@ -0,0 +1,33 @@
use enums::by_table::wallet_ledger_entries::wallet_ledger_entry_type::WalletLedgerEntryType;
use enums::common::job_status_plus::JobStatusPlus;
use serde_derive::{Deserialize, Serialize};
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use tokens::tokens::media_files::MediaFileToken;
use tokens::tokens::users::UserToken;
use tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken;
use utoipa::ToSchema;
pub const LIST_USER_JOBS_PATH: &str = "/v1/moderation/jobs/user/{user_token}/list";
#[derive(Deserialize, ToSchema)]
pub struct ListUserJobsPathInfo {
pub user_token: UserToken,
}
#[derive(Serialize, ToSchema)]
pub struct ListUserJobsResponse {
pub success: bool,
pub jobs: Vec<ListUserJobsEntry>,
}
#[derive(Serialize, ToSchema)]
pub struct ListUserJobsEntry {
pub job_status: JobStatusPlus,
pub job_failure_reason: Option<String>,
pub credits_delta: Option<i32>,
pub maybe_linked_refund_ledger_token: Option<WalletLedgerEntryToken>,
pub on_success_result_media_token: Option<MediaFileToken>,
pub job_token: InferenceJobToken,
pub wallet_ledger_entry_token: Option<WalletLedgerEntryToken>,
pub wallet_ledger_entry_type: Option<WalletLedgerEntryType>,
}
@@ -0,0 +1 @@
pub mod list_user_jobs;
@@ -1 +1,2 @@
pub mod jobs;
pub mod user;
@@ -0,0 +1,60 @@
use anyhow::anyhow;
use log::warn;
use sqlx::MySqlPool;
use enums::by_table::wallet_ledger_entries::wallet_ledger_entry_type::WalletLedgerEntryType;
use enums::common::job_status_plus::JobStatusPlus;
use errors::AnyhowResult;
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use tokens::tokens::media_files::MediaFileToken;
use tokens::tokens::users::UserToken;
use tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken;
pub struct UserJobForModerationResult {
pub job_status: JobStatusPlus,
pub job_failure_reason: Option<String>,
pub credits_delta: Option<i32>,
pub maybe_linked_refund_ledger_token: Option<WalletLedgerEntryToken>,
pub on_success_result_media_token: Option<MediaFileToken>,
pub job_token: InferenceJobToken,
pub wallet_ledger_entry_token: Option<WalletLedgerEntryToken>,
pub wallet_ledger_entry_type: Option<WalletLedgerEntryType>,
}
pub async fn list_user_jobs_for_moderation(
user_token: &UserToken,
mysql_pool: &MySqlPool,
) -> AnyhowResult<Vec<UserJobForModerationResult>> {
let results = sqlx::query_as!(
UserJobForModerationResult,
r#"
SELECT
j.status as `job_status: enums::common::job_status_plus::JobStatusPlus`,
j.failure_reason as job_failure_reason,
wle.credits_delta,
wle.maybe_linked_refund_ledger_token as `maybe_linked_refund_ledger_token: tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken`,
j.on_success_result_entity_token as `on_success_result_media_token: tokens::tokens::media_files::MediaFileToken`,
j.token as `job_token: tokens::tokens::generic_inference_jobs::InferenceJobToken`,
wle.token as `wallet_ledger_entry_token: tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken`,
wle.entry_type as `wallet_ledger_entry_type: enums::by_table::wallet_ledger_entries::wallet_ledger_entry_type::WalletLedgerEntryType`
FROM generic_inference_jobs as j
LEFT OUTER JOIN users as u
ON j.maybe_creator_user_token = u.token
LEFT OUTER JOIN wallet_ledger_entries as wle
ON wle.maybe_entity_ref = j.token
WHERE u.token = ?
ORDER BY j.id DESC
"#,
user_token,
)
.fetch_all(mysql_pool)
.await;
match results {
Ok(records) => Ok(records),
Err(err) => {
warn!("list_user_jobs_for_moderation query error: {:?}", err);
Err(anyhow!("query error"))
}
}
}
@@ -5,6 +5,7 @@ pub mod get_pending_inference_job_count;
pub mod insert_generic_inference_job;
pub mod job_status;
pub mod kill_generic_inference_jobs;
pub mod list_user_jobs_for_moderation;
pub mod kill_jobs_in_development;
pub mod list_session_jobs;
pub mod mark_generic_inference_job_cancelled_by_user;
@@ -267,6 +267,7 @@ use artcraft_api_defs::media_file::list_batch_generated_media_files::*;
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 artcraft_api_defs::moderation::jobs::user::list_user_jobs::*;
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::*;
@@ -438,6 +439,7 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux
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,
crate::http_server::endpoints::moderation::jobs::user::list_user_jobs_handler::list_user_jobs_handler,
// Credits
crate::http_server::endpoints::credits::get_session_credits_handler::get_session_credits_handler,
// Subscriptions
@@ -621,6 +623,9 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux
ModeratorUserLookupByStripeCustomerIdRequest,
ModeratorUserLookupByStripeCustomerIdResponse,
ModeratorUserLookupByStripeCustomerIdEntry,
ListUserJobsPathInfo,
ListUserJobsResponse,
ListUserJobsEntry,
GptImage1EditImageRequest,
GptImage1EditImageImageSize,
GptImage1EditImageNumImages,
@@ -1,3 +1,4 @@
pub mod get_tts_inference_queue_count;
pub mod get_w2l_inference_queue_count;
pub mod kill_tts_inference_jobs;
pub mod kill_tts_inference_jobs;
pub mod user;
@@ -0,0 +1,64 @@
use std::sync::Arc;
use actix_web::web::{Json, Path};
use actix_web::{web, HttpRequest};
use log::warn;
use artcraft_api_defs::moderation::jobs::user::list_user_jobs::{
ListUserJobsEntry,
ListUserJobsPathInfo,
ListUserJobsResponse,
};
use mysql_queries::queries::generic_inference::web::list_user_jobs_for_moderation::list_user_jobs_for_moderation;
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;
/// List jobs for a user (moderation)
#[utoipa::path(
get,
tag = "Moderation",
path = "/v1/moderation/jobs/user/{user_token}/list",
responses(
(status = 200, description = "Success", body = ListUserJobsResponse),
(status = 401, description = "Unauthorized"),
(status = 500, description = "Server error"),
),
params(
("user_token" = UserToken, Path, description = "User token to look up jobs for"),
)
)]
pub async fn list_user_jobs_handler(
http_request: HttpRequest,
path: Path<ListUserJobsPathInfo>,
server_state: web::Data<Arc<ServerState>>,
) -> Result<Json<ListUserJobsResponse>, CommonWebError> {
let _user_session = require_moderator(&http_request, &server_state, UseDatabase::Implicit)
.await
.map_err(|_| CommonWebError::NotAuthorized)?;
let results = list_user_jobs_for_moderation(&path.user_token, &server_state.mysql_pool)
.await
.map_err(|err| {
warn!("list_user_jobs error: {:?}", err);
CommonWebError::ServerError
})?;
let jobs = results.into_iter().map(|row| ListUserJobsEntry {
job_status: row.job_status,
job_failure_reason: row.job_failure_reason,
credits_delta: row.credits_delta,
maybe_linked_refund_ledger_token: row.maybe_linked_refund_ledger_token,
on_success_result_media_token: row.on_success_result_media_token,
job_token: row.job_token,
wallet_ledger_entry_token: row.wallet_ledger_entry_token,
wallet_ledger_entry_type: row.wallet_ledger_entry_type,
}).collect();
Ok(Json(ListUserJobsResponse {
success: true,
jobs,
}))
}
@@ -0,0 +1 @@
pub mod list_user_jobs_handler;
@@ -3,4 +3,4 @@ pub mod ip_bans;
pub mod jobs;
pub mod user;
pub mod user_bans;
pub mod user_feature_flags;
pub mod user_feature_flags;
@@ -19,6 +19,7 @@ use crate::http_server::endpoints::moderation::ip_bans::add_ip_ban::add_ip_ban_h
use crate::http_server::endpoints::moderation::ip_bans::delete_ip_ban::delete_ip_ban_handler;
use crate::http_server::endpoints::moderation::ip_bans::get_ip_ban::get_ip_ban_handler;
use crate::http_server::endpoints::moderation::ip_bans::list_ip_bans::list_ip_bans_handler;
use crate::http_server::endpoints::moderation::jobs::user::list_user_jobs_handler::list_user_jobs_handler;
use crate::http_server::endpoints::moderation::jobs::get_tts_inference_queue_count::get_tts_inference_queue_count_handler;
use crate::http_server::endpoints::moderation::jobs::get_w2l_inference_queue_count::get_w2l_inference_queue_count_handler;
use crate::http_server::endpoints::moderation::jobs::kill_tts_inference_jobs::kill_tts_inference_jobs_handler;
@@ -54,7 +55,14 @@ pub fn add_moderator_routes<T, B> (app: App<T>) -> App<T>
.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))
.route(web::head().to(|| HttpResponse::Ok()))
)
)
)
// NB: Old paths are not under /v1 -
.service(web::scope("/moderation")
.service(web::resource("/staff")
.route(web::get().to(list_staff_handler))